1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 | "use strict";
|
36 |
|
37 | const validateProjectName = require("validate-npm-package-name");
|
38 | const chalk = require("chalk");
|
39 | const commander = require("commander");
|
40 | const fs = require("fs-extra");
|
41 | const path = require("path");
|
42 | const execSync = require("child_process").execSync;
|
43 | const spawn = require("cross-spawn");
|
44 | const dns = require("dns");
|
45 | const url = require("url");
|
46 | const envinfo = require("envinfo");
|
47 |
|
48 | const packageJson = require("./package.json");
|
49 | const _wpThemeVersion = packageJson.version;
|
50 | const _createReactAppVersion = _wpThemeVersion.split("-wp.")[0];
|
51 |
|
52 |
|
53 | const _reactScriptsWpThemeVersion = "3.2.0-wp.2";
|
54 | const _getScriptsPath = function() {
|
55 | return scriptsFromNpm();
|
56 | };
|
57 |
|
58 | const scriptsFromNpm = function() {
|
59 |
|
60 | return {
|
61 | path: `@devloco/react-scripts-wptheme@${_reactScriptsWpThemeVersion}`,
|
62 | callback: function() {}
|
63 | };
|
64 | };
|
65 |
|
66 | const scriptsFromGit = function() {
|
67 | console.log("SCRIPTS FROM GIT");
|
68 | const deleteFolderRecursive = (path) => {
|
69 | if (fs.existsSync(path)) {
|
70 | fs.readdirSync(path).forEach(function(file) {
|
71 | let curPath = path + "/" + file;
|
72 | if (fs.statSync(curPath).isDirectory()) {
|
73 |
|
74 | deleteFolderRecursive(curPath);
|
75 | } else {
|
76 |
|
77 | fs.unlinkSync(curPath);
|
78 | }
|
79 | });
|
80 |
|
81 | fs.rmdirSync(path);
|
82 | }
|
83 | };
|
84 |
|
85 | const tempFolderName = "temp";
|
86 | fs.ensureDirSync(tempFolderName);
|
87 | process.chdir(tempFolderName);
|
88 | const tempPath = process.cwd();
|
89 | console.log(chalk.magenta("Cloning @devloco/create-react-app/react-scripts from GitHub..."));
|
90 | execSync("git clone https://github.com/devloco/create-react-app.git");
|
91 | process.chdir("..");
|
92 | let scriptsPath = "file:" + path.join(tempPath, "create-react-app", "packages", "react-scripts");
|
93 | return {
|
94 | path: scriptsPath,
|
95 | callback: function() {
|
96 | deleteFolderRecursive(tempPath);
|
97 | }
|
98 | };
|
99 | };
|
100 |
|
101 | let projectName;
|
102 | const program = new commander.Command(packageJson.name)
|
103 | .version(packageJson.version)
|
104 | .arguments("<project-directory>")
|
105 | .usage(`${chalk.green("<project-directory>")} [options]`)
|
106 | .action((name) => {
|
107 | projectName = name;
|
108 | })
|
109 | .option("--verbose", "force create-react-app to print additional logs (NOTE: create-react-wptheme is always verbose)")
|
110 | .option("--info", "print environment debug info")
|
111 | .option("--use-npm", "force downloading packages using npm instead of yarn (if both are installed)")
|
112 | .option("--use-pnp")
|
113 | .option("--typescript")
|
114 | .allowUnknownOption()
|
115 | .on("--help", () => {
|
116 | console.log(` Only ${chalk.green("<project-directory>")} is required.`);
|
117 | console.log();
|
118 | console.log(` If you have any problems, do not hesitate to file an issue:`);
|
119 | console.log(` ${chalk.cyan("https://github.com/devloco/create-react-wptheme/issues/new")}`);
|
120 | console.log();
|
121 | })
|
122 | .parse(process.argv);
|
123 |
|
124 | if (program.info) {
|
125 | console.log(chalk.bold("\nEnvironment Info:"));
|
126 | return envinfo
|
127 | .run(
|
128 | {
|
129 | System: ["OS", "CPU"],
|
130 | Binaries: ["Node", "npm", "Yarn"],
|
131 | Browsers: ["Chrome", "Edge", "Internet Explorer", "Firefox", "Safari"],
|
132 | npmPackages: ["react", "react-dom", "react-scripts"],
|
133 | npmGlobalPackages: ["create-react-app"]
|
134 | },
|
135 | {
|
136 | duplicates: true,
|
137 | showNotFound: true
|
138 | }
|
139 | )
|
140 | .then(console.log);
|
141 | }
|
142 |
|
143 | if (typeof projectName === "undefined") {
|
144 | console.error("Please specify the project directory:");
|
145 | console.log(` ${chalk.cyan(program.name())} ${chalk.green("<project-directory>")}`);
|
146 | console.log();
|
147 | console.log("For example:");
|
148 | console.log(` ${chalk.cyan(program.name())} ${chalk.green("my-react-app")}`);
|
149 | console.log();
|
150 | console.log(`Run ${chalk.cyan(`${program.name()} --help`)} to see all options.`);
|
151 | process.exit(1);
|
152 | }
|
153 |
|
154 | function printValidationResults(results) {
|
155 | if (typeof results !== "undefined") {
|
156 | results.forEach((error) => {
|
157 | console.error(chalk.red(` * ${error}`));
|
158 | });
|
159 | }
|
160 | }
|
161 |
|
162 | console.log(program.name() + " version: " + chalk.magenta(_wpThemeVersion));
|
163 | console.log("@devloco/react-scripts-wptheme version: " + chalk.magenta(_reactScriptsWpThemeVersion));
|
164 | console.log("create-react-app version: " + chalk.magenta(_createReactAppVersion));
|
165 | createApp(projectName, program.verbose, program.scriptsVersion, program.useNpm, program.usePnp, program.typescript);
|
166 |
|
167 | function createApp(name, verbose, version, useNpm, usePnp, useTypescript, template) {
|
168 | const root = path.resolve(name);
|
169 | const appName = path.basename(root);
|
170 |
|
171 | checkAppName(appName);
|
172 | fs.ensureDirSync(name);
|
173 |
|
174 | console.log(`Creating a new React WP theme in ${chalk.green(root)}.`);
|
175 | console.log();
|
176 |
|
177 | let useYarn = useNpm ? false : shouldUseYarn();
|
178 |
|
179 | const originalDirectory = process.cwd();
|
180 | process.chdir(root);
|
181 |
|
182 | createWpTheme(root, appName, version, verbose, originalDirectory, template, useYarn, usePnp, useTypescript);
|
183 | }
|
184 |
|
185 | function shouldUseYarn() {
|
186 | try {
|
187 | execSync("yarnpkg --version", { stdio: "ignore" });
|
188 | return true;
|
189 | } catch (e) {
|
190 | return false;
|
191 | }
|
192 | }
|
193 |
|
194 | function createWpTheme(root, appName, version, verbose, originalDirectory, template, useYarn, usePnp, useTypescript) {
|
195 | const packageToInstall = "create-react-app";
|
196 |
|
197 | return Promise.resolve(packageToInstall)
|
198 | .then((packageName) =>
|
199 | checkIfOnline(useYarn).then((isOnline) => ({
|
200 | isOnline: isOnline,
|
201 | packageName: packageName
|
202 | }))
|
203 | )
|
204 | .then((info) => {
|
205 | if (!info.isOnline) {
|
206 | abortCommand(chalk.yellow("You appear to be offline."));
|
207 | }
|
208 |
|
209 | let createWpThemeReactRoot = "react-src";
|
210 | createReactApp(createWpThemeReactRoot, appName, version, verbose, originalDirectory, template, useYarn, usePnp, useTypescript);
|
211 | })
|
212 | .catch((reason) => {
|
213 | console.log();
|
214 | console.log("Aborting installation.");
|
215 |
|
216 | if (reason.command) {
|
217 | console.log(` ${chalk.cyan(reason.command)} has failed.`);
|
218 | } else {
|
219 | console.log(chalk.red("Unexpected error."), reason);
|
220 | console.log("Please report it as a bug here:");
|
221 | console.log("https://github.com/devloco/create-react-wptheme/issues");
|
222 | }
|
223 |
|
224 | console.log();
|
225 | console.log("Done.");
|
226 | process.exit(1);
|
227 | });
|
228 | }
|
229 |
|
230 | function createReactApp(createWpThemeReactRoot, appName, version, verbose, originalDirectory, template, useYarn, usePnp, useTypescript) {
|
231 | return new Promise((resolve, reject) => {
|
232 | let command = "npx";
|
233 |
|
234 | let args = [];
|
235 | args.push(`create-react-app@${_createReactAppVersion}`);
|
236 | args.push(createWpThemeReactRoot);
|
237 |
|
238 | if (verbose) {
|
239 | args.push("--verbose");
|
240 | }
|
241 |
|
242 | if (!useYarn) {
|
243 | args.push("--use-npm");
|
244 | }
|
245 |
|
246 | if (usePnp) {
|
247 | args.push("--use-pnp");
|
248 | }
|
249 |
|
250 | if (useTypescript) {
|
251 | args.push("--typescript");
|
252 | }
|
253 |
|
254 | let scriptsPath = _getScriptsPath();
|
255 | args.push("--scripts-version");
|
256 | args.push(scriptsPath.path);
|
257 |
|
258 | const child = spawn(command, args, { stdio: "inherit" })
|
259 | .on("error", function(err) {
|
260 | console.log(`createReactWpTheme.js ERROR for command: ${command} ${args.join(" ")}`);
|
261 | throw err;
|
262 | })
|
263 | .on("close", (code) => {
|
264 | if (code !== 0) {
|
265 | reject({
|
266 | command: `${command} ${args.join(" ")}`
|
267 | });
|
268 |
|
269 | return;
|
270 | }
|
271 |
|
272 | scriptsPath.callback();
|
273 | resolve();
|
274 | });
|
275 | }).catch((code) => {
|
276 | reject(code);
|
277 | });
|
278 | }
|
279 |
|
280 | function checkAppName(appName) {
|
281 | const validationResult = validateProjectName(appName);
|
282 | if (!validationResult.validForNewPackages) {
|
283 | console.error(`Could not create a project called ${chalk.red(`"${appName}"`)} because of npm naming restrictions:`);
|
284 |
|
285 | printValidationResults(validationResult.errors);
|
286 | printValidationResults(validationResult.warnings);
|
287 | process.exit(1);
|
288 | }
|
289 |
|
290 |
|
291 | const dependencies = ["react", "react-dom", "react-scripts", "@devloco/react-scripts-wptheme"].sort();
|
292 | if (dependencies.indexOf(appName) >= 0) {
|
293 | console.error(
|
294 | chalk.red(`We cannot create a project called ${chalk.green(appName)} because a dependency with the same name exists.\n` + `Due to the way npm works, the following names are not allowed:\n\n`) +
|
295 | chalk.cyan(dependencies.map((depName) => ` ${depName}`).join("\n")) +
|
296 | chalk.red("\n\nPlease choose a different project name.")
|
297 | );
|
298 | process.exit(1);
|
299 | }
|
300 | }
|
301 |
|
302 | function getProxy() {
|
303 | if (process.env.https_proxy) {
|
304 | return process.env.https_proxy;
|
305 | } else {
|
306 | try {
|
307 |
|
308 | let httpsProxy = execSync("npm config get https-proxy")
|
309 | .toString()
|
310 | .trim();
|
311 | return httpsProxy !== "null" ? httpsProxy : undefined;
|
312 | } catch (e) {
|
313 | return;
|
314 | }
|
315 | }
|
316 | }
|
317 |
|
318 | function checkIfOnline(useYarn) {
|
319 | if (!useYarn) {
|
320 |
|
321 |
|
322 | return Promise.resolve(true);
|
323 | }
|
324 |
|
325 | return new Promise((resolve) => {
|
326 | dns.lookup("registry.yarnpkg.com", (err) => {
|
327 | let proxy;
|
328 | if (err != null && (proxy = getProxy())) {
|
329 |
|
330 |
|
331 | dns.lookup(url.parse(proxy).hostname, (proxyErr) => {
|
332 | resolve(proxyErr == null);
|
333 | });
|
334 | } else {
|
335 | resolve(err == null);
|
336 | }
|
337 | });
|
338 | });
|
339 | }
|