UNPKG

install-peerdeps

Version:
280 lines (261 loc) 10 kB
"use strict"; var _child_process = require("child_process"); var _fs = _interopRequireDefault(require("fs")); var _path = _interopRequireDefault(require("path")); var _tape = _interopRequireDefault(require("tape")); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } /** * Spawns the CLI with the provided arguments * @param {string[]} extraArgs - arguments to be passed to the CLI * @param {string} cwd - the fixtures directory to run the command in * @returns {ChildProcess} - an EventEmitter that represents the spawned child process */ function spawnCli(extraArgs, cwd = "sandbox") { const fullCwd = _path.default.join(__dirname, "..", "fixtures", cwd); // Clean up sandbox before and after every run if (cwd === "sandbox") { _fs.default.copyFileSync(_path.default.join(fullCwd, "package-clean.json"), _path.default.join(fullCwd, "package.json")); } const cli = (0, _child_process.spawn)("node", [...["--require", "@babel/register", _path.default.join(__dirname, "cli.js")], ...extraArgs], { cwd: fullCwd }); // Clean up sandbox before and after every run cli.on("exit", () => { if (cwd === "sandbox") { _fs.default.copyFileSync(_path.default.join(fullCwd, "package-clean.json"), _path.default.join(fullCwd, "package.json")); } }); return cli; } /** * Gets the resulting CLI dry-run output given the provided arguments * @async * @param {string[]} extraArgs - arguments to be passed to the CLI * @param {string} cwd - the fixtures directory to run the command in * @param {boolean} noDryRun - whether to actually run the command or not * @returns {Promise<[number, string, string]>} - a Promise which resolves to an array contaning [exit code, stderr, stdout] */ async function getCliOutput(extraArgs, cwd = "sandbox") { return new Promise((resolve, reject) => { // Always do dry run, so the command is the last // non-whitespace line written to stdout const cli = spawnCli([...extraArgs, "--dry-run"], cwd); const fullstdout = []; const fullstderr = []; cli.stdout.on("data", data => { // not guaranteed to be line-by-line in fact these can be Buffers fullstdout.push(String(data)); }); cli.stderr.on("data", data => { // not guaranteed to be line-by-line in fact these can be Buffers fullstderr.push(String(data)); }); cli.on("close", code => { resolve([code, fullstderr.join(""), fullstdout.join("")]); }); // Make sure to call reject() on error so that the Promise // doesn't hang forever cli.on("error", err => reject(err)); }); } /** * Gets the resulting dry-run install command given the provided arguments * @async * @param {string[]} extraArgs - arguments to be passed to the CLI * @param {string} cwd - the fixtures directory to run the command in * @param {boolean} noDryRun - whether to actually run the command or not * @returns {Promise<string>} - a Promise which resolves to the resulting install command */ async function getCliInstallCommand(extraArgs, cwd = "sandbox") { const [code, stderr, stdout] = await getCliOutput(extraArgs, cwd); if (code !== 0) { throw new Error(`Unsuccessful exit code. ${stderr}`); } // The command will be the last non-whitespace line written to // stdout by the cli during a dry run const lines = stdout.split(/\r?\n/g).filter(l => !!l.trim()); return lines[lines.length - 1]; } (0, _tape.default)("errors when more than one package is provided", t => { const cli = spawnCli(["eslint-config-airbnb", "angular"]); cli.on("exit", code => { // We should be able to do t.equal(code, 1), but earlier Node versions // handle uncaught exceptions differently so we can't (0.10 returns 8, // 0.12 returns 9). t.notEqual(code, 0, `errored, exit code was ${code}.`); t.end(); }); }); (0, _tape.default)("errors when no arguments are provided", t => { const cli = spawnCli([]); cli.on("exit", code => { t.notEqual(code, 0, `errored, exit code was ${code}`); t.end(); }); }); (0, _tape.default)("errors when the package name argument is formatted incorrectly", t => { const cli = spawnCli(["heyhe#@&*()"]); cli.on("exit", code => { t.notEqual(code, 0, `errored, exit code was ${code}`); t.end(); }); }); (0, _tape.default)("installs a package with no peer dependencies without errors, but prints a warning", async t => { t.plan(2); try { const [, stderr] = await getCliOutput(["postcss@^8.1.0"]); t.equal(/\bno peer dependencies\b/i.test(stderr), true); t.equal(/\binstalling anyway\b/i.test(stderr), true); } catch (err) { t.fail(err); } }); (0, _tape.default)("installs successfully when a version range is provided that results in `npm info` returning an array", async t => { t.plan(1); try { const command = await getCliInstallCommand(["postcss@^8.1.0"]); t.equal(/\bpostcss\b/.test(command), true); } catch (err) { t.fail(err); } }); (0, _tape.default)("only installs peerDependencies when `--only-peers` is specified", async t => { t.plan(1); try { const command = await getCliInstallCommand(["eslint-config-airbnb", "--only-peers"]); t.equal(/ eslint-config-airbnb /.test(command), false); } catch (err) { t.fail(err); } }); (0, _tape.default)("adds explicit `--save-dev` flag when using `-D, -d, --dev` with npm", async t => { const flags = ["-D", "-d", "--dev"]; t.plan(flags.length); try { const commands = await Promise.all(flags.map(flag => getCliInstallCommand(["eslint-config-airbnb", flag]))); commands.forEach((command, i) => t.equal(/ --save-dev\b/.test(command), true, `flag: \`${flags[i]}\``)); } catch (err) { t.fail(err); } }); (0, _tape.default)("places `global` as first arg following `yarn` when using yarn and `--global` is specified", async t => { t.plan(1); try { const command = await getCliInstallCommand(["eslint-config-airbnb", "--global", "-Y"]); t.equal(/^yarn global/.test(command), true); } catch (err) { t.fail(err); } }); (0, _tape.default)("adds explicit `--global` flag when using `--global` with npm", async t => { t.plan(1); try { const command = await getCliInstallCommand(["eslint-config-airbnb", "--global"]); t.equal(/\bnpm\s--global\b/.test(command), true); } catch (err) { t.fail(err); } }); (0, _tape.default)("does not add `--save` when using `--global` with npm", async t => { t.plan(1); try { const command = await getCliInstallCommand(["eslint-config-airbnb", "--global"]); t.equal(/\s--save\b/.test(command), false); } catch (err) { t.fail(err); } }); (0, _tape.default)("grabs peer dependencies from the local `node_modules` directory when using `--no-registry`", async t => { t.plan(2); try { const command = await getCliInstallCommand(["dummy-local-package", "--no-registry"], "no-registry"); t.equal(/\sdummy-local-package@/.test(command), true); t.equal(/\sdummy-peer-dependency@\^1\.0\.0\s/.test(command), true); } catch (err) { t.fail(err); } }); (0, _tape.default)("adds an explicit `--no-save` when using `--silent` with npm", async t => { t.plan(1); try { const command = await getCliInstallCommand(["eslint-config-airbnb", "--silent"]); t.equal(/\s--no-save\b/.test(command), true); } catch (err) { t.fail(err); } }); (0, _tape.default)("installs with pnpm successfully", t => { const cli = spawnCli(["eslint-config-airbnb", "--pnpm"], "pnpm"); cli.on("data", data => { t.comment(data); }); cli.on("exit", code => { if (code !== 0) { t.fail(`CLI exited with error code ${code}`); } const pnpmLockYamlPath = _path.default.resolve(__dirname, "..", "fixtures", "pnpm", "pnpm-lock.yaml"); const hasPnpmLockYaml = _fs.default.existsSync(pnpmLockYamlPath); t.equal(hasPnpmLockYaml, true); // Delete pnpm-lock.yaml file try { _fs.default.unlinkSync(pnpmLockYamlPath); } catch (e) { /**/ } t.end(); }); }); // See https://github.com/nathanhleung/install-peerdeps/issues/33 // test("installs packages correctly even if package name ends with '-0'", t => { // const cli = spawnCli(["enzyme-adapter-react-16@1.1.1"]); // cli.on("exit", code => { // t.equal(code, 0, `errored, exit code was ${code}.`); // t.end(); // }); // }); // Work on this test later /* test("doesn't replace existing installed peer dependencies", t => { const fullCwd = path.join(__dirname, "..", "fixtures", "replace"); const npm = spawn("npm", ["install", "data-forge"], { cwd: fullCwd }); function onCliExit(code, forgeVersion) { if (code !== 0) { t.fail(`errored, cli exit code was ${code}.`); } fs.readFile(`${fullCwd}/package.json`, "utf8", (err, data) => { if (err) { t.fail(`errored, couldn't open package.json`); } const pkg = JSON.parse(data); const newForgeVersion = pkg.dependencies["data-forge"]; t.equal( newForgeVersion, forgeVersion, `error, replaced old peer. ${newForgeVersion} replaced ${forgeVersion}` ); }); } npm.on("exit", code => { if (code !== 0) { t.fail(`errored, npm install exit code was ${code}.`); } fs.readFile(`${fullCwd}/package.json`, "utf8", (err, data) => { if (err) { t.fail(`errored, couldn't open package.json`); } const pkg = JSON.parse(data); const forgeVersion = pkg.dependencies["data-forge"]; const cli = spawnCli(["data-forge-indicators"], "replace"); cli.on("exit", cliExitCode => onCliExit(cliExitCode, forgeVersion)); }); }); }); */ // @todo - tests for the actual install process // see https://github.com/sindresorhus/has-yarn/blob/master/test.js for details // Perhaps abstract the functionality of getting the package name // into its own function, and test that // Also see commander tests // https://github.com/tj/commander.js/blob/master/test/test.arguments.js