1 | /**
|
2 | * @fileoverview Utility for executing npm commands.
|
3 | * @author Ian VanSchooten
|
4 | */
|
5 |
|
6 | ;
|
7 |
|
8 | //------------------------------------------------------------------------------
|
9 | // Requirements
|
10 | //------------------------------------------------------------------------------
|
11 |
|
12 | const fs = require("fs"),
|
13 | spawn = require("cross-spawn"),
|
14 | path = require("path"),
|
15 | log = require("../shared/logging");
|
16 |
|
17 | //------------------------------------------------------------------------------
|
18 | // Helpers
|
19 | //------------------------------------------------------------------------------
|
20 |
|
21 | /**
|
22 | * Find the closest package.json file, starting at process.cwd (by default),
|
23 | * and working up to root.
|
24 | * @param {string} [startDir=process.cwd()] Starting directory
|
25 | * @returns {string} Absolute path to closest package.json file
|
26 | */
|
27 | function findPackageJson(startDir) {
|
28 | let dir = path.resolve(startDir || process.cwd());
|
29 |
|
30 | do {
|
31 | const pkgFile = path.join(dir, "package.json");
|
32 |
|
33 | if (!fs.existsSync(pkgFile) || !fs.statSync(pkgFile).isFile()) {
|
34 | dir = path.join(dir, "..");
|
35 | continue;
|
36 | }
|
37 | return pkgFile;
|
38 | } while (dir !== path.resolve(dir, ".."));
|
39 | return null;
|
40 | }
|
41 |
|
42 | //------------------------------------------------------------------------------
|
43 | // Private
|
44 | //------------------------------------------------------------------------------
|
45 |
|
46 | /**
|
47 | * Install node modules synchronously and save to devDependencies in package.json
|
48 | * @param {string|string[]} packages Node module or modules to install
|
49 | * @returns {void}
|
50 | */
|
51 | function installSyncSaveDev(packages) {
|
52 | const packageList = Array.isArray(packages) ? packages : [packages];
|
53 | const npmProcess = spawn.sync("npm", ["i", "--save-dev"].concat(packageList),
|
54 | { stdio: "inherit" });
|
55 | const error = npmProcess.error;
|
56 |
|
57 | if (error && error.code === "ENOENT") {
|
58 | const pluralS = packageList.length > 1 ? "s" : "";
|
59 |
|
60 | log.error(`Could not execute npm. Please install the following package${pluralS} with a package manager of your choice: ${packageList.join(", ")}`);
|
61 | }
|
62 | }
|
63 |
|
64 | /**
|
65 | * Fetch `peerDependencies` of the given package by `npm show` command.
|
66 | * @param {string} packageName The package name to fetch peerDependencies.
|
67 | * @returns {Object} Gotten peerDependencies. Returns null if npm was not found.
|
68 | */
|
69 | function fetchPeerDependencies(packageName) {
|
70 | const npmProcess = spawn.sync(
|
71 | "npm",
|
72 | ["show", "--json", packageName, "peerDependencies"],
|
73 | { encoding: "utf8" }
|
74 | );
|
75 |
|
76 | const error = npmProcess.error;
|
77 |
|
78 | if (error && error.code === "ENOENT") {
|
79 | return null;
|
80 | }
|
81 | const fetchedText = npmProcess.stdout.trim();
|
82 |
|
83 | return JSON.parse(fetchedText || "{}");
|
84 |
|
85 |
|
86 | }
|
87 |
|
88 | /**
|
89 | * Check whether node modules are include in a project's package.json.
|
90 | * @param {string[]} packages Array of node module names
|
91 | * @param {Object} opt Options Object
|
92 | * @param {boolean} opt.dependencies Set to true to check for direct dependencies
|
93 | * @param {boolean} opt.devDependencies Set to true to check for development dependencies
|
94 | * @param {boolean} opt.startdir Directory to begin searching from
|
95 | * @returns {Object} An object whose keys are the module names
|
96 | * and values are booleans indicating installation.
|
97 | */
|
98 | function check(packages, opt) {
|
99 | const deps = new Set();
|
100 | const pkgJson = (opt) ? findPackageJson(opt.startDir) : findPackageJson();
|
101 | let fileJson;
|
102 |
|
103 | if (!pkgJson) {
|
104 | throw new Error("Could not find a package.json file. Run 'npm init' to create one.");
|
105 | }
|
106 |
|
107 | try {
|
108 | fileJson = JSON.parse(fs.readFileSync(pkgJson, "utf8"));
|
109 | } catch (e) {
|
110 | const error = new Error(e);
|
111 |
|
112 | error.messageTemplate = "failed-to-read-json";
|
113 | error.messageData = {
|
114 | path: pkgJson,
|
115 | message: e.message
|
116 | };
|
117 | throw error;
|
118 | }
|
119 |
|
120 | ["dependencies", "devDependencies"].forEach(key => {
|
121 | if (opt[key] && typeof fileJson[key] === "object") {
|
122 | Object.keys(fileJson[key]).forEach(dep => deps.add(dep));
|
123 | }
|
124 | });
|
125 |
|
126 | return packages.reduce((status, pkg) => {
|
127 | status[pkg] = deps.has(pkg);
|
128 | return status;
|
129 | }, {});
|
130 | }
|
131 |
|
132 | /**
|
133 | * Check whether node modules are included in the dependencies of a project's
|
134 | * package.json.
|
135 | *
|
136 | * Convenience wrapper around check().
|
137 | * @param {string[]} packages Array of node modules to check.
|
138 | * @param {string} rootDir The directory containing a package.json
|
139 | * @returns {Object} An object whose keys are the module names
|
140 | * and values are booleans indicating installation.
|
141 | */
|
142 | function checkDeps(packages, rootDir) {
|
143 | return check(packages, { dependencies: true, startDir: rootDir });
|
144 | }
|
145 |
|
146 | /**
|
147 | * Check whether node modules are included in the devDependencies of a project's
|
148 | * package.json.
|
149 | *
|
150 | * Convenience wrapper around check().
|
151 | * @param {string[]} packages Array of node modules to check.
|
152 | * @returns {Object} An object whose keys are the module names
|
153 | * and values are booleans indicating installation.
|
154 | */
|
155 | function checkDevDeps(packages) {
|
156 | return check(packages, { devDependencies: true });
|
157 | }
|
158 |
|
159 | /**
|
160 | * Check whether package.json is found in current path.
|
161 | * @param {string} [startDir] Starting directory
|
162 | * @returns {boolean} Whether a package.json is found in current path.
|
163 | */
|
164 | function checkPackageJson(startDir) {
|
165 | return !!findPackageJson(startDir);
|
166 | }
|
167 |
|
168 | //------------------------------------------------------------------------------
|
169 | // Public Interface
|
170 | //------------------------------------------------------------------------------
|
171 |
|
172 | module.exports = {
|
173 | installSyncSaveDev,
|
174 | fetchPeerDependencies,
|
175 | checkDeps,
|
176 | checkDevDeps,
|
177 | checkPackageJson
|
178 | };
|