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.3.0-wp.11";
|
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", "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 |
|
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();
|
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(`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);
|
182 |
|
183 | createWpTheme(root, appName, version, verbose, originalDirectory, template, useYarn, usePnp, useTypescript);
|
184 | }
|
185 |
|
186 | function shouldUseYarn() {
|
187 | try {
|
188 | execSync("yarnpkg --version", { stdio: "ignore" });
|
189 | return true;
|
190 | } catch (e) {
|
191 | return false;
|
192 | }
|
193 | }
|
194 |
|
195 | function 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 |
|
239 | function 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 |
|
288 | function 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 |
|
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 |
|
310 | function getProxy() {
|
311 | if (process.env.https_proxy) {
|
312 | return process.env.https_proxy;
|
313 | } else {
|
314 | try {
|
315 |
|
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 |
|
326 | function checkIfOnline(useYarn) {
|
327 | if (!useYarn) {
|
328 |
|
329 |
|
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 |
|
338 |
|
339 | dns.lookup(url.parse(proxy).hostname, (proxyErr) => {
|
340 | resolve(proxyErr == null);
|
341 | });
|
342 | } else {
|
343 | resolve(err == null);
|
344 | }
|
345 | });
|
346 | });
|
347 | }
|