UNPKG

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