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