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