stim 1.11.0 → 1.12.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/dist/{activity-BtG4Va9l.mjs → activity-CCZJs__P.mjs} +4 -3
- package/dist/{android-C4zG2Tuc.mjs → android-75Sk2rIR.mjs} +25 -8
- package/dist/{android-C1q4l523.mjs → android-B7X-8H7h.mjs} +75 -32
- package/dist/{android-BA2i_KAV.mjs → android-BQiKJYK0.mjs} +1 -1
- package/dist/{android-cas-DfJkwCXW.mjs → android-cas-DzLPXiEn.mjs} +7 -7
- package/dist/{app-install-DHut6JnV.mjs → app-install-CsZi0SAT.mjs} +1 -1
- package/dist/{workspaces-COD2xCz-.mjs → budget-DbzW5VTD.mjs} +641 -32
- package/dist/{build-plan-CeAYgKAx.mjs → build-plan-CqntbR7b.mjs} +120 -35
- package/dist/{build-progress-CDwogz6R.mjs → build-progress-h60GPV27.mjs} +3 -3
- package/dist/{cache-manifest-Pubfp8wV.mjs → cache-manifest-DWPF4RKJ.mjs} +1 -1
- package/dist/cache-manifest.mjs +1 -1
- package/dist/cli.mjs +16 -16
- package/dist/collector-run.mjs +4 -4
- package/dist/{command-output-D5z2_bcF.mjs → command-output-BWCj5Vh-.mjs} +1 -1
- package/dist/{config-BEq0Qw_a.mjs → config-0jCcMM0E.mjs} +10 -1
- package/dist/{dependency-state-r9UesA9a.mjs → dependency-state-DNN0yfx6.mjs} +1 -1
- package/dist/{deps-OF7CD5Dx.mjs → deps-CO-_7UOm.mjs} +2 -2
- package/dist/{device-BwH2iI3O.mjs → device-9r9xRYDa.mjs} +7 -7
- package/dist/{device-lease-D83nqeMX.mjs → device-lease-B9LAXIUH.mjs} +5 -5
- package/dist/{device-pool-DHj7cdPy.mjs → device-pool-BoezJd-c.mjs} +4 -4
- package/dist/{device-remote-BQZSnnmE.mjs → device-remote-C0_o-tJH.mjs} +254 -579
- package/dist/{doctor-D8QdMRMf.mjs → doctor-LfDlbR-N.mjs} +19 -18
- package/dist/{error-diagnostics-UYFSQwRy.mjs → error-diagnostics-CCxjn31V.mjs} +7 -7
- package/dist/{gc-COZteyiq.mjs → gc-C4nQFcTX.mjs} +203 -39
- package/dist/{guide-NDvTmMi-.mjs → guide-BPBlh9ML.mjs} +189 -38
- package/dist/{guide-status-CWvti2Lg.mjs → guide-status-i9ogHt4v.mjs} +2 -2
- package/dist/{idle-NETbeNiQ.mjs → idle-S3tFZ53_.mjs} +7 -7
- package/dist/{in-use-DVhlSoyn.mjs → in-use-D9XYEgFD.mjs} +7 -7
- package/dist/{ios-CQLKpNfr.mjs → ios-CDzWL1mj.mjs} +30 -29
- package/dist/{ios-DArbmwer.mjs → ios-CZWOHTTc.mjs} +23 -19
- package/dist/{ios-device-CwDZo0X4.mjs → ios-device-D9cLxVr5.mjs} +1 -1
- package/dist/{launch-verify-CuPDGXdr.mjs → launch-verify-B2oR_kFN.mjs} +2 -2
- package/dist/{logs-B44a1UeU.mjs → logs-SzuYudOj.mjs} +6 -6
- package/dist/{merge-state-DDxS-XIZ.mjs → merge-state-ytFQCeh4.mjs} +38 -8
- package/dist/{metro-BWLOJ_Wr.mjs → metro-Dzo30hvJ.mjs} +1 -1
- package/dist/{named-ports-Dc7neAlc.mjs → named-ports-Dn1o61D9.mjs} +3 -3
- package/dist/{ownership-BiojdtAp.mjs → ownership-B8d4kXBx.mjs} +49 -16
- package/dist/{ownership-D2xbLVoz.mjs → ownership-Dqnrjrio.mjs} +2 -2
- package/dist/{ports-DwBaRFJs.mjs → ports-Dp_QTwTC.mjs} +3 -3
- package/dist/{project-BXZzOABt.mjs → project-B8rbutVC.mjs} +4 -72
- package/dist/{reload-By9Dwsrt.mjs → reload-nocmx8KB.mjs} +9 -9
- package/dist/remote-cache-BRsRZru5.mjs +526 -0
- package/dist/{server-bare-D_RK1RCj.mjs → server-bare-Gn3ssSdK.mjs} +1 -1
- package/dist/{server-expo-o6H5gcBc.mjs → server-expo-CeT1nIIk.mjs} +2 -2
- package/dist/server-expo-eWi9Coof.mjs +2 -0
- package/dist/{settings-B5oFZEri.mjs → settings-BlyDn2Oz.mjs} +2 -2
- package/dist/{settings-C0ugALSC.mjs → settings-bE-Gu3Ti.mjs} +23 -11
- package/dist/settings.schema.json +22 -2
- package/dist/{simslim-CIR3IYJO.mjs → simslim-COtEEP7x.mjs} +6 -5
- package/dist/{slot-launch-rggO1Esw.mjs → slot-launch-DIACGEfH.mjs} +4 -4
- package/dist/{spawn-claims-BJwXTMPt.mjs → spawn-claims-PT8aQpCa.mjs} +1 -1
- package/dist/{start-i8zc9VmS.mjs → start-BFstVBlx.mjs} +1 -1
- package/dist/{start-BSsf2tUs.mjs → start-BboE1HIS.mjs} +108 -17
- package/dist/{state-WGdIbi_d.mjs → state-DcDgzfu3.mjs} +15 -3
- package/dist/{stats-BAgJaLA4.mjs → stats-D8rUTPHd.mjs} +1 -1
- package/dist/{stats-DBYJ5bxL.mjs → stats-DiCtmluY.mjs} +4 -4
- package/dist/{status-Q-WDEGCB.mjs → status-BUASYyy_.mjs} +73 -28
- package/dist/{status-CitcBRsC.mjs → status-DI9Tjo7T.mjs} +41 -29
- package/dist/stim-desktop-D0xhpZt8.mjs +29 -0
- package/dist/{stop-rst5mL14.mjs → stop-BI7QY76M.mjs} +2 -2
- package/dist/{stop-DIhrvu0B.mjs → stop-Bn6v-GvS.mjs} +23 -16
- package/dist/supervisor-run.mjs +11 -11
- package/dist/{workspace-process-lock-Cx74RSDZ.mjs → workspace-process-lock-BvvJTfo_.mjs} +2 -2
- package/dist/{worktree-DhxDTK44.mjs → worktree-B5sTRskL.mjs} +1 -1
- package/dist/{worktree-KOjMX3zA.mjs → worktree-ChDL5fO1.mjs} +17 -16
- package/dist/{worktree-BlizKDpZ.mjs → worktree-DeBQSVtR.mjs} +3 -1
- package/dist/{xcode-B5SPD--o.mjs → xcode-BzplpQzc.mjs} +7 -7
- package/package.json +5 -5
- package/dist/budget-De3P2rq_.mjs +0 -620
- package/dist/native-run-D7yB_AsB.mjs +0 -143
- package/dist/server-expo-B-CCUJZj.mjs +0 -2
|
@@ -1,538 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { d as getConfigDir } from "./config-0jCcMM0E.mjs";
|
|
2
2
|
import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { c as
|
|
6
|
-
import {
|
|
7
|
-
import { t as withWorkspaceProcessLock } from "./workspace-process-lock-
|
|
8
|
-
import {
|
|
9
|
-
import { B as planMetroReach } from "./settings-
|
|
10
|
-
import { u as probePublicHttp } from "./ownership-
|
|
11
|
-
import { C as sanitizeDeviceLabel } from "./ios-
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { c as readMetroRecords, o as isBundleProof } from "./launch-verify-CuPDGXdr.mjs";
|
|
3
|
+
import { N as workspaceStateFile, O as workspaceDir, P as withDirLock, f as clearWorkspaceStateKey, g as updateWorkspaceState, i as pidExists, j as workspaceLogsDir, k as workspaceId, m as readWorkspaceState$1 } from "./metro-Dzo30hvJ.mjs";
|
|
4
|
+
import { o as ownedDeviceLabel } from "./project-B8rbutVC.mjs";
|
|
5
|
+
import { a as clearRemoteSession, c as readMetroTunnel, u as readRemoteSession } from "./state-DcDgzfu3.mjs";
|
|
6
|
+
import { i as signalDeclaredSpawns, r as refuseDeclaredSpawns, t as allowDeclaredSpawns } from "./spawn-claims-PT8aQpCa.mjs";
|
|
7
|
+
import { t as withWorkspaceProcessLock } from "./workspace-process-lock-BvvJTfo_.mjs";
|
|
8
|
+
import { a as formatElapsed } from "./command-output-BWCj5Vh-.mjs";
|
|
9
|
+
import { B as planMetroReach } from "./settings-BlyDn2Oz.mjs";
|
|
10
|
+
import { u as probePublicHttp } from "./ownership-B8d4kXBx.mjs";
|
|
11
|
+
import { C as sanitizeDeviceLabel } from "./ios-CZWOHTTc.mjs";
|
|
12
|
+
import { a as LAUNCH_ERROR, i as INSTALL_ERROR, l as devClientDeepLink } from "./app-install-CsZi0SAT.mjs";
|
|
13
|
+
import { c as readMetroRecords, o as isBundleProof } from "./launch-verify-B2oR_kFN.mjs";
|
|
15
14
|
import { t as compareStimVersions } from "./stim-installations-CINXy_su.mjs";
|
|
16
|
-
import {
|
|
17
|
-
import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { existsSync, mkdirSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
18
16
|
import { easMachineStateRoot, easMachineStateRoot as easMachineStateRoot$1, easSessionLedgerFile, easSessionLedgerLock, readEasSessionLedger, readEasSessionLedger as readEasSessionLedger$1, readJsonObject, validEasSessionClaim } from "@stim-cli/core/state";
|
|
19
17
|
import { dirname, join, resolve } from "node:path";
|
|
20
|
-
import { pathToFileURL } from "node:url";
|
|
21
|
-
import { format } from "node:util";
|
|
22
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
23
|
-
//#region src/engine/remote-cache.ts
|
|
24
|
-
const EAS_PROVIDER_PACKAGE = "eas-build-cache-provider";
|
|
25
|
-
const RESOLVE_TIMEOUT_MS = 3e4;
|
|
26
|
-
const UPLOAD_TIMEOUT_MS = 6e4;
|
|
27
|
-
const CONFIG_TIMEOUT_MS = 3e4;
|
|
28
|
-
const DYNAMIC_CONFIG_FILES = [
|
|
29
|
-
"app.config.ts",
|
|
30
|
-
"app.config.js",
|
|
31
|
-
"app.config.mjs",
|
|
32
|
-
"app.config.cjs"
|
|
33
|
-
];
|
|
34
|
-
const TIMED_OUT = Symbol("timed-out");
|
|
35
|
-
function dynamicConfigFile(root) {
|
|
36
|
-
for (const name of DYNAMIC_CONFIG_FILES) if (existsSync(join(root, name))) return name;
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
function providerFromConfig(config) {
|
|
40
|
-
if (!config || typeof config !== "object") return null;
|
|
41
|
-
const expoField = config.expo;
|
|
42
|
-
const exp = expoField && typeof expoField === "object" ? expoField : config;
|
|
43
|
-
return exp.buildCacheProvider ?? exp.experiments?.buildCacheProvider ?? null;
|
|
44
|
-
}
|
|
45
|
-
function ownerFromConfig(config) {
|
|
46
|
-
if (!config || typeof config !== "object") return null;
|
|
47
|
-
const expoField = config.expo;
|
|
48
|
-
const owner = (expoField && typeof expoField === "object" ? expoField : config).owner;
|
|
49
|
-
return typeof owner === "string" && owner.trim() !== "" ? owner.trim() : null;
|
|
50
|
-
}
|
|
51
|
-
function normalizeProvider(raw) {
|
|
52
|
-
if (raw === null || raw === void 0 || raw === "") return null;
|
|
53
|
-
if (raw === "eas") return {
|
|
54
|
-
name: "eas",
|
|
55
|
-
reference: EAS_PROVIDER_PACKAGE,
|
|
56
|
-
options: {}
|
|
57
|
-
};
|
|
58
|
-
if (typeof raw === "object") {
|
|
59
|
-
const obj = raw;
|
|
60
|
-
if (typeof obj.plugin === "string" && obj.plugin.trim() !== "") {
|
|
61
|
-
const reference = obj.plugin.trim();
|
|
62
|
-
return {
|
|
63
|
-
name: reference,
|
|
64
|
-
reference,
|
|
65
|
-
options: obj.options && typeof obj.options === "object" ? obj.options : {}
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return { invalid: typeof raw === "string" ? `"${raw}"` : JSON.stringify(raw) };
|
|
70
|
-
}
|
|
71
|
-
function runOptionsFor(platform) {
|
|
72
|
-
return platform === "android" ? { variant: "debug" } : { configuration: "Debug" };
|
|
73
|
-
}
|
|
74
|
-
function isProviderPlugin(plugin) {
|
|
75
|
-
if (!plugin || typeof plugin !== "object") return false;
|
|
76
|
-
const p = plugin;
|
|
77
|
-
const resolves = typeof p.resolveBuildCache === "function" || typeof p.resolveRemoteBuildCache === "function";
|
|
78
|
-
const uploads = typeof p.uploadBuildCache === "function" || typeof p.uploadRemoteBuildCache === "function";
|
|
79
|
-
return resolves && uploads;
|
|
80
|
-
}
|
|
81
|
-
function cacheLevel(value) {
|
|
82
|
-
return value === "local" || value === "remote" ? value : false;
|
|
83
|
-
}
|
|
84
|
-
function exitAfterFlush(code = 0, { exit = (c) => process.exit(c), stream = process.stdout } = {}) {
|
|
85
|
-
try {
|
|
86
|
-
stream.write("", () => exit(code));
|
|
87
|
-
} catch {
|
|
88
|
-
exit(code);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
function readProjectConfig(root, { run = null, timeoutMs = CONFIG_TIMEOUT_MS } = {}) {
|
|
92
|
-
const dynamic = dynamicConfigFile(root);
|
|
93
|
-
if (!dynamic) {
|
|
94
|
-
const file = join(root, "app.json");
|
|
95
|
-
if (!existsSync(file)) return {
|
|
96
|
-
config: null,
|
|
97
|
-
source: null
|
|
98
|
-
};
|
|
99
|
-
try {
|
|
100
|
-
return {
|
|
101
|
-
config: JSON.parse(readFileSync(file, "utf-8")),
|
|
102
|
-
source: "app.json"
|
|
103
|
-
};
|
|
104
|
-
} catch (err) {
|
|
105
|
-
return { unavailable: `app.json could not be parsed: ${firstLine(err)}` };
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
const bin = expoBinPath(root);
|
|
109
|
-
if (!bin) return { unavailable: `${dynamic} is code, so it has to be evaluated to read its buildCacheProvider, and the \`expo\` package is not resolvable from this project (no expo/package.json to read a bin from, and no node_modules/.bin/expo here or in any parent)` };
|
|
110
|
-
const exec = run || ((file, args, opts) => getExecutor().runFile(file, args, opts));
|
|
111
|
-
let stdout;
|
|
112
|
-
try {
|
|
113
|
-
stdout = exec(bin, [
|
|
114
|
-
"config",
|
|
115
|
-
"--json",
|
|
116
|
-
root
|
|
117
|
-
], { timeoutMs });
|
|
118
|
-
} catch (err) {
|
|
119
|
-
return { unavailable: `\`expo config --json\` failed: ${firstLine(err)}` };
|
|
120
|
-
}
|
|
121
|
-
try {
|
|
122
|
-
return {
|
|
123
|
-
config: JSON.parse(String(stdout)),
|
|
124
|
-
source: dynamic
|
|
125
|
-
};
|
|
126
|
-
} catch (err) {
|
|
127
|
-
return { unavailable: `\`expo config --json\` did not print JSON: ${firstLine(err)}` };
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
async function loadPlugin(projectRoot, reference, { requireFrom = null } = {}) {
|
|
131
|
-
const localRequire = requireFrom ? requireFrom(projectRoot) : createRequire(join(projectRoot, "package.json"));
|
|
132
|
-
const file = localRequire.resolve(reference);
|
|
133
|
-
let mod;
|
|
134
|
-
try {
|
|
135
|
-
mod = localRequire(file);
|
|
136
|
-
} catch (err) {
|
|
137
|
-
if (err?.code !== "ERR_REQUIRE_ESM") throw err;
|
|
138
|
-
mod = await import(pathToFileURL(file).href);
|
|
139
|
-
}
|
|
140
|
-
const plugin = mod?.default ?? mod;
|
|
141
|
-
if (!isProviderPlugin(plugin)) throw new Error(`"${reference}" does not export resolveBuildCache and uploadBuildCache functions`);
|
|
142
|
-
return plugin;
|
|
143
|
-
}
|
|
144
|
-
async function loadProjectProvider(projectRoot, { isExpo = true, run = null, requireFrom = null, timeoutMs = CONFIG_TIMEOUT_MS } = {}) {
|
|
145
|
-
if (!isExpo) return { none: true };
|
|
146
|
-
const read = readProjectConfig(projectRoot, {
|
|
147
|
-
run,
|
|
148
|
-
timeoutMs
|
|
149
|
-
});
|
|
150
|
-
if (read.unavailable) return { unavailable: read.unavailable };
|
|
151
|
-
const normalized = normalizeProvider(providerFromConfig(read.config));
|
|
152
|
-
if (!normalized) return { none: true };
|
|
153
|
-
if (normalized.invalid) return { unavailable: `buildCacheProvider is ${normalized.invalid}, which is not "eas" or { plugin: <module> }` };
|
|
154
|
-
if (normalized.reference === "@stim-cli/expo-build-cache") return { none: true };
|
|
155
|
-
let plugin;
|
|
156
|
-
try {
|
|
157
|
-
plugin = await loadPlugin(projectRoot, normalized.reference, { requireFrom });
|
|
158
|
-
} catch (err) {
|
|
159
|
-
const missing = err?.code === "MODULE_NOT_FOUND";
|
|
160
|
-
return { unavailable: normalized.name === "eas" && missing ? `the EAS build cache needs the \`${EAS_PROVIDER_PACKAGE}\` package, which is not installed in this project` : `${normalized.name} could not be loaded: ${firstLine(err)}` };
|
|
161
|
-
}
|
|
162
|
-
return {
|
|
163
|
-
provider: {
|
|
164
|
-
plugin,
|
|
165
|
-
options: normalized.options ?? {}
|
|
166
|
-
},
|
|
167
|
-
name: normalized.name,
|
|
168
|
-
owner: ownerFromConfig(read.config)
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
const WHOAMI_TIMEOUT_MS = 15e3;
|
|
172
|
-
const EAS_CLI_PACKAGE = "eas-cli";
|
|
173
|
-
const EAS_CLI_BIN = "eas";
|
|
174
|
-
function parseWhoami({ stdout = "", stderr = "", exitCode = 0 } = {}) {
|
|
175
|
-
const out = stripAnsi(stdout);
|
|
176
|
-
const combined = `${out}\n${stripAnsi(stderr)}`;
|
|
177
|
-
if (/^\s*Not logged in\s*$/m.test(combined)) return { loggedOut: true };
|
|
178
|
-
if (exitCode !== 0) return { unknown: firstLine(combined.trim() || `eas whoami exited ${exitCode}`) };
|
|
179
|
-
const lines = out.split("\n").map((line) => line.trim()).filter((line) => line !== "");
|
|
180
|
-
if (lines.length === 0) return { unknown: "eas whoami printed nothing" };
|
|
181
|
-
const raw = lines[0];
|
|
182
|
-
if (raw === void 0) return { unknown: "eas whoami printed nothing" };
|
|
183
|
-
const viaToken = /\(authenticated using EXPO_TOKEN\)\s*$/.test(raw);
|
|
184
|
-
const display = raw.replace(/\s*\(authenticated using EXPO_TOKEN\)\s*$/, "").trim();
|
|
185
|
-
const accounts = [];
|
|
186
|
-
for (const line of lines) {
|
|
187
|
-
const name = /^\u2022\s*(\S+)\s*\(Role:/.exec(line)?.[1];
|
|
188
|
-
if (name !== void 0) accounts.push(name);
|
|
189
|
-
}
|
|
190
|
-
if (accounts.length === 0 && !display.endsWith("(robot)") && display !== "robot") accounts.push(display);
|
|
191
|
-
return {
|
|
192
|
-
loggedIn: true,
|
|
193
|
-
account: display,
|
|
194
|
-
accounts: accounts.length ? accounts : null,
|
|
195
|
-
viaToken
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
function isEasAuthFailureText(text) {
|
|
199
|
-
const t = stripAnsi(text || "");
|
|
200
|
-
if (t.trim() === "") return false;
|
|
201
|
-
return /not logged in/i.test(t) || /\beas login\b/i.test(t) || /EXPO_TOKEN/.test(t) || /unauthorized/i.test(t) || /not authorized/i.test(t) || /authentication (failed|required)/i.test(t);
|
|
202
|
-
}
|
|
203
|
-
function easAuthNote(status) {
|
|
204
|
-
const { code, account, owner, reason, phase = "resolve" } = status || {};
|
|
205
|
-
const because = reason ? ` (${reason})` : "";
|
|
206
|
-
if (code === "logged-out") return `eas is not authenticated${because} -- run \`eas login\` (or set EXPO_TOKEN); ` + (phase === "upload" ? "this build stayed in the local cache" : "building with the local cache only");
|
|
207
|
-
if (code === "wrong-account") return `eas is authenticated as ${account}, and this project's owner is ${owner} -- run \`eas login\` as a member of ${owner} (or set EXPO_TOKEN); consulting the EAS cache anyway`;
|
|
208
|
-
return null;
|
|
209
|
-
}
|
|
210
|
-
function resolveEasCliBin(projectRoot, { lookupPath = null } = {}) {
|
|
211
|
-
const fromPackage = expoBinFromPackage(resolvePackageJson(projectRoot, EAS_CLI_PACKAGE), EAS_CLI_BIN);
|
|
212
|
-
if (fromPackage) return {
|
|
213
|
-
file: fromPackage,
|
|
214
|
-
source: "project"
|
|
215
|
-
};
|
|
216
|
-
const shim = findBinUpward(projectRoot, EAS_CLI_BIN);
|
|
217
|
-
if (shim) return {
|
|
218
|
-
file: shim,
|
|
219
|
-
source: "project"
|
|
220
|
-
};
|
|
221
|
-
const onPath = lookupPath ? lookupPath() : getExecutor().findExecutable(EAS_CLI_BIN);
|
|
222
|
-
const file = (String(onPath || "").split("\n")[0] ?? "").trim();
|
|
223
|
-
return file ? {
|
|
224
|
-
file,
|
|
225
|
-
source: "path"
|
|
226
|
-
} : null;
|
|
227
|
-
}
|
|
228
|
-
const easAuthCache = /* @__PURE__ */ new Map();
|
|
229
|
-
function checkEasAuth({ projectRoot, owner = null, run = null, resolveBin = null, timeoutMs = WHOAMI_TIMEOUT_MS, cache = easAuthCache }) {
|
|
230
|
-
const key = `${projectRoot}::${owner || ""}`;
|
|
231
|
-
if (cache?.has(key)) return cache.get(key);
|
|
232
|
-
const answer = probeEasAuth({
|
|
233
|
-
projectRoot,
|
|
234
|
-
owner,
|
|
235
|
-
run,
|
|
236
|
-
resolveBin,
|
|
237
|
-
timeoutMs
|
|
238
|
-
});
|
|
239
|
-
cache?.set(key, answer);
|
|
240
|
-
return answer;
|
|
241
|
-
}
|
|
242
|
-
function probeEasAuth({ projectRoot, owner, run, resolveBin, timeoutMs }) {
|
|
243
|
-
const bin = (resolveBin || resolveEasCliBin)(projectRoot);
|
|
244
|
-
if (!bin) return {
|
|
245
|
-
failed: true,
|
|
246
|
-
code: "no-cli",
|
|
247
|
-
reason: "no `eas` executable is resolvable from this project or on PATH",
|
|
248
|
-
remedy: "Install eas-cli (`npm i -g eas-cli`, or as a project devDependency), then run `eas login`."
|
|
249
|
-
};
|
|
250
|
-
const exec = run || ((file, args, opts) => getExecutor().runFile(file, args, opts));
|
|
251
|
-
let result;
|
|
252
|
-
try {
|
|
253
|
-
result = {
|
|
254
|
-
stdout: String(exec(bin.file, ["whoami"], { timeoutMs }) ?? ""),
|
|
255
|
-
exitCode: 0
|
|
256
|
-
};
|
|
257
|
-
} catch (e) {
|
|
258
|
-
const err = e;
|
|
259
|
-
if (err?.code === "ETIMEDOUT" || err?.signal === "SIGTERM" || err?.killed) return { unknown: `eas whoami timed out after ${timeoutMs}ms` };
|
|
260
|
-
result = {
|
|
261
|
-
stdout: String(err?.stdout ?? ""),
|
|
262
|
-
stderr: String(err?.stderr ?? err?.message ?? ""),
|
|
263
|
-
exitCode: typeof err?.status === "number" ? err.status : 1
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
const parsed = parseWhoami(result);
|
|
267
|
-
if (parsed.loggedOut) return {
|
|
268
|
-
failed: true,
|
|
269
|
-
code: "logged-out",
|
|
270
|
-
reason: "eas whoami says \"Not logged in\"",
|
|
271
|
-
remedy: "Run `eas login` (or set EXPO_TOKEN)."
|
|
272
|
-
};
|
|
273
|
-
if (parsed.unknown) return { unknown: parsed.unknown };
|
|
274
|
-
if (owner && Array.isArray(parsed.accounts) && !parsed.accounts.includes(owner)) return {
|
|
275
|
-
failed: true,
|
|
276
|
-
code: "wrong-account",
|
|
277
|
-
account: parsed.account,
|
|
278
|
-
accounts: parsed.accounts,
|
|
279
|
-
owner,
|
|
280
|
-
reason: `eas is authenticated as ${parsed.account} (accounts: ${parsed.accounts.join(", ")}), which does not include this project's owner ${owner}`,
|
|
281
|
-
remedy: `Run \`eas login\` as a member of ${owner}, or set EXPO_TOKEN to a token for it.`
|
|
282
|
-
};
|
|
283
|
-
return {
|
|
284
|
-
ok: true,
|
|
285
|
-
account: parsed.account,
|
|
286
|
-
accounts: parsed.accounts,
|
|
287
|
-
viaToken: parsed.viaToken,
|
|
288
|
-
source: bin.source
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
const capturing = [];
|
|
292
|
-
const abandoned = /* @__PURE__ */ new Set();
|
|
293
|
-
let patched = null;
|
|
294
|
-
const PROVIDER_CONTEXT = new AsyncLocalStorage();
|
|
295
|
-
function uploadDestination(lines) {
|
|
296
|
-
const text = (lines || []).map(stripAnsi);
|
|
297
|
-
for (const line of text) {
|
|
298
|
-
const url = /(https?:\/\/[^\s'"]+)/.exec(line)?.[1];
|
|
299
|
-
if (url !== void 0) return url.replace(/[.,)\]]+$/, "");
|
|
300
|
-
}
|
|
301
|
-
for (const line of text) {
|
|
302
|
-
const slug = /\bto\s+(@?[\w.-]+\/[\w.-]+)/i.exec(line)?.[1];
|
|
303
|
-
if (slug !== void 0) return slug.replace(/[.,)\]]+$/, "");
|
|
304
|
-
}
|
|
305
|
-
return null;
|
|
306
|
-
}
|
|
307
|
-
function providerNote(text) {
|
|
308
|
-
return `${"cache".padEnd(11)} ${text}`;
|
|
309
|
-
}
|
|
310
|
-
function currentFrame() {
|
|
311
|
-
const store = PROVIDER_CONTEXT.getStore();
|
|
312
|
-
return store && (capturing.includes(store) || abandoned.has(store)) ? store : null;
|
|
313
|
-
}
|
|
314
|
-
function install() {
|
|
315
|
-
if (patched && process.stdout.write === patched.patchedWrite) return;
|
|
316
|
-
const stdout = process.stdout;
|
|
317
|
-
const write = stdout.write.bind(stdout);
|
|
318
|
-
const log = console.log;
|
|
319
|
-
const passthrough = write;
|
|
320
|
-
const patchedWrite = ((chunk, encoding, callback) => {
|
|
321
|
-
const frame = currentFrame();
|
|
322
|
-
if (!frame) return passthrough(chunk, encoding, callback);
|
|
323
|
-
const cb = typeof encoding === "function" ? encoding : callback;
|
|
324
|
-
absorb(frame, typeof chunk === "string" ? chunk : String(chunk));
|
|
325
|
-
if (typeof cb === "function") cb();
|
|
326
|
-
return true;
|
|
327
|
-
});
|
|
328
|
-
const patchedLog = (...args) => {
|
|
329
|
-
const frame = currentFrame();
|
|
330
|
-
if (!frame) return log(...args);
|
|
331
|
-
absorb(frame, `${format(...args)}\n`);
|
|
332
|
-
};
|
|
333
|
-
stdout.write = patchedWrite;
|
|
334
|
-
console.log = patchedLog;
|
|
335
|
-
patched = {
|
|
336
|
-
stdout,
|
|
337
|
-
write,
|
|
338
|
-
log,
|
|
339
|
-
patchedWrite,
|
|
340
|
-
patchedLog
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
function uninstall() {
|
|
344
|
-
if (!patched || capturing.length || abandoned.size) return;
|
|
345
|
-
if (patched.stdout.write === patched.patchedWrite) patched.stdout.write = patched.write;
|
|
346
|
-
if (console.log === patched.patchedLog) console.log = patched.log;
|
|
347
|
-
patched = null;
|
|
348
|
-
}
|
|
349
|
-
function absorb(frame, chunk) {
|
|
350
|
-
try {
|
|
351
|
-
process.stderr.write(chunk);
|
|
352
|
-
} catch {}
|
|
353
|
-
frame.pending += chunk;
|
|
354
|
-
let index = frame.pending.indexOf("\n");
|
|
355
|
-
while (index !== -1) {
|
|
356
|
-
emitLine(frame, frame.pending.slice(0, index));
|
|
357
|
-
frame.pending = frame.pending.slice(index + 1);
|
|
358
|
-
index = frame.pending.indexOf("\n");
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
function emitLine(frame, raw) {
|
|
362
|
-
const line = stripAnsi(raw).trim();
|
|
363
|
-
if (line === "") return;
|
|
364
|
-
frame.lines.push(line);
|
|
365
|
-
const writer = frame.writer();
|
|
366
|
-
try {
|
|
367
|
-
writer?.write({
|
|
368
|
-
src: "build",
|
|
369
|
-
level: "debug",
|
|
370
|
-
event: "provider",
|
|
371
|
-
msg: line
|
|
372
|
-
});
|
|
373
|
-
} catch {}
|
|
374
|
-
frame.onLine?.(line);
|
|
375
|
-
}
|
|
376
|
-
function beginCapture({ logWriter = null, projectRoot = null, platform = null, onLine = null } = {}) {
|
|
377
|
-
let own = null;
|
|
378
|
-
const frame = {
|
|
379
|
-
lines: [],
|
|
380
|
-
pending: "",
|
|
381
|
-
onLine,
|
|
382
|
-
writer: () => {
|
|
383
|
-
if (logWriter) return logWriter;
|
|
384
|
-
if (own) return own;
|
|
385
|
-
if (!projectRoot || !platform) return null;
|
|
386
|
-
own = createNdjsonWriter(join(workspaceLogsDir(projectRoot), `build-${platform}.ndjson`));
|
|
387
|
-
return own;
|
|
388
|
-
},
|
|
389
|
-
close: () => {
|
|
390
|
-
own?.close?.();
|
|
391
|
-
own = null;
|
|
392
|
-
}
|
|
393
|
-
};
|
|
394
|
-
capturing.push(frame);
|
|
395
|
-
install();
|
|
396
|
-
return frame;
|
|
397
|
-
}
|
|
398
|
-
function endCapture(frame) {
|
|
399
|
-
if (!frame) return;
|
|
400
|
-
const at = capturing.lastIndexOf(frame);
|
|
401
|
-
if (at !== -1) capturing.splice(at, 1);
|
|
402
|
-
abandoned.delete(frame);
|
|
403
|
-
if (frame.pending !== "") {
|
|
404
|
-
const rest = frame.pending;
|
|
405
|
-
frame.pending = "";
|
|
406
|
-
emitLine(frame, rest);
|
|
407
|
-
}
|
|
408
|
-
frame.close();
|
|
409
|
-
uninstall();
|
|
410
|
-
}
|
|
411
|
-
function abandonCapture(frame, work) {
|
|
412
|
-
if (!frame) return;
|
|
413
|
-
const at = capturing.lastIndexOf(frame);
|
|
414
|
-
if (at !== -1) capturing.splice(at, 1);
|
|
415
|
-
abandoned.add(frame);
|
|
416
|
-
Promise.resolve(work).then(() => endCapture(frame), () => endCapture(frame));
|
|
417
|
-
}
|
|
418
|
-
async function resolveRemote({ provider, platform, projectRoot, fingerprintHash, runOptions = null, timeoutMs = RESOLVE_TIMEOUT_MS, logWriter = null } = {}) {
|
|
419
|
-
if (!provider?.plugin || !fingerprintHash) return null;
|
|
420
|
-
const opts = runOptions || runOptionsFor(platform);
|
|
421
|
-
const outcome = await withBudget(async () => {
|
|
422
|
-
const props = {
|
|
423
|
-
fingerprintHash: await providerFingerprint({
|
|
424
|
-
provider,
|
|
425
|
-
platform,
|
|
426
|
-
projectRoot,
|
|
427
|
-
runOptions: opts
|
|
428
|
-
}) ?? fingerprintHash,
|
|
429
|
-
platform,
|
|
430
|
-
runOptions: opts,
|
|
431
|
-
projectRoot
|
|
432
|
-
};
|
|
433
|
-
return typeof provider.plugin.resolveBuildCache === "function" ? provider.plugin.resolveBuildCache(props, provider.options) : provider.plugin.resolveRemoteBuildCache(props, provider.options);
|
|
434
|
-
}, timeoutMs, {
|
|
435
|
-
logWriter,
|
|
436
|
-
projectRoot,
|
|
437
|
-
platform
|
|
438
|
-
});
|
|
439
|
-
if (outcome.timedOut) return { timedOut: true };
|
|
440
|
-
if (outcome.error) return { failed: firstLine(outcome.error) };
|
|
441
|
-
const appPath = typeof outcome.value === "string" ? outcome.value.trim() : "";
|
|
442
|
-
if (!appPath) return null;
|
|
443
|
-
if (!existsSync(appPath)) return { failed: `returned ${appPath}, which does not exist` };
|
|
444
|
-
return { appPath };
|
|
445
|
-
}
|
|
446
|
-
async function uploadRemote({ provider, platform, projectRoot, fingerprintHash, buildPath, runOptions = null, timeoutMs = UPLOAD_TIMEOUT_MS, logWriter = null, note = (line) => console.error(line) } = {}) {
|
|
447
|
-
if (!provider?.plugin || !fingerprintHash || !buildPath) return { skipped: true };
|
|
448
|
-
const opts = runOptions || runOptionsFor(platform);
|
|
449
|
-
let announced = false;
|
|
450
|
-
const onLine = (line) => {
|
|
451
|
-
if (announced) return;
|
|
452
|
-
const dest = uploadDestination([line]);
|
|
453
|
-
if (!dest) return;
|
|
454
|
-
announced = true;
|
|
455
|
-
note(providerNote(`uploading to ${dest}`));
|
|
456
|
-
};
|
|
457
|
-
const outcome = await withBudget(async () => {
|
|
458
|
-
const props = {
|
|
459
|
-
projectRoot,
|
|
460
|
-
platform,
|
|
461
|
-
fingerprintHash: await providerFingerprint({
|
|
462
|
-
provider,
|
|
463
|
-
platform,
|
|
464
|
-
projectRoot,
|
|
465
|
-
runOptions: opts
|
|
466
|
-
}) ?? fingerprintHash,
|
|
467
|
-
buildPath,
|
|
468
|
-
runOptions: opts
|
|
469
|
-
};
|
|
470
|
-
return typeof provider.plugin.uploadBuildCache === "function" ? provider.plugin.uploadBuildCache(props, provider.options) : provider.plugin.uploadRemoteBuildCache(props, provider.options);
|
|
471
|
-
}, timeoutMs, {
|
|
472
|
-
logWriter,
|
|
473
|
-
projectRoot,
|
|
474
|
-
platform,
|
|
475
|
-
onLine
|
|
476
|
-
});
|
|
477
|
-
if (outcome.timedOut) return { timedOut: true };
|
|
478
|
-
if (outcome.error) return { failed: firstLine(outcome.error) };
|
|
479
|
-
if (typeof outcome.value !== "string" || outcome.value.trim() === "") {
|
|
480
|
-
const authLine = outcome.lines.find((line) => isEasAuthFailureText(line));
|
|
481
|
-
return { failed: authLine ? firstLine(authLine) : "the provider returned no upload destination" };
|
|
482
|
-
}
|
|
483
|
-
const destination = uploadDestination(outcome.lines);
|
|
484
|
-
return destination ? {
|
|
485
|
-
uploaded: true,
|
|
486
|
-
destination
|
|
487
|
-
} : { uploaded: true };
|
|
488
|
-
}
|
|
489
|
-
async function providerFingerprint({ provider, platform, projectRoot, runOptions }) {
|
|
490
|
-
if (typeof provider.plugin.calculateFingerprintHash !== "function") return null;
|
|
491
|
-
try {
|
|
492
|
-
const hash = await provider.plugin.calculateFingerprintHash({
|
|
493
|
-
projectRoot,
|
|
494
|
-
platform,
|
|
495
|
-
runOptions
|
|
496
|
-
}, provider.options);
|
|
497
|
-
return typeof hash === "string" && hash.trim() !== "" ? hash.trim() : null;
|
|
498
|
-
} catch {
|
|
499
|
-
return null;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
async function withBudget(factory, ms, capture) {
|
|
503
|
-
let timer = null;
|
|
504
|
-
const timeout = new Promise((resolve) => {
|
|
505
|
-
timer = setTimeout(() => resolve(TIMED_OUT), ms);
|
|
506
|
-
timer?.unref?.();
|
|
507
|
-
});
|
|
508
|
-
const frame = beginCapture(capture || {});
|
|
509
|
-
const work = Promise.resolve().then(() => PROVIDER_CONTEXT.run(frame, factory)).then((value) => ({ value }), (error) => ({ error }));
|
|
510
|
-
try {
|
|
511
|
-
const settled = await Promise.race([work, timeout]);
|
|
512
|
-
if (settled === TIMED_OUT) {
|
|
513
|
-
abandonCapture(frame, work);
|
|
514
|
-
return {
|
|
515
|
-
timedOut: true,
|
|
516
|
-
lines: frame.lines
|
|
517
|
-
};
|
|
518
|
-
}
|
|
519
|
-
endCapture(frame);
|
|
520
|
-
return {
|
|
521
|
-
...settled,
|
|
522
|
-
lines: frame.lines
|
|
523
|
-
};
|
|
524
|
-
} catch (err) {
|
|
525
|
-
endCapture(frame);
|
|
526
|
-
throw err;
|
|
527
|
-
} finally {
|
|
528
|
-
if (timer) clearTimeout(timer);
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
function firstLine(err) {
|
|
532
|
-
const line = (String(err?.message || err || "unknown error").trim().split("\n")[0] ?? "").trim();
|
|
533
|
-
return line.length > 200 ? `${line.slice(0, 197)}...` : line;
|
|
534
|
-
}
|
|
535
|
-
//#endregion
|
|
536
18
|
//#region src/engine/metro-gate.ts
|
|
537
19
|
const REMOTE_METRO_WRONG = "STIM_REMOTE_METRO_WRONG";
|
|
538
20
|
const GATE_TIMEOUT_MS = 25e3;
|
|
@@ -1004,6 +486,14 @@ function removeEasSessionClaim(sessionId, root = easMachineStateRoot()) {
|
|
|
1004
486
|
//#endregion
|
|
1005
487
|
//#region src/engine/eas-project-lock.ts
|
|
1006
488
|
const EAS_PROJECT_LOCK_WAIT_MS = 24e4;
|
|
489
|
+
function describeEasProjectLockHolder(holder, now) {
|
|
490
|
+
const { purpose, workspace } = holder.details;
|
|
491
|
+
const facts = [`pid ${holder.owner.pid}`];
|
|
492
|
+
if (typeof workspace === "string") facts.push(`in ${workspace}`);
|
|
493
|
+
const started = Date.parse(holder.startedAt);
|
|
494
|
+
if (Number.isFinite(started)) facts.push(`running for ${formatElapsed(now - started)}`);
|
|
495
|
+
return `${typeof purpose === "string" ? purpose : "another Stim EAS operation"} (${facts.join(", ")})`;
|
|
496
|
+
}
|
|
1007
497
|
function withEasProjectLock(_root, fn, options = {}) {
|
|
1008
498
|
const { machineRoot = easMachineStateRoot$1(), ...lockOptions } = options;
|
|
1009
499
|
assertEasMachineRootWritable(machineRoot);
|
|
@@ -1014,6 +504,144 @@ function withEasProjectLock(_root, fn, options = {}) {
|
|
|
1014
504
|
});
|
|
1015
505
|
}
|
|
1016
506
|
//#endregion
|
|
507
|
+
//#region src/engine/native-run.ts
|
|
508
|
+
const NATIVE_RUN_LOCK = "native-run";
|
|
509
|
+
const NATIVE_RUN_WAIT_MS = 18e5;
|
|
510
|
+
const WAIT_HEARTBEAT_MS = 3e4;
|
|
511
|
+
const CANCEL_REQUEST_KEY = "nativeRunCancel";
|
|
512
|
+
function nativeRunHolder(holder) {
|
|
513
|
+
const { command, platform, slot } = holder.details;
|
|
514
|
+
return {
|
|
515
|
+
command: typeof command === "string" ? command : null,
|
|
516
|
+
platform: typeof platform === "string" ? platform : null,
|
|
517
|
+
slot: typeof slot === "string" ? slot : "default"
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
function describeNativeRunHolder(holder, now) {
|
|
521
|
+
const { command, slot } = nativeRunHolder(holder);
|
|
522
|
+
const started = Date.parse(holder.startedAt);
|
|
523
|
+
const facts = [`pid ${holder.owner.pid}`];
|
|
524
|
+
if (Number.isFinite(started)) facts.push(`running for ${formatElapsed(now - started)}`);
|
|
525
|
+
if (command === null) return `another Stim run (${facts.join(", ")})`;
|
|
526
|
+
return `\`stim ${command}${slot === "default" ? "" : ` --slot ${slot}`}\` (${facts.join(", ")})`;
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Report a wait on the native-run lock: one line as soon as a holder is seen, a line for each new
|
|
530
|
+
* holder, and a heartbeat every WAIT_HEARTBEAT_MS while the same holder keeps it.
|
|
531
|
+
*/
|
|
532
|
+
function nativeRunWaitNotice({ write, now = Date.now, describe = describeNativeRunHolder }) {
|
|
533
|
+
let claimId = null;
|
|
534
|
+
let lastAt = 0;
|
|
535
|
+
return (holder, firstLine) => {
|
|
536
|
+
const at = now();
|
|
537
|
+
if (holder.claimId !== claimId) {
|
|
538
|
+
claimId = holder.claimId;
|
|
539
|
+
lastAt = at;
|
|
540
|
+
write(firstLine ?? `waiting for ${describe(holder, at)} in this workspace to finish`);
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
if (at - lastAt < WAIT_HEARTBEAT_MS) return;
|
|
544
|
+
lastAt = at;
|
|
545
|
+
write(`still waiting for ${describe(holder, at)}`);
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* What `stop` does about the run holding the native-run lock. A build is interrupted only when the stop
|
|
550
|
+
* leaves it nothing to deploy to: the whole workspace stops, the build targets the stopped slot, or the
|
|
551
|
+
* stopped slot is the workspace's only device. A build for another slot that stays is left running and
|
|
552
|
+
* the stop proceeds without the lock. Only an owner whose identity is proven live is signalled.
|
|
553
|
+
*/
|
|
554
|
+
function decideStopAction({ stopSlot, holder, ownerIdentity, deviceSlots }) {
|
|
555
|
+
if (holder.command !== "ios" && holder.command !== "android") return { action: "wait" };
|
|
556
|
+
if (!(stopSlot === void 0 || holder.slot === stopSlot || deviceSlots.includes(stopSlot) && deviceSlots.every((slot) => slot === stopSlot))) return { action: "proceed" };
|
|
557
|
+
return ownerIdentity === "same" ? { action: "interrupt" } : { action: "refuse" };
|
|
558
|
+
}
|
|
559
|
+
function requestNativeRunCancel(root, claimId, pid = process.pid) {
|
|
560
|
+
updateWorkspaceState(root, (state) => ({
|
|
561
|
+
...state,
|
|
562
|
+
[CANCEL_REQUEST_KEY]: {
|
|
563
|
+
claimId,
|
|
564
|
+
pid,
|
|
565
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
566
|
+
}
|
|
567
|
+
}));
|
|
568
|
+
}
|
|
569
|
+
function cancelRequestFor(root, claimId) {
|
|
570
|
+
const request = readWorkspaceState$1(root)?.[CANCEL_REQUEST_KEY];
|
|
571
|
+
if (request?.claimId !== claimId || typeof request.pid !== "number") return null;
|
|
572
|
+
return { pid: request.pid };
|
|
573
|
+
}
|
|
574
|
+
function clearNativeRunCancel(root, claimId) {
|
|
575
|
+
try {
|
|
576
|
+
clearWorkspaceStateKey(root, CANCEL_REQUEST_KEY, (value) => value?.claimId === claimId);
|
|
577
|
+
} catch {}
|
|
578
|
+
}
|
|
579
|
+
let cancelled = null;
|
|
580
|
+
/** Why this process's native run was cancelled, or null while it was not. */
|
|
581
|
+
function runCancellation() {
|
|
582
|
+
return cancelled;
|
|
583
|
+
}
|
|
584
|
+
function cancelledFailure(platform, { code, message }) {
|
|
585
|
+
if (cancelled === null) return null;
|
|
586
|
+
return {
|
|
587
|
+
code: "STIM_CANCELLED",
|
|
588
|
+
message: `The ${platform} run was ${cancelled} before it finished.`,
|
|
589
|
+
remedy: `Run \`stim ${platform}\` again when you want the app on a device.`,
|
|
590
|
+
lines: [`it stopped at ${code ?? "an unfinished step"}${message ? `: ${message}` : ""}`]
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
function cancelOnInterrupt({ root, claim, write, exit }) {
|
|
594
|
+
let signals = 0;
|
|
595
|
+
const onInterrupt = () => {
|
|
596
|
+
signals += 1;
|
|
597
|
+
if (signals > 1) {
|
|
598
|
+
exit(130);
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
const request = cancelRequestFor(root, claim.claimId);
|
|
602
|
+
cancelled = request ? `cancelled by \`stim stop\` (pid ${request.pid})` : "cancelled by an interrupt";
|
|
603
|
+
refuseDeclaredSpawns(`the run was ${cancelled}`);
|
|
604
|
+
const signalled = signalDeclaredSpawns("SIGINT");
|
|
605
|
+
write(signalled > 0 ? `${cancelled}; stopping the running build tool` : request ? `${cancelled}; stopping before the next build tool or install` : cancelled);
|
|
606
|
+
if (signalled === 0 && !request) exit(130);
|
|
607
|
+
};
|
|
608
|
+
process.on("SIGINT", onInterrupt);
|
|
609
|
+
return () => {
|
|
610
|
+
process.off("SIGINT", onInterrupt);
|
|
611
|
+
allowDeclaredSpawns();
|
|
612
|
+
clearNativeRunCancel(root, claim.claimId);
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Run an `ios` or `android` invocation under the workspace's native-run lock: the claim records the
|
|
617
|
+
* command, platform and slot, and a wait for another holder is reported on `write`. The first SIGINT
|
|
618
|
+
* forwards the interrupt to the running declared build tool; a run `stim stop` cancelled between build
|
|
619
|
+
* tools starts no further one; a terminal interrupt with no build tool running, or a second SIGINT, exits
|
|
620
|
+
* with 130 at once.
|
|
621
|
+
*/
|
|
622
|
+
function withNativeBuildRun(root, details, fn, { write, exit = (code) => process.exit(code) }) {
|
|
623
|
+
const notice = nativeRunWaitNotice({ write });
|
|
624
|
+
return withWorkspaceProcessLock(workspaceDir(root), NATIVE_RUN_LOCK, async (claim) => {
|
|
625
|
+
const dispose = cancelOnInterrupt({
|
|
626
|
+
root,
|
|
627
|
+
claim,
|
|
628
|
+
write,
|
|
629
|
+
exit
|
|
630
|
+
});
|
|
631
|
+
try {
|
|
632
|
+
return await fn(claim);
|
|
633
|
+
} finally {
|
|
634
|
+
dispose();
|
|
635
|
+
}
|
|
636
|
+
}, {
|
|
637
|
+
external: true,
|
|
638
|
+
waitMs: NATIVE_RUN_WAIT_MS,
|
|
639
|
+
declareSpawns: true,
|
|
640
|
+
details: { ...details },
|
|
641
|
+
onHeld: (holder) => notice(holder)
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
//#endregion
|
|
1017
645
|
//#region src/engine/device-remote.ts
|
|
1018
646
|
const REMOTE_SESSION_ERROR = "STIM_NO_REMOTE_SESSION";
|
|
1019
647
|
const REMOTE_METRO_ERROR = "STIM_REMOTE_METRO_UNREACHABLE";
|
|
@@ -1437,67 +1065,114 @@ function withRemoteSessionLock(root, fn, options = {}) {
|
|
|
1437
1065
|
...options
|
|
1438
1066
|
});
|
|
1439
1067
|
}
|
|
1440
|
-
|
|
1068
|
+
const EAS_START_HOLD_MS = 108e4 + Math.ceil(DAEMON_WAIT_MS / DAEMON_POLL_MS) * 35e3;
|
|
1069
|
+
async function ensureRemoteBootOwned({ root, platform, sessionName, startedAt, boot, createdSessionId, abandonCreatedSession, webPreviewUrl = () => null, writeState, register = () => {}, withProjectLock = withEasProjectLock, withLock = withRemoteSessionLock, removeEasSessionClaim: removeClaim = removeEasSessionClaim, ledgerRoot = easMachineStateRoot$1(), notice = () => {}, now = Date.now }) {
|
|
1070
|
+
const waitNotice = nativeRunWaitNotice({
|
|
1071
|
+
write: notice,
|
|
1072
|
+
now,
|
|
1073
|
+
describe: describeEasProjectLockHolder
|
|
1074
|
+
});
|
|
1075
|
+
let stuck = null;
|
|
1076
|
+
let seen = null;
|
|
1077
|
+
let projectLocked = false;
|
|
1441
1078
|
try {
|
|
1442
|
-
return await withProjectLock(root, () =>
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1079
|
+
return await withProjectLock(root, () => {
|
|
1080
|
+
projectLocked = true;
|
|
1081
|
+
return withLock(root, async () => {
|
|
1082
|
+
register();
|
|
1083
|
+
const booted = await boot();
|
|
1084
|
+
if (booted.failed) return booted;
|
|
1085
|
+
const sessionId = createdSessionId();
|
|
1086
|
+
if (!sessionId) return booted;
|
|
1087
|
+
try {
|
|
1088
|
+
recordEasSessionClaim({
|
|
1089
|
+
sessionId,
|
|
1090
|
+
name: sessionName,
|
|
1091
|
+
platform,
|
|
1092
|
+
workspaceRoot: root,
|
|
1093
|
+
workspaceHome: getConfigDir(),
|
|
1094
|
+
stateFile: workspaceStateFile(root)
|
|
1095
|
+
}, ledgerRoot);
|
|
1096
|
+
const preview = webPreviewUrl();
|
|
1097
|
+
writeState(root, { remoteDevice: {
|
|
1098
|
+
platform,
|
|
1099
|
+
sessionId,
|
|
1100
|
+
startedAt,
|
|
1101
|
+
...preview ? { webPreviewUrl: preview } : {}
|
|
1102
|
+
} });
|
|
1103
|
+
return booted;
|
|
1104
|
+
} catch (err) {
|
|
1105
|
+
const cleanup = abandonCreatedSession();
|
|
1106
|
+
if (cleanup.ok) {
|
|
1107
|
+
let removalFailure = "the claim store did not remove it";
|
|
1108
|
+
try {
|
|
1109
|
+
if (removeClaim(sessionId, ledgerRoot)) return {
|
|
1110
|
+
failed: true,
|
|
1111
|
+
code: "STIM_REMOTE_SESSION_STATE",
|
|
1112
|
+
reason: `Could not record EAS session ${sessionId}: ${describe(err)}. The session was stopped.`,
|
|
1113
|
+
remedy: "Fix workspace state storage, then retry the remote command."
|
|
1114
|
+
};
|
|
1115
|
+
} catch (error) {
|
|
1116
|
+
removalFailure = describe(error);
|
|
1117
|
+
}
|
|
1118
|
+
return {
|
|
1471
1119
|
failed: true,
|
|
1472
|
-
code: "
|
|
1473
|
-
reason: `Could not record EAS session ${sessionId}: ${describe(err)}. The session was stopped.`,
|
|
1474
|
-
remedy:
|
|
1120
|
+
code: "STIM_REMOTE_SESSION_CLEANUP",
|
|
1121
|
+
reason: `Could not record EAS session ${sessionId}: ${describe(err)}. The session was stopped, but its ownership claim could not be removed: ${removalFailure}.`,
|
|
1122
|
+
remedy: `The stopped session claim remains under ${ledgerRoot}. Repair that ownership ledger before retrying the remote command.`
|
|
1475
1123
|
};
|
|
1476
|
-
} catch (error) {
|
|
1477
|
-
removalFailure = describe(error);
|
|
1478
1124
|
}
|
|
1479
1125
|
return {
|
|
1480
1126
|
failed: true,
|
|
1481
|
-
code: "STIM_REMOTE_SESSION_CLEANUP",
|
|
1482
|
-
reason: `Could not record EAS session ${sessionId}: ${describe(err)}.
|
|
1483
|
-
remedy:
|
|
1127
|
+
code: cleanup.code ?? "STIM_REMOTE_SESSION_CLEANUP",
|
|
1128
|
+
reason: `Could not record EAS session ${sessionId}: ${describe(err)}. ${cleanup.reason ?? ""}`.trim(),
|
|
1129
|
+
remedy: cleanup.remedy ?? `Run \`eas simulator:stop --id ${sessionId}\`.`
|
|
1484
1130
|
};
|
|
1485
1131
|
}
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1132
|
+
});
|
|
1133
|
+
}, {
|
|
1134
|
+
ownerPurpose: "EAS remote start",
|
|
1135
|
+
machineRoot: ledgerRoot,
|
|
1136
|
+
waitMs: Number.POSITIVE_INFINITY,
|
|
1137
|
+
details: {
|
|
1138
|
+
workspace: root,
|
|
1139
|
+
platform
|
|
1140
|
+
},
|
|
1141
|
+
now,
|
|
1142
|
+
onHeld: (holder) => {
|
|
1143
|
+
const at = now();
|
|
1144
|
+
if (seen?.claimId !== holder.claimId) seen = {
|
|
1145
|
+
claimId: holder.claimId,
|
|
1146
|
+
at
|
|
1491
1147
|
};
|
|
1148
|
+
const claimedAt = Date.parse(holder.startedAt);
|
|
1149
|
+
const heldMs = at - (Number.isFinite(claimedAt) ? Math.min(claimedAt, seen.at) : seen.at);
|
|
1150
|
+
if (heldMs > EAS_START_HOLD_MS) {
|
|
1151
|
+
stuck = {
|
|
1152
|
+
holder,
|
|
1153
|
+
heldMs
|
|
1154
|
+
};
|
|
1155
|
+
const error = /* @__PURE__ */ new Error(`The eas-project lock was held past ${formatElapsed(EAS_START_HOLD_MS)}.`);
|
|
1156
|
+
error.code = "STIM_LOCK_TIMEOUT";
|
|
1157
|
+
throw error;
|
|
1158
|
+
}
|
|
1159
|
+
waitNotice(holder, `waiting for ${describeEasProjectLockHolder(holder, at)} to release the EAS project lock; one EAS session starts at a time on this machine`);
|
|
1492
1160
|
}
|
|
1493
|
-
}), {
|
|
1494
|
-
ownerPurpose: "EAS remote start",
|
|
1495
|
-
machineRoot: ledgerRoot
|
|
1496
1161
|
});
|
|
1497
1162
|
} catch (err) {
|
|
1163
|
+
const code = err.code ?? "STIM_NO_REMOTE_SESSION";
|
|
1164
|
+
if (stuck && !projectLocked) {
|
|
1165
|
+
const { holder, heldMs } = stuck;
|
|
1166
|
+
return {
|
|
1167
|
+
failed: true,
|
|
1168
|
+
code,
|
|
1169
|
+
reason: `${describeEasProjectLockHolder(holder, now())} has held the EAS project lock for ${formatElapsed(heldMs)}; an EAS session start holds it for at most ${formatElapsed(EAS_START_HOLD_MS)}.`,
|
|
1170
|
+
remedy: `If pid ${holder.owner.pid} is stuck, stop it, then run the remote command again.`
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1498
1173
|
return {
|
|
1499
1174
|
failed: true,
|
|
1500
|
-
code
|
|
1175
|
+
code,
|
|
1501
1176
|
reason: describe(err),
|
|
1502
1177
|
remedy: "Retry the remote command after the other workspace operation finishes."
|
|
1503
1178
|
};
|
|
@@ -1740,4 +1415,4 @@ function bundleEntryPoint(root, isExpo) {
|
|
|
1740
1415
|
return local ? normalized : `node_modules/${normalized}`;
|
|
1741
1416
|
}
|
|
1742
1417
|
//#endregion
|
|
1743
|
-
export {
|
|
1418
|
+
export { findOrphanedOwnedSessions as A, withEasProjectLock as C, MIN_EAS_CLI_SIMULATOR_VERSION as D, removeEasSessionClaim as E, ownedSessionName as F, parseSessionListPage as I, stopSessionArgs as L, inspectSessionForTeardown as M, isDefinitiveMissingSessionError as N, easCliSupport as O, listOwnedSessionsArgs as P, verifyStoppedSession as R, withNativeBuildRun as S, readEasSessionLedger$1 as T, describeNativeRunHolder as _, ensureMetroReachable as a, requestNativeRunCancel as b, remoteAndroidDeps as c, withRemoteSessionLock as d, NATIVE_RUN_LOCK as f, decideStopAction as g, clearNativeRunCancel as h, endRecordedSession as i, getSessionArgs as j, easCliUpgradeRemedy as k, remoteIosDeps as l, cancelledFailure as m, binOnPath as n, ensureRemoteBootOwned as o, NATIVE_RUN_WAIT_MS as p, bundleEntryPoint as r, readInstalledEasCliVersion as s, REMOTE_SESSION_ERROR as t, resolveRemoteContext as u, nativeRunHolder as v, easMachineStateRoot$1 as w, runCancellation as x, nativeRunWaitNotice as y, gateMetroOrigin as z };
|