stim 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -10
- package/dist/android-BW8i_YxU.mjs +204 -0
- package/dist/android-Bcmev2s2.mjs +2334 -0
- package/dist/android-K63_0UfF.mjs +727 -0
- package/dist/android-cas-D6HPZ6UQ.mjs +1078 -0
- package/dist/android-cas-compiler.mjs +2 -2
- package/dist/app-install-NFxUahnH.mjs +1149 -0
- package/dist/build-lock-BsVoLDaq.mjs +645 -0
- package/dist/build-slots-D0b6sJyo.mjs +152 -0
- package/dist/{cache-manifest-4tH64LQ9.mjs → cache-manifest-Cu4_Znw9.mjs} +2 -2
- package/dist/cache-manifest.mjs +1 -1
- package/dist/cli.mjs +24 -27166
- package/dist/collector-run.d.mts +1 -0
- package/dist/collector-run.mjs +7 -5
- package/dist/command-output-8wHMEde0.mjs +289 -0
- package/dist/{config-7kmtuhO2.mjs → config-CMtIaTk9.mjs} +44 -13
- package/dist/deps-BLBsTNDS.mjs +435 -0
- package/dist/dev-client-RpvXxCXV.mjs +620 -0
- package/dist/device-CoCPmazS.mjs +250 -0
- package/dist/device-lease-Dpik41RY.mjs +335 -0
- package/dist/device-pool-B5pEVa3t.mjs +408 -0
- package/dist/device-remote-E81KAdXf.mjs +1209 -0
- package/dist/doctor-CqPeMsSm.mjs +1130 -0
- package/dist/doctor-D_CX7ib5.mjs +530 -0
- package/dist/error-diagnostics-Dw8Gn3yw.mjs +636 -0
- package/dist/{exec-bsN9MJXb.mjs → exec-CyylIdq9.mjs} +4 -2
- package/dist/gc-B7KkDinF.mjs +1665 -0
- package/dist/guide-BS2Xc24o.mjs +4503 -0
- package/dist/ios-B7KLgIp5.mjs +2733 -0
- package/dist/ios-D1h1Di39.mjs +451 -0
- package/dist/ios-device-D3pzTKQE.mjs +81 -0
- package/dist/ios-device-xNt0Lg6v.mjs +413 -0
- package/dist/logs-DcX9AvRF.mjs +176 -0
- package/dist/logs-query-oqn47Ffx.mjs +273 -0
- package/dist/metro-COslRK_F.mjs +184 -0
- package/dist/metro-store-CDDZHtPl.mjs +92 -0
- package/dist/ownership-CGPart6T.mjs +60 -0
- package/dist/ownership-CYOyqxUo.mjs +475 -0
- package/dist/ownership-claim-7zRxxTfZ.mjs +569 -0
- package/dist/{project-Dit1CckW.mjs → project-BBTVABUz.mjs} +12 -3
- package/dist/reclaim-C5Jmuahx.mjs +348 -0
- package/dist/reload-Z8nKifAk.mjs +332 -0
- package/dist/remote-cache-DDmJ1Roq.mjs +994 -0
- package/dist/{server-bare-BmTVVmBB.mjs → server-bare-DEk37Mz2.mjs} +8 -2
- package/dist/{server-expo-CHJe1r6j.mjs → server-expo-C3ktAwUG.mjs} +6 -5
- package/dist/server-expo-Drd7ksHu.mjs +2 -0
- package/dist/settings-DwU1_U_q.mjs +708 -0
- package/dist/spawn-entry-nCE6m770.mjs +14 -0
- package/dist/start-BrY4EuIg.mjs +822 -0
- package/dist/{state-2nush3MK.mjs → state-CZ-ER-8d.mjs} +21 -22
- package/dist/stats-BCWEVLrM.mjs +307 -0
- package/dist/stats-BsF15Bti.mjs +62 -0
- package/dist/status-DFI1Hfpg.mjs +393 -0
- package/dist/stop-B7DR4awK.mjs +3 -0
- package/dist/stop-Btg770vV.mjs +591 -0
- package/dist/supervisor-run.d.mts +0 -1
- package/dist/supervisor-run.mjs +4 -4
- package/dist/workspace-process-lock-BKfdys1q.mjs +47 -0
- package/dist/worktree-BPTbI-LN.mjs +1322 -0
- package/dist/worktree-C79xx_kM.mjs +543 -0
- package/dist/xcode-DeKCribl.mjs +1832 -0
- package/package.json +5 -5
- package/shim/bundle-response.cjs +5 -0
- package/dist/android-jc8MnUyL.mjs +0 -514
- package/dist/metro-store-COLl1pOk.mjs +0 -1249
- package/dist/server-expo-BZJm--wA.mjs +0 -2
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
import { t as getExecutor } from "./exec-CyylIdq9.mjs";
|
|
2
|
+
import { y as ensureWorkspaceStorage } from "./state-CZ-ER-8d.mjs";
|
|
3
|
+
import { C as removeTemporaryEntry, S as makeTemporaryDirectory } from "./worktree-C79xx_kM.mjs";
|
|
4
|
+
import { i as detectIsExpo, o as isPackageResolvable } from "./project-BBTVABUz.mjs";
|
|
5
|
+
import { n as stripAnsi, r as waitForChild, t as createLineReader } from "./process-output-C7IlIzd7.mjs";
|
|
6
|
+
import { t as NOT_OURS_FOREIGN_CWD } from "./metro-COslRK_F.mjs";
|
|
7
|
+
import { i as LAUNCH_BUNDLING, s as LAUNCH_UNVERIFIED } from "./app-install-NFxUahnH.mjs";
|
|
8
|
+
import { f as startBuildHeartbeat, p as tailLines, r as HEARTBEAT_INTERVAL_MS } from "./xcode-DeKCribl.mjs";
|
|
9
|
+
import { i as expoBinRefusal, r as expoBinPath, t as cleanLine } from "./server-expo-C3ktAwUG.mjs";
|
|
10
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync } from "node:fs";
|
|
11
|
+
import { basename, join } from "node:path";
|
|
12
|
+
import chalk from "chalk";
|
|
13
|
+
import { request } from "node:http";
|
|
14
|
+
//#region src/engine/metro-warmup.ts
|
|
15
|
+
const WARMUP_TIMEOUT_MS = 6e4;
|
|
16
|
+
async function requestLocal(url, platform, prefetch) {
|
|
17
|
+
return new Promise((resolve) => {
|
|
18
|
+
const req = request(url, { headers: {
|
|
19
|
+
accept: "application/json",
|
|
20
|
+
"expo-platform": platform,
|
|
21
|
+
...prefetch ? { "x-stim-metro-warmup": "1" } : {}
|
|
22
|
+
} });
|
|
23
|
+
const timer = setTimeout(() => req.destroy(), WARMUP_TIMEOUT_MS);
|
|
24
|
+
timer.unref();
|
|
25
|
+
req.on("socket", (socket) => socket.unref());
|
|
26
|
+
req.on("error", () => resolve(null));
|
|
27
|
+
req.on("close", () => {
|
|
28
|
+
clearTimeout(timer);
|
|
29
|
+
resolve(null);
|
|
30
|
+
});
|
|
31
|
+
req.on("response", (res) => {
|
|
32
|
+
let body = "";
|
|
33
|
+
res.setEncoding("utf8");
|
|
34
|
+
res.on("data", (chunk) => {
|
|
35
|
+
if (!prefetch) body += chunk;
|
|
36
|
+
});
|
|
37
|
+
res.on("error", () => resolve(null));
|
|
38
|
+
res.on("end", () => resolve(res.statusCode === 200 ? body : null));
|
|
39
|
+
});
|
|
40
|
+
req.end();
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
async function warmMetro({ port, platform, isExpo, appId, bundleUrl }) {
|
|
44
|
+
try {
|
|
45
|
+
const origin = `http://127.0.0.1:${port}`;
|
|
46
|
+
if (await requestLocal(new URL("/_stim/metro-warmup", origin), platform, false) !== "ready") return;
|
|
47
|
+
const url = new URL("/index.bundle", origin);
|
|
48
|
+
url.search = new URLSearchParams({
|
|
49
|
+
platform,
|
|
50
|
+
dev: "true",
|
|
51
|
+
lazy: "true",
|
|
52
|
+
minify: "false",
|
|
53
|
+
...platform === "ios" ? { inlineSourceMap: "false" } : {},
|
|
54
|
+
modulesOnly: "false",
|
|
55
|
+
runModule: "true",
|
|
56
|
+
excludeSource: "true",
|
|
57
|
+
sourcePaths: "url-server",
|
|
58
|
+
...appId ? { app: appId } : {}
|
|
59
|
+
}).toString();
|
|
60
|
+
if (!bundleUrl && isExpo) {
|
|
61
|
+
const body = await requestLocal(new URL(origin), platform, false);
|
|
62
|
+
if (body === null) return;
|
|
63
|
+
const manifest = JSON.parse(body);
|
|
64
|
+
bundleUrl = manifest.launchAsset?.url ?? manifest.bundleUrl;
|
|
65
|
+
if (typeof bundleUrl !== "string") return;
|
|
66
|
+
}
|
|
67
|
+
if (bundleUrl) {
|
|
68
|
+
const bundle = new URL(bundleUrl, origin);
|
|
69
|
+
url.pathname = bundle.pathname;
|
|
70
|
+
url.search = bundle.search;
|
|
71
|
+
}
|
|
72
|
+
await requestLocal(url, platform, true);
|
|
73
|
+
} catch {}
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region src/commands/native-runtime.ts
|
|
77
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
78
|
+
const GATE_RETRY_DELAYS_MS = [
|
|
79
|
+
3e3,
|
|
80
|
+
7e3,
|
|
81
|
+
1e4
|
|
82
|
+
];
|
|
83
|
+
function gateShouldRetry(resolution) {
|
|
84
|
+
if (resolution?.metro) return false;
|
|
85
|
+
return resolution?.kind !== NOT_OURS_FOREIGN_CWD;
|
|
86
|
+
}
|
|
87
|
+
async function resolveMetroWithRetry(resolve, port, root, { delays = GATE_RETRY_DELAYS_MS, sleep: wait = sleep, onRetry = (_info) => {} } = {}) {
|
|
88
|
+
let resolution = await resolve(port, root);
|
|
89
|
+
for (let i = 0; i < delays.length && gateShouldRetry(resolution); i++) {
|
|
90
|
+
const delayMs = delays[i];
|
|
91
|
+
if (delayMs === void 0) break;
|
|
92
|
+
onRetry({
|
|
93
|
+
attempt: i + 1,
|
|
94
|
+
delayMs,
|
|
95
|
+
resolution
|
|
96
|
+
});
|
|
97
|
+
await wait(delayMs);
|
|
98
|
+
resolution = await resolve(port, root);
|
|
99
|
+
}
|
|
100
|
+
return resolution;
|
|
101
|
+
}
|
|
102
|
+
function noMetroMessage({ port, resolution, supervisor, supervisorAlive }) {
|
|
103
|
+
const foreign = resolution?.notOurs;
|
|
104
|
+
if (supervisor && supervisor.port === port && supervisorAlive) {
|
|
105
|
+
const mode = supervisor.mode ? `${supervisor.mode} ` : "";
|
|
106
|
+
return `A supervisor record exists for port ${port} (pid ${supervisor.pid}, ${mode}dev server) but it did not verify as this workspace's Metro${foreign ? `: ${foreign}` : " -- nothing answered /status"}. Metro may still be indexing this project (a monorepo file-map crawl blocks its event loop for ~20s after the port opens).`;
|
|
107
|
+
}
|
|
108
|
+
if (foreign) return `Port ${port} is in use but is NOT this workspace's dev server: ${foreign}.`;
|
|
109
|
+
return `Nothing is serving this workspace's dev server on port ${port}.`;
|
|
110
|
+
}
|
|
111
|
+
function noMetroRemedy({ port, supervisor, supervisorAlive }) {
|
|
112
|
+
if (supervisor && supervisor.port === port && supervisorAlive) return "Re-run `stim ios` in a few seconds, or give the dev server longer to verify with `stim start --wait <seconds>`.";
|
|
113
|
+
return "Run `stim start` first, or pass --no-metro-check.";
|
|
114
|
+
}
|
|
115
|
+
async function ensureWorkspaceStorageSafely(root, { note = (_line) => {} } = {}) {
|
|
116
|
+
try {
|
|
117
|
+
return ensureWorkspaceStorage(root);
|
|
118
|
+
} catch (err) {
|
|
119
|
+
note(chalk.dim(`Could not prepare this workspace's Stim state: ${err?.message || err}`));
|
|
120
|
+
throw err;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function launchOutcomeRecord({ launchState, release, bundleId, configuration, metroPort }) {
|
|
124
|
+
const unverified = launchState === LAUNCH_UNVERIFIED;
|
|
125
|
+
const bundling = launchState === LAUNCH_BUNDLING;
|
|
126
|
+
let msg;
|
|
127
|
+
if (release) msg = unverified ? `${bundleId} could not be verified as running after its ${configuration} launch` : `${bundleId} is running its embedded ${configuration} bundle`;
|
|
128
|
+
else if (unverified) msg = `no bundle request from ${bundleId} reached this workspace's Metro on port ${metroPort}`;
|
|
129
|
+
else if (bundling) msg = `${bundleId} requested a bundle from this workspace's Metro on port ${metroPort}; it was still being built when the launch check ended`;
|
|
130
|
+
else msg = `${bundleId} fetched a bundle from this workspace's Metro on port ${metroPort}`;
|
|
131
|
+
return {
|
|
132
|
+
src: "build",
|
|
133
|
+
level: unverified ? "warn" : "info",
|
|
134
|
+
event: unverified ? "launch_unverified" : bundling ? "launch_bundling" : "launch_verified",
|
|
135
|
+
msg
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
//#endregion
|
|
139
|
+
//#region src/engine/prebuild.ts
|
|
140
|
+
const PREBUILD_ERROR = "STIM_PREBUILD_FAILED";
|
|
141
|
+
const LAST_LINES$1 = 20;
|
|
142
|
+
function nativeDirName(platform) {
|
|
143
|
+
return platform === "android" ? "android" : "ios";
|
|
144
|
+
}
|
|
145
|
+
function nativeDir(root, platform) {
|
|
146
|
+
return join(root, nativeDirName(platform));
|
|
147
|
+
}
|
|
148
|
+
function shouldPrebuild({ isExpo, nativeDirExists }) {
|
|
149
|
+
return Boolean(isExpo) && !nativeDirExists;
|
|
150
|
+
}
|
|
151
|
+
function needsPrebuild(root, platform, isExpo) {
|
|
152
|
+
return shouldPrebuild({
|
|
153
|
+
isExpo,
|
|
154
|
+
nativeDirExists: existsSync(nativeDir(root, platform))
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
function prebuildRefusal({ isExpo, platform, nativeDirExists }) {
|
|
158
|
+
if (nativeDirExists || isExpo) return null;
|
|
159
|
+
const dir = nativeDirName(platform);
|
|
160
|
+
return {
|
|
161
|
+
code: PREBUILD_ERROR,
|
|
162
|
+
message: `This project has no ${dir}/ directory and is not an Expo (CNG) project, so Stim cannot generate one.`,
|
|
163
|
+
remedy: `Check out or generate the native project (\`npx @react-native-community/cli init\` produced one originally), or add \`expo\` to the project so \`expo prebuild\` can create ${dir}/.`
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function prebuildFailure(logWriter, result) {
|
|
167
|
+
logWriter?.write?.({
|
|
168
|
+
src: "build",
|
|
169
|
+
level: "error",
|
|
170
|
+
msg: result.reason || "Expo prebuild failed.",
|
|
171
|
+
event: "prebuild_failed"
|
|
172
|
+
});
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
async function runPrebuild(root, platform, logWriter, { spawnFn = null, now = Date.now, isExpo = null } = {}) {
|
|
176
|
+
const dirExists = existsSync(nativeDir(root, platform));
|
|
177
|
+
const refusal = prebuildRefusal({
|
|
178
|
+
isExpo: isExpo === null ? detectIsExpo(root) : isExpo,
|
|
179
|
+
platform,
|
|
180
|
+
nativeDirExists: dirExists
|
|
181
|
+
});
|
|
182
|
+
if (refusal) return prebuildFailure(logWriter, {
|
|
183
|
+
failed: true,
|
|
184
|
+
code: refusal.code,
|
|
185
|
+
reason: refusal.message,
|
|
186
|
+
remedy: refusal.remedy,
|
|
187
|
+
error: refusal,
|
|
188
|
+
lastLines: []
|
|
189
|
+
});
|
|
190
|
+
const bin = expoBinPath(root);
|
|
191
|
+
if (!bin) {
|
|
192
|
+
const binRefusal = expoBinRefusal(root, "prebuild");
|
|
193
|
+
return prebuildFailure(logWriter, {
|
|
194
|
+
failed: true,
|
|
195
|
+
code: PREBUILD_ERROR,
|
|
196
|
+
reason: binRefusal.message,
|
|
197
|
+
remedy: binRefusal.remedy,
|
|
198
|
+
lastLines: []
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
const spawn = spawnFn || ((cmd, args, opts) => getExecutor().spawn(cmd, args, opts));
|
|
202
|
+
const startedAt = now();
|
|
203
|
+
const tail = [];
|
|
204
|
+
const push = (line) => {
|
|
205
|
+
const msg = stripAnsi(String(line)).trimEnd();
|
|
206
|
+
if (!msg.trim()) return;
|
|
207
|
+
tail.push(msg);
|
|
208
|
+
if (tail.length > LAST_LINES$1) tail.shift();
|
|
209
|
+
logWriter?.write?.({
|
|
210
|
+
src: "build",
|
|
211
|
+
level: "debug",
|
|
212
|
+
msg,
|
|
213
|
+
raw: true,
|
|
214
|
+
event: "prebuild"
|
|
215
|
+
});
|
|
216
|
+
};
|
|
217
|
+
let child;
|
|
218
|
+
try {
|
|
219
|
+
child = spawn(bin, [
|
|
220
|
+
"prebuild",
|
|
221
|
+
"-p",
|
|
222
|
+
platform,
|
|
223
|
+
"--no-install"
|
|
224
|
+
], {
|
|
225
|
+
cwd: root,
|
|
226
|
+
stdio: [
|
|
227
|
+
"ignore",
|
|
228
|
+
"pipe",
|
|
229
|
+
"pipe"
|
|
230
|
+
],
|
|
231
|
+
env: {
|
|
232
|
+
...process.env,
|
|
233
|
+
FORCE_COLOR: "0",
|
|
234
|
+
CI: "1"
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
} catch (err) {
|
|
238
|
+
return prebuildFailure(logWriter, {
|
|
239
|
+
failed: true,
|
|
240
|
+
code: PREBUILD_ERROR,
|
|
241
|
+
reason: `Could not run \`expo prebuild\`: ${err?.message || err}`,
|
|
242
|
+
lastLines: []
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
const outReader = createLineReader(push);
|
|
246
|
+
const errReader = createLineReader(push);
|
|
247
|
+
child.stdout?.setEncoding?.("utf-8");
|
|
248
|
+
child.stderr?.setEncoding?.("utf-8");
|
|
249
|
+
child.stdout?.on("data", (chunk) => outReader.push(chunk));
|
|
250
|
+
child.stderr?.on("data", (chunk) => errReader.push(chunk));
|
|
251
|
+
const result = await waitForChild(child);
|
|
252
|
+
outReader.flush();
|
|
253
|
+
errReader.flush();
|
|
254
|
+
const durationMs = now() - startedAt;
|
|
255
|
+
if (result.error) return prebuildFailure(logWriter, {
|
|
256
|
+
failed: true,
|
|
257
|
+
code: PREBUILD_ERROR,
|
|
258
|
+
reason: `Could not run \`expo prebuild\`: ${result.error?.message || result.error}`,
|
|
259
|
+
lastLines: tail.slice(),
|
|
260
|
+
durationMs
|
|
261
|
+
});
|
|
262
|
+
if (result.code !== 0) {
|
|
263
|
+
const how = result.signal ? `signal ${result.signal}` : `exit code ${result.code}`;
|
|
264
|
+
return prebuildFailure(logWriter, {
|
|
265
|
+
failed: true,
|
|
266
|
+
code: PREBUILD_ERROR,
|
|
267
|
+
reason: `\`expo prebuild -p ${platform}\` failed (${how}).`,
|
|
268
|
+
lastLines: tail.slice(),
|
|
269
|
+
durationMs
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
if (!existsSync(nativeDir(root, platform))) return prebuildFailure(logWriter, {
|
|
273
|
+
failed: true,
|
|
274
|
+
code: PREBUILD_ERROR,
|
|
275
|
+
reason: `\`expo prebuild -p ${platform}\` succeeded but did not create ${nativeDirName(platform)}/.`,
|
|
276
|
+
lastLines: tail.slice(),
|
|
277
|
+
durationMs
|
|
278
|
+
});
|
|
279
|
+
return {
|
|
280
|
+
ok: true,
|
|
281
|
+
durationMs,
|
|
282
|
+
nativeDir: nativeDir(root, platform)
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
//#endregion
|
|
286
|
+
//#region src/engine/js-swap.ts
|
|
287
|
+
const JS_BUNDLE_NAME = "main.jsbundle";
|
|
288
|
+
const LAST_LINES = 5;
|
|
289
|
+
function hermesEnabledFromProperties(text) {
|
|
290
|
+
if (typeof text !== "string") return true;
|
|
291
|
+
let data;
|
|
292
|
+
try {
|
|
293
|
+
data = JSON.parse(text);
|
|
294
|
+
} catch {
|
|
295
|
+
return true;
|
|
296
|
+
}
|
|
297
|
+
if (!data || typeof data !== "object") return true;
|
|
298
|
+
const raw = data.hermesEnabled;
|
|
299
|
+
return raw !== "false" && raw !== false;
|
|
300
|
+
}
|
|
301
|
+
function readHermesEnabled(root) {
|
|
302
|
+
try {
|
|
303
|
+
return hermesEnabledFromProperties(readFileSync(join(root, "ios", "Podfile.properties.json"), "utf-8"));
|
|
304
|
+
} catch {
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
const ENTRY_CANDIDATES = [
|
|
309
|
+
"index.js",
|
|
310
|
+
"index.ts",
|
|
311
|
+
"index.tsx",
|
|
312
|
+
"index.jsx"
|
|
313
|
+
];
|
|
314
|
+
function pickEntryFile(entries) {
|
|
315
|
+
const names = new Set((Array.isArray(entries) ? entries : []).filter((e) => typeof e === "string"));
|
|
316
|
+
return ENTRY_CANDIDATES.find((c) => names.has(c)) ?? "index.js";
|
|
317
|
+
}
|
|
318
|
+
function detectEntryFile(root) {
|
|
319
|
+
let entries;
|
|
320
|
+
try {
|
|
321
|
+
entries = readdirSync(root);
|
|
322
|
+
} catch {
|
|
323
|
+
return "index.js";
|
|
324
|
+
}
|
|
325
|
+
return pickEntryFile(entries);
|
|
326
|
+
}
|
|
327
|
+
function bundleCommand({ isExpo, entryFile, bundleOutput, assetsDest }) {
|
|
328
|
+
if (isExpo) return {
|
|
329
|
+
file: "npx",
|
|
330
|
+
args: [
|
|
331
|
+
"expo",
|
|
332
|
+
"export:embed",
|
|
333
|
+
"--platform",
|
|
334
|
+
"ios",
|
|
335
|
+
"--dev",
|
|
336
|
+
"false",
|
|
337
|
+
"--bundle-output",
|
|
338
|
+
bundleOutput,
|
|
339
|
+
"--assets-dest",
|
|
340
|
+
assetsDest
|
|
341
|
+
]
|
|
342
|
+
};
|
|
343
|
+
return {
|
|
344
|
+
file: "npx",
|
|
345
|
+
args: [
|
|
346
|
+
"react-native",
|
|
347
|
+
"bundle",
|
|
348
|
+
"--platform",
|
|
349
|
+
"ios",
|
|
350
|
+
"--dev",
|
|
351
|
+
"false",
|
|
352
|
+
"--entry-file",
|
|
353
|
+
entryFile,
|
|
354
|
+
"--bundle-output",
|
|
355
|
+
bundleOutput,
|
|
356
|
+
"--assets-dest",
|
|
357
|
+
assetsDest
|
|
358
|
+
]
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
function hermescPath(root, { exists = existsSync } = {}) {
|
|
362
|
+
const candidates = [
|
|
363
|
+
join(root, "node_modules", "hermes-compiler", "hermesc", "osx-bin", "hermesc"),
|
|
364
|
+
join(root, "ios", "Pods", "hermes-engine", "destroot", "bin", "hermesc"),
|
|
365
|
+
join(root, "node_modules", "react-native", "sdks", "hermesc", "osx-bin", "hermesc")
|
|
366
|
+
];
|
|
367
|
+
return candidates.find(exists) ?? candidates[candidates.length - 1];
|
|
368
|
+
}
|
|
369
|
+
function hermescArgs({ bundle, out }) {
|
|
370
|
+
return [
|
|
371
|
+
"-emit-binary",
|
|
372
|
+
"-out",
|
|
373
|
+
out,
|
|
374
|
+
bundle
|
|
375
|
+
];
|
|
376
|
+
}
|
|
377
|
+
async function swapJsBundle({ root, isExpo, cachedAppPath, logWriter = null, exec = null, spawnFn = null, mkdtemp = () => makeTemporaryDirectory(cachedAppPath, "stim-js-swap-"), exists = existsSync, hermesEnabled = null, now = Date.now, heartbeatMs = HEARTBEAT_INTERVAL_MS, onHeartbeat = (line) => console.error(line) }) {
|
|
378
|
+
const e = exec || getExecutor();
|
|
379
|
+
const startedAt = now();
|
|
380
|
+
const elapsed = () => now() - startedAt;
|
|
381
|
+
let tmp;
|
|
382
|
+
const fail = (step, reason, lastLines = []) => {
|
|
383
|
+
logWriter?.write?.({
|
|
384
|
+
src: "build",
|
|
385
|
+
level: "error",
|
|
386
|
+
msg: `JS swap failed at ${step}: ${reason}`,
|
|
387
|
+
event: "js_swap"
|
|
388
|
+
});
|
|
389
|
+
if (tmp) removeTemporaryEntry(tmp);
|
|
390
|
+
return {
|
|
391
|
+
failed: true,
|
|
392
|
+
step,
|
|
393
|
+
reason,
|
|
394
|
+
lastLines,
|
|
395
|
+
durationMs: elapsed()
|
|
396
|
+
};
|
|
397
|
+
};
|
|
398
|
+
let appCopy;
|
|
399
|
+
try {
|
|
400
|
+
tmp = mkdtemp();
|
|
401
|
+
appCopy = join(tmp, basename(cachedAppPath));
|
|
402
|
+
try {
|
|
403
|
+
e.runFile("cp", [
|
|
404
|
+
"-c",
|
|
405
|
+
"-R",
|
|
406
|
+
cachedAppPath,
|
|
407
|
+
appCopy
|
|
408
|
+
]);
|
|
409
|
+
} catch {
|
|
410
|
+
e.runFile("cp", [
|
|
411
|
+
"-R",
|
|
412
|
+
cachedAppPath,
|
|
413
|
+
appCopy
|
|
414
|
+
]);
|
|
415
|
+
}
|
|
416
|
+
} catch (err) {
|
|
417
|
+
return fail("copy", `could not copy ${cachedAppPath} aside: ${describe(err)}`);
|
|
418
|
+
}
|
|
419
|
+
const bundleOutput = join(tmp, JS_BUNDLE_NAME);
|
|
420
|
+
const assetsDest = join(tmp, "assets");
|
|
421
|
+
const command = bundleCommand({
|
|
422
|
+
isExpo,
|
|
423
|
+
entryFile: isExpo ? "index.js" : detectEntryFile(root),
|
|
424
|
+
bundleOutput,
|
|
425
|
+
assetsDest
|
|
426
|
+
});
|
|
427
|
+
try {
|
|
428
|
+
mkdirSync(assetsDest, { recursive: true });
|
|
429
|
+
} catch (err) {
|
|
430
|
+
return fail("bundle", `could not create ${assetsDest}: ${describe(err)}`);
|
|
431
|
+
}
|
|
432
|
+
logWriter?.write?.({
|
|
433
|
+
src: "build",
|
|
434
|
+
level: "info",
|
|
435
|
+
msg: `${command.file} ${command.args.join(" ")}`,
|
|
436
|
+
event: "js_swap"
|
|
437
|
+
});
|
|
438
|
+
const spawn = spawnFn || ((cmd, args, opts) => e.spawn(cmd, args, opts));
|
|
439
|
+
const transcript = [];
|
|
440
|
+
const push = (line) => {
|
|
441
|
+
const msg = cleanLine(line);
|
|
442
|
+
if (msg.trim() === "") return;
|
|
443
|
+
transcript.push(msg);
|
|
444
|
+
logWriter?.write?.({
|
|
445
|
+
src: "build",
|
|
446
|
+
level: "debug",
|
|
447
|
+
msg,
|
|
448
|
+
raw: true,
|
|
449
|
+
event: "js_swap"
|
|
450
|
+
});
|
|
451
|
+
};
|
|
452
|
+
let child;
|
|
453
|
+
try {
|
|
454
|
+
child = spawn(command.file, command.args, {
|
|
455
|
+
cwd: root,
|
|
456
|
+
stdio: [
|
|
457
|
+
"ignore",
|
|
458
|
+
"pipe",
|
|
459
|
+
"pipe"
|
|
460
|
+
],
|
|
461
|
+
env: {
|
|
462
|
+
...process.env,
|
|
463
|
+
FORCE_COLOR: "0"
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
} catch (err) {
|
|
467
|
+
return fail("bundle", `could not run ${command.file} ${command.args[0]}: ${describe(err)}`);
|
|
468
|
+
}
|
|
469
|
+
const reader = {
|
|
470
|
+
out: createLineReader(push),
|
|
471
|
+
err: createLineReader(push)
|
|
472
|
+
};
|
|
473
|
+
child.stdout?.setEncoding?.("utf-8");
|
|
474
|
+
child.stderr?.setEncoding?.("utf-8");
|
|
475
|
+
child.stdout?.on("data", (chunk) => reader.out.push(chunk));
|
|
476
|
+
child.stderr?.on("data", (chunk) => reader.err.push(chunk));
|
|
477
|
+
const stopHeartbeat = startBuildHeartbeat({
|
|
478
|
+
intervalMs: heartbeatMs,
|
|
479
|
+
elapsed,
|
|
480
|
+
emit: onHeartbeat,
|
|
481
|
+
label: "swap"
|
|
482
|
+
});
|
|
483
|
+
let outcome;
|
|
484
|
+
try {
|
|
485
|
+
outcome = await waitForChild(child);
|
|
486
|
+
} finally {
|
|
487
|
+
stopHeartbeat();
|
|
488
|
+
}
|
|
489
|
+
reader.out.flush();
|
|
490
|
+
reader.err.flush();
|
|
491
|
+
if (outcome.error) return fail("bundle", `could not run ${command.file} ${command.args[0]}: ${describe(outcome.error)}`);
|
|
492
|
+
if (outcome.code !== 0) {
|
|
493
|
+
const how = outcome.signal ? `signal ${outcome.signal}` : `exit code ${outcome.code}`;
|
|
494
|
+
return fail("bundle", `\`${command.args.slice(0, 2).join(" ")}\` failed (${how})`, tailLines(transcript, LAST_LINES));
|
|
495
|
+
}
|
|
496
|
+
if (!exists(bundleOutput)) return fail("bundle", `the bundle command exited 0 but wrote no ${JS_BUNDLE_NAME} at ${bundleOutput}`);
|
|
497
|
+
let hermes = false;
|
|
498
|
+
let note;
|
|
499
|
+
if (hermesEnabled ?? readHermesEnabled(root)) {
|
|
500
|
+
const hermesc = hermescPath(root);
|
|
501
|
+
if (!exists(hermesc)) note = `hermesc not found at ${hermesc}; embedding the plain JS bundle instead of Hermes bytecode`;
|
|
502
|
+
else {
|
|
503
|
+
const hbc = join(tmp, `${JS_BUNDLE_NAME}.hbc`);
|
|
504
|
+
try {
|
|
505
|
+
e.runFile(hermesc, hermescArgs({
|
|
506
|
+
bundle: bundleOutput,
|
|
507
|
+
out: hbc
|
|
508
|
+
}));
|
|
509
|
+
e.runFile("mv", [hbc, bundleOutput]);
|
|
510
|
+
hermes = true;
|
|
511
|
+
} catch (err) {
|
|
512
|
+
return fail("hermesc", `hermesc failed on ${bundleOutput}: ${describe(err)}`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
try {
|
|
517
|
+
e.runFile("cp", [bundleOutput, join(appCopy, JS_BUNDLE_NAME)]);
|
|
518
|
+
e.runFile("cp", [
|
|
519
|
+
"-R",
|
|
520
|
+
`${assetsDest}/.`,
|
|
521
|
+
`${appCopy}/`
|
|
522
|
+
]);
|
|
523
|
+
} catch (err) {
|
|
524
|
+
return fail("replace", `could not replace the JS bundle inside ${appCopy}: ${describe(err)}`);
|
|
525
|
+
}
|
|
526
|
+
try {
|
|
527
|
+
e.runFile("codesign", [
|
|
528
|
+
"--force",
|
|
529
|
+
"--sign",
|
|
530
|
+
"-",
|
|
531
|
+
appCopy
|
|
532
|
+
]);
|
|
533
|
+
} catch (err) {
|
|
534
|
+
return fail("codesign", `codesign --force --sign - ${appCopy} failed: ${describe(err)}`);
|
|
535
|
+
}
|
|
536
|
+
logWriter?.write?.({
|
|
537
|
+
src: "build",
|
|
538
|
+
level: "info",
|
|
539
|
+
msg: `JS swap done: ${hermes ? "hermes bytecode" : "plain JS"} into ${appCopy} in ${elapsed()}ms`,
|
|
540
|
+
event: "js_swap"
|
|
541
|
+
});
|
|
542
|
+
const result = {
|
|
543
|
+
ok: true,
|
|
544
|
+
appPath: appCopy,
|
|
545
|
+
tmpDir: tmp,
|
|
546
|
+
hermes,
|
|
547
|
+
durationMs: elapsed()
|
|
548
|
+
};
|
|
549
|
+
if (note) result.note = note;
|
|
550
|
+
return result;
|
|
551
|
+
}
|
|
552
|
+
function describe(err) {
|
|
553
|
+
return String(err?.message || err);
|
|
554
|
+
}
|
|
555
|
+
//#endregion
|
|
556
|
+
//#region src/commands/dev-client.ts
|
|
557
|
+
function devClientScheme(root, appPath = null, { exec = null } = {}) {
|
|
558
|
+
if (!hasDevClient(root)) return void 0;
|
|
559
|
+
const fromBundle = pickDevClientScheme(readBundleSchemes(appPath, { exec }));
|
|
560
|
+
if (fromBundle) return fromBundle;
|
|
561
|
+
const app = readJson(join(root, "app.json"));
|
|
562
|
+
const raw = app?.expo?.scheme ?? app?.scheme ?? null;
|
|
563
|
+
const scheme = Array.isArray(raw) ? raw.find((s) => typeof s === "string" && s.trim() !== "") : raw;
|
|
564
|
+
if (typeof scheme !== "string" || scheme.trim() === "") return void 0;
|
|
565
|
+
return scheme.trim();
|
|
566
|
+
}
|
|
567
|
+
function schemesFromInfoPlist(plist) {
|
|
568
|
+
const types = plist?.CFBundleURLTypes;
|
|
569
|
+
if (!Array.isArray(types)) return [];
|
|
570
|
+
const out = [];
|
|
571
|
+
for (const type of types) {
|
|
572
|
+
const schemes = type?.CFBundleURLSchemes;
|
|
573
|
+
if (Array.isArray(schemes)) out.push(...schemes.filter((s) => typeof s === "string" && s.trim() !== ""));
|
|
574
|
+
}
|
|
575
|
+
return out;
|
|
576
|
+
}
|
|
577
|
+
function readBundleSchemes(appPath, { exec = null } = {}) {
|
|
578
|
+
if (typeof appPath !== "string" || appPath.trim() === "") return [];
|
|
579
|
+
const e = exec || getExecutor();
|
|
580
|
+
let out;
|
|
581
|
+
try {
|
|
582
|
+
out = e.runFile("plutil", [
|
|
583
|
+
"-convert",
|
|
584
|
+
"json",
|
|
585
|
+
"-o",
|
|
586
|
+
"-",
|
|
587
|
+
join(appPath, "Info.plist")
|
|
588
|
+
]);
|
|
589
|
+
} catch {
|
|
590
|
+
return [];
|
|
591
|
+
}
|
|
592
|
+
try {
|
|
593
|
+
return schemesFromInfoPlist(JSON.parse(String(out)));
|
|
594
|
+
} catch {
|
|
595
|
+
return [];
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
const THIRD_PARTY_SCHEME = /^(?:fb\d+|com\.googleusercontent\.apps\.|msauth\.|msauthv2|twitterkit-|db-[a-z0-9]+$|spotify|snapchat|com\.facebook)/i;
|
|
599
|
+
function pickDevClientScheme(schemes) {
|
|
600
|
+
const all = (Array.isArray(schemes) ? schemes : []).filter((s) => typeof s === "string" && s.trim() !== "").map((s) => s.trim()).filter((s) => !/^(?:https?|mailto|tel|sms|itms(?:-apps)?)$/i.test(s));
|
|
601
|
+
const expo = all.filter((s) => s.startsWith("exp+"));
|
|
602
|
+
return (expo.length ? expo : all.filter((s) => !THIRD_PARTY_SCHEME.test(s))).toSorted((a, b) => b.length - a.length)[0] ?? null;
|
|
603
|
+
}
|
|
604
|
+
function hasDevClient(root) {
|
|
605
|
+
const pkg = readJson(join(root, "package.json"));
|
|
606
|
+
if ("expo-dev-client" in {
|
|
607
|
+
...pkg?.dependencies,
|
|
608
|
+
...pkg?.devDependencies
|
|
609
|
+
}) return true;
|
|
610
|
+
return isPackageResolvable(root, "expo-dev-client");
|
|
611
|
+
}
|
|
612
|
+
function readJson(file) {
|
|
613
|
+
try {
|
|
614
|
+
return JSON.parse(readFileSync(file, "utf-8"));
|
|
615
|
+
} catch {
|
|
616
|
+
return null;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
//#endregion
|
|
620
|
+
export { swapJsBundle as a, ensureWorkspaceStorageSafely as c, noMetroMessage as d, noMetroRemedy as f, warmMetro as h, detectEntryFile as i, gateShouldRetry as l, sleep as m, pickDevClientScheme as n, needsPrebuild as o, resolveMetroWithRetry as p, schemesFromInfoPlist as r, runPrebuild as s, devClientScheme as t, launchOutcomeRecord as u };
|