UNPKG

12.1 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.3.0-wp.11";
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", "set your theme to use 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();
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(`Using Create React App ${chalk.green(_createReactAppVersion)} to scaffold the theme's source code...`);
176 console.log();
177
178 let useYarn = useNpm ? false : shouldUseYarn();
179
180 const originalDirectory = process.cwd();
181 process.chdir(root); // change into the newly created folder, then run create-react-app.
182
183 createWpTheme(root, appName, version, verbose, originalDirectory, template, useYarn, usePnp, useTypescript);
184}
185
186function shouldUseYarn() {
187 try {
188 execSync("yarnpkg --version", { stdio: "ignore" });
189 return true;
190 } catch (e) {
191 return false;
192 }
193}
194
195function createWpTheme(root, appName, version, verbose, originalDirectory, template, useYarn, usePnp, useTypescript) {
196 const packageToInstall = "create-react-app";
197
198 if (useTypescript === true) {
199 template = "wptheme-typescript";
200 }
201
202 if (typeof template !== "string" || template.trim().length === 0) {
203 template = "wptheme";
204 }
205
206 return Promise.resolve(packageToInstall)
207 .then((packageName) =>
208 checkIfOnline(useYarn).then((isOnline) => ({
209 isOnline: isOnline,
210 packageName: packageName
211 }))
212 )
213 .then((info) => {
214 if (!info.isOnline) {
215 abortCommand(chalk.yellow("You appear to be offline."));
216 }
217
218 let createWpThemeReactRoot = "react-src";
219 createReactApp(createWpThemeReactRoot, appName, version, verbose, originalDirectory, template, useYarn, usePnp);
220 })
221 .catch((reason) => {
222 console.log();
223 console.log("Aborting installation.");
224
225 if (reason.command) {
226 console.log(` ${chalk.cyan(reason.command)} has failed.`);
227 } else {
228 console.log(chalk.red("Unexpected error."), reason);
229 console.log("Please report it as a bug here:");
230 console.log("https://github.com/devloco/create-react-wptheme/issues");
231 }
232
233 console.log();
234 console.log("Done.");
235 process.exit(1);
236 });
237}
238
239function createReactApp(createWpThemeReactRoot, appName, version, verbose, originalDirectory, template, useYarn, usePnp) {
240 return new Promise((resolve, reject) => {
241 let command = "npx";
242
243 let args = [];
244 args.push(`create-react-app@${_createReactAppVersion}`);
245 args.push(createWpThemeReactRoot);
246
247 if (verbose) {
248 args.push("--verbose");
249 }
250
251 if (!useYarn) {
252 args.push("--use-npm");
253 }
254
255 if (usePnp) {
256 args.push("--use-pnp");
257 }
258
259 args.push("--template");
260 args.push(template);
261
262 let scriptsPath = _getScriptsPath();
263 args.push("--scripts-version");
264 args.push(scriptsPath.path);
265
266 const child = spawn(command, args, { stdio: "inherit" })
267 .on("error", function(err) {
268 console.log(`createReactWpTheme.js ERROR for command: ${command} ${args.join(" ")}`);
269 throw err;
270 })
271 .on("close", (code) => {
272 if (code !== 0) {
273 reject({
274 command: `${command} ${args.join(" ")}`
275 });
276
277 return;
278 }
279
280 scriptsPath.callback();
281 resolve();
282 });
283 }).catch((code) => {
284 reject(code);
285 });
286}
287
288function checkAppName(appName) {
289 const validationResult = validateProjectName(appName);
290 if (!validationResult.validForNewPackages) {
291 console.error(`Could not create a project called ${chalk.red(`"${appName}"`)} because of npm naming restrictions:`);
292
293 printValidationResults(validationResult.errors);
294 printValidationResults(validationResult.warnings);
295 process.exit(1);
296 }
297
298 // TODO: there should be a single place that holds the dependencies
299 const dependencies = ["react", "react-dom", "react-scripts", "@devloco/react-scripts-wptheme"].sort();
300 if (dependencies.indexOf(appName) >= 0) {
301 console.error(
302 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`) +
303 chalk.cyan(dependencies.map((depName) => ` ${depName}`).join("\n")) +
304 chalk.red("\n\nPlease choose a different project name.")
305 );
306 process.exit(1);
307 }
308}
309
310function getProxy() {
311 if (process.env.https_proxy) {
312 return process.env.https_proxy;
313 } else {
314 try {
315 // Trying to read https-proxy from .npmrc
316 let httpsProxy = execSync("npm config get https-proxy")
317 .toString()
318 .trim();
319 return httpsProxy !== "null" ? httpsProxy : undefined;
320 } catch (e) {
321 return;
322 }
323 }
324}
325
326function checkIfOnline(useYarn) {
327 if (!useYarn) {
328 // Don't ping the Yarn registry.
329 // We'll just assume the best case.
330 return Promise.resolve(true);
331 }
332
333 return new Promise((resolve) => {
334 dns.lookup("registry.yarnpkg.com", (err) => {
335 let proxy;
336 if (err != null && (proxy = getProxy())) {
337 // If a proxy is defined, we likely can't resolve external hostnames.
338 // Try to resolve the proxy name as an indication of a connection.
339 dns.lookup(url.parse(proxy).hostname, (proxyErr) => {
340 resolve(proxyErr == null);
341 });
342 } else {
343 resolve(err == null);
344 }
345 });
346 });
347}