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