Skip to content

Commit 252afb5

Browse files
authored
feat: make init command standalone (#1167)
1 parent bbc610e commit 252afb5

File tree

4 files changed

+170
-17
lines changed

4 files changed

+170
-17
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"main": "scripts/configure.js",
4444
"bin": {
4545
"configure-test-app": "scripts/configure.js",
46+
"init": "scripts/init.js",
4647
"init-test-app": "scripts/init.js",
4748
"install-windows-test-app": "windows/test-app.js"
4849
},

scripts/configure.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const semver = require("semver");
3434
* @typedef {{
3535
* name: string;
3636
* packagePath: string;
37+
* templatePath?: string;
3738
* testAppPath: string;
3839
* targetVersion: string;
3940
* platforms: Platform[];
@@ -407,13 +408,15 @@ const getConfig = (() => {
407408
typeof configuration === "undefined" ||
408409
"JEST_WORKER_ID" in process.env // skip caching when testing
409410
) {
410-
const { name, testAppPath, flatten, init } = params;
411+
const { name, templatePath, testAppPath, flatten, init } = params;
411412
const projectPathFlag = flatten ? " --project-path ." : "";
412413
const testAppRelPath = projectRelativePath(params);
413-
const templateDir = path.relative(
414-
process.cwd(),
415-
path.dirname(require.resolve("react-native/template/package.json"))
416-
);
414+
const templateDir =
415+
templatePath ||
416+
path.relative(
417+
process.cwd(),
418+
path.dirname(require.resolve("react-native/template/package.json"))
419+
);
417420
configuration = {
418421
common: {
419422
files: {

scripts/init.js

Lines changed: 160 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,155 @@
22
// @ts-check
33
"use strict";
44

5-
(async () => {
6-
const { version: targetVersion } = require("react-native/package.json");
7-
const { configure } = require("./configure");
5+
const { spawnSync } = require("child_process");
6+
const path = require("path");
7+
8+
/**
9+
* @template T
10+
* @param {() => T | null} fn
11+
* @returns {() => T | null}
12+
*/
13+
function memo(fn) {
14+
/** @type {T | null} */
15+
let result;
16+
return () => {
17+
if (result === undefined) {
18+
result = fn();
19+
}
20+
return result;
21+
};
22+
}
23+
24+
/**
25+
* Invokes `npm`.
26+
* @param {...string} args
27+
*/
28+
function npm(...args) {
29+
const { error, stderr, stdout } = spawnSync("npm", args, {
30+
encoding: "utf-8",
31+
});
32+
if (!stdout) {
33+
if (stderr) {
34+
console.error(stderr);
35+
}
36+
throw error;
37+
}
38+
return stdout.trim();
39+
}
40+
41+
/**
42+
* Invokes `tar xf`.
43+
* @param {string} archive
44+
*/
45+
function untar(archive) {
46+
return spawnSync("tar", ["xf", archive], { cwd: path.dirname(archive) });
47+
}
48+
49+
/**
50+
* Returns the installed `react-native` manifest, if present.
51+
* @returns {string | null}
52+
*/
53+
const getInstalledReactNativeManifest = memo(() => {
54+
try {
55+
const options = { paths: [process.cwd()] };
56+
return require.resolve("react-native/package.json", options);
57+
} catch (_) {
58+
return null;
59+
}
60+
});
61+
62+
/**
63+
* Returns the installed `react-native` version, if present.
64+
* @returns {string | null}
65+
*/
66+
const getInstalledVersion = memo(() => {
67+
const manifestPath = getInstalledReactNativeManifest();
68+
if (manifestPath) {
69+
const { version } = require(manifestPath);
70+
return version;
71+
}
72+
73+
return null;
74+
});
75+
76+
/**
77+
* Returns the desired `react-native` version.
78+
*
79+
* Checks the following in order:
80+
*
81+
* - Command line flag, e.g. `--version 0.70`
82+
* - Currently installed `react-native` version
83+
* - Latest version from npm
84+
*
85+
* @returns {string}
86+
*/
87+
function getVersion() {
88+
const index = process.argv.lastIndexOf("--version");
89+
if (index >= 0) {
90+
return process.argv[index + 1];
91+
}
92+
93+
const version = getInstalledVersion();
94+
if (version) {
95+
return version;
96+
}
97+
98+
return npm("view", "react-native", "version");
99+
}
100+
101+
/**
102+
* Returns the React Native version and path to the template.
103+
* @returns {Promise<[string] | [string, string]>}
104+
*/
105+
function getTemplate() {
106+
return new Promise((resolve, reject) => {
107+
const version = getVersion();
108+
if (getInstalledVersion() === version) {
109+
const rnManifest = getInstalledReactNativeManifest();
110+
if (rnManifest) {
111+
resolve([version, path.join(path.dirname(rnManifest), "template")]);
112+
return;
113+
}
114+
}
115+
116+
// `npm view` may return an array if there are multiple versions matching
117+
// `version`. If there is only one match, the return type is a string.
118+
const tarballs = JSON.parse(
119+
npm("view", "--json", `react-native@${version}`, "dist.tarball")
120+
);
121+
const url = Array.isArray(tarballs)
122+
? tarballs[tarballs.length - 1]
123+
: tarballs;
124+
125+
console.log(`Downloading ${path.basename(url)}...`);
126+
127+
require("https")
128+
.get(url, (res) => {
129+
const fs = require("fs");
130+
const os = require("os");
131+
132+
const tmpDir = path.join(os.tmpdir(), "react-native-test-app");
133+
fs.mkdirSync(tmpDir, { recursive: true });
8134

135+
const dest = path.join(tmpDir, path.basename(url));
136+
const file = fs.createWriteStream(dest);
137+
res.pipe(file);
138+
file.on("finish", () => {
139+
file.close();
140+
141+
untar(dest);
142+
143+
const template = path.join(tmpDir, "package", "template");
144+
resolve([version, template]);
145+
});
146+
})
147+
.on("error", (err) => {
148+
reject(err);
149+
});
150+
});
151+
}
152+
153+
async function main() {
9154
/**
10155
* @type {{
11156
* name?: string;
@@ -17,8 +162,8 @@
17162
{
18163
type: "text",
19164
name: "name",
20-
message: "What is the name of your test app?",
21-
initial: "TestApp",
165+
message: "What is the name of your app?",
166+
initial: "Example",
22167
validate: Boolean,
23168
},
24169
{
@@ -43,22 +188,25 @@
43188
]);
44189

45190
if (!name || !packagePath || !platforms) {
46-
process.exit(1);
191+
return 1;
47192
}
48193

49-
const path = require("path");
194+
const { configure } = require("./configure");
50195

51-
const result = configure({
196+
const [targetVersion, templatePath] = await getTemplate();
197+
return configure({
52198
name,
53199
packagePath,
200+
templatePath,
54201
testAppPath: path.resolve(__dirname, ".."),
55202
targetVersion,
56203
platforms,
57204
flatten: true,
58205
force: true,
59206
init: true,
60207
});
61-
if (result !== 0) {
62-
process.exit(result);
63-
}
64-
})();
208+
}
209+
210+
main().then((result) => {
211+
process.exitCode = result;
212+
});

yarn.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10942,6 +10942,7 @@ fsevents@^2.3.2:
1094210942
optional: true
1094310943
bin:
1094410944
configure-test-app: scripts/configure.js
10945+
init: scripts/init.js
1094510946
init-test-app: scripts/init.js
1094610947
install-windows-test-app: windows/test-app.js
1094710948
languageName: unknown

0 commit comments

Comments
 (0)