UNPKG

11.9 kBJavaScriptView Raw
1/**
2 * Copyright (c) 2019-present, https://github.com/devloco
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
9// /!\ DO NOT MODIFY THIS FILE /!\
10// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
11//
12// create-react-wptheme is installed globally on people's computers. This means
13// that it is extremely difficult to have them upgrade the version and
14// because there's only one global version installed, it is very prone to
15// breaking changes.
16//
17// The only job of create-react-wptheme is to init the repository and then
18// forward all the commands to the local version of create-react-wptheme.
19//
20// If you need to add a new command, please add it to the scripts/ folder.
21//
22// The only reason to modify this file is to add more warnings and
23// troubleshooting information for the `create-react-wptheme` command.
24//
25// Do not make breaking changes! We absolutely don't want to have to
26// tell people to update their global version of create-react-wptheme.
27//
28// Also be careful with new language features.
29// This file must work on Node 6+.
30//
31// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
32// /!\ DO NOT MODIFY THIS FILE /!\
33// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
34
35"use strict";
36
37const validateProjectName = require("validate-npm-package-name");
38const chalk = require("chalk");
39const commander = require("commander");
40const fs = require("fs-extra");
41const path = require("path");
42const execSync = require("child_process").execSync;
43const spawn = require("cross-spawn");
44const dns = require("dns");
45const url = require("url");
46const envinfo = require("envinfo");
47
48const packageJson = require("./package.json");
49const _wpThemeVersion = packageJson.version;
50const _createReactAppVersion = _wpThemeVersion.split("-wp.")[0];
51
52// Check these!!!!
53const _reactScriptsWpThemeVersion = "3.2.0-wp.2";
54const _getScriptsPath = function() {
55 return scriptsFromNpm();
56};
57
58const scriptsFromNpm = function() {
59 //console.log("SCRIPTS FROM NPM");
60 return {
61 path: `@devloco/react-scripts-wptheme@${_reactScriptsWpThemeVersion}`,
62 callback: function() {}
63 };
64};
65
66const 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 // recurse
74 deleteFolderRecursive(curPath);
75 } else {
76 // delete file
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
101let projectName;
102const 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
124if (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
143if (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
154function printValidationResults(results) {
155 if (typeof results !== "undefined") {
156 results.forEach((error) => {
157 console.error(chalk.red(` * ${error}`));
158 });
159 }
160}
161
162console.log(program.name() + " version: " + chalk.magenta(_wpThemeVersion));
163console.log("@devloco/react-scripts-wptheme version: " + chalk.magenta(_reactScriptsWpThemeVersion));
164console.log("create-react-app version: " + chalk.magenta(_createReactAppVersion));
165createApp(projectName, program.verbose, program.scriptsVersion, program.useNpm, program.usePnp, program.typescript);
166
167function 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); // change into the newly created folder, then run create-react-app.
181
182 createWpTheme(root, appName, version, verbose, originalDirectory, template, useYarn, usePnp, useTypescript);
183}
184
185function shouldUseYarn() {
186 try {
187 execSync("yarnpkg --version", { stdio: "ignore" });
188 return true;
189 } catch (e) {
190 return false;
191 }
192}
193
194function 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
230function 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
280function 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 // TODO: there should be a single place that holds the dependencies
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
302function getProxy() {
303 if (process.env.https_proxy) {
304 return process.env.https_proxy;
305 } else {
306 try {
307 // Trying to read https-proxy from .npmrc
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
318function checkIfOnline(useYarn) {
319 if (!useYarn) {
320 // Don't ping the Yarn registry.
321 // We'll just assume the best case.
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 // If a proxy is defined, we likely can't resolve external hostnames.
330 // Try to resolve the proxy name as an indication of a connection.
331 dns.lookup(url.parse(proxy).hostname, (proxyErr) => {
332 resolve(proxyErr == null);
333 });
334 } else {
335 resolve(err == null);
336 }
337 });
338 });
339}