UNPKG

3.62 kBJavaScriptView Raw
1#!/usr/bin/env node
2
3/**
4 * @param {string} command process to run
5 * @param {string[]} args command line arguments
6 * @returns {Promise<void>} promise
7 */
8const runCommand = (command, args) => {
9 const cp = require("child_process");
10 return new Promise((resolve, reject) => {
11 const executedCommand = cp.spawn(command, args, {
12 stdio: "inherit",
13 shell: true
14 });
15
16 executedCommand.on("error", error => {
17 reject(error);
18 });
19
20 executedCommand.on("exit", code => {
21 if (code === 0) {
22 resolve();
23 } else {
24 reject();
25 }
26 });
27 });
28};
29
30/**
31 * @param {string} packageName name of the package
32 * @returns {boolean} is the package installed?
33 */
34const isInstalled = packageName => {
35 try {
36 require.resolve(packageName);
37
38 return true;
39 } catch (err) {
40 return false;
41 }
42};
43
44/**
45 * @param {CliOption} cli options
46 * @returns {void}
47 */
48const runCli = cli => {
49 const path = require("path");
50 const pkgPath = require.resolve(`${cli.package}/package.json`);
51 // eslint-disable-next-line node/no-missing-require
52 const pkg = require(pkgPath);
53 // eslint-disable-next-line node/no-missing-require
54 require(path.resolve(path.dirname(pkgPath), pkg.bin[cli.binName]));
55};
56
57/**
58 * @typedef {Object} CliOption
59 * @property {string} name display name
60 * @property {string} package npm package name
61 * @property {string} binName name of the executable file
62 * @property {boolean} installed currently installed?
63 * @property {string} url homepage
64 */
65
66/** @type {CliOption} */
67const cli = {
68 name: "webpack-cli",
69 package: "webpack-cli",
70 binName: "webpack-cli",
71 installed: isInstalled("webpack-cli"),
72 url: "https://github.com/webpack/webpack-cli"
73};
74
75if (!cli.installed) {
76 const path = require("path");
77 const fs = require("graceful-fs");
78 const readLine = require("readline");
79
80 const notify =
81 "CLI for webpack must be installed.\n" + ` ${cli.name} (${cli.url})\n`;
82
83 console.error(notify);
84
85 let packageManager;
86
87 if (fs.existsSync(path.resolve(process.cwd(), "yarn.lock"))) {
88 packageManager = "yarn";
89 } else if (fs.existsSync(path.resolve(process.cwd(), "pnpm-lock.yaml"))) {
90 packageManager = "pnpm";
91 } else {
92 packageManager = "npm";
93 }
94
95 const installOptions = [packageManager === "yarn" ? "add" : "install", "-D"];
96
97 console.error(
98 `We will use "${packageManager}" to install the CLI via "${packageManager} ${installOptions.join(
99 " "
100 )} ${cli.package}".`
101 );
102
103 const question = `Do you want to install 'webpack-cli' (yes/no): `;
104
105 const questionInterface = readLine.createInterface({
106 input: process.stdin,
107 output: process.stderr
108 });
109
110 // In certain scenarios (e.g. when STDIN is not in terminal mode), the callback function will not be
111 // executed. Setting the exit code here to ensure the script exits correctly in those cases. The callback
112 // function is responsible for clearing the exit code if the user wishes to install webpack-cli.
113 process.exitCode = 1;
114 questionInterface.question(question, answer => {
115 questionInterface.close();
116
117 const normalizedAnswer = answer.toLowerCase().startsWith("y");
118
119 if (!normalizedAnswer) {
120 console.error(
121 "You need to install 'webpack-cli' to use webpack via CLI.\n" +
122 "You can also install the CLI manually."
123 );
124
125 return;
126 }
127 process.exitCode = 0;
128
129 console.log(
130 `Installing '${
131 cli.package
132 }' (running '${packageManager} ${installOptions.join(" ")} ${
133 cli.package
134 }')...`
135 );
136
137 runCommand(packageManager, installOptions.concat(cli.package))
138 .then(() => {
139 runCli(cli);
140 })
141 .catch(error => {
142 console.error(error);
143 process.exitCode = 1;
144 });
145 });
146} else {
147 runCli(cli);
148}