stim 1.2.0 → 1.3.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 +30 -0
- package/dist/android-BqVIJoG7.mjs +1368 -0
- package/dist/{android-Bcmev2s2.mjs → android-CqcW1hrP.mjs} +148 -56
- package/dist/{android-cas-D6HPZ6UQ.mjs → android-cas-BR5YJBYv.mjs} +5 -5
- package/dist/{app-install-NFxUahnH.mjs → app-install-DIp9riNx.mjs} +6 -6
- package/dist/{build-slots-D0b6sJyo.mjs → build-slots-C3-R4XQr.mjs} +2 -2
- package/dist/{cache-manifest-Cu4_Znw9.mjs → cache-manifest-CXR-Y8pH.mjs} +1 -1
- package/dist/cache-manifest.mjs +1 -1
- package/dist/cli.mjs +13 -13
- package/dist/collector-run.d.mts +3 -1
- package/dist/collector-run.mjs +33 -8
- package/dist/{config-CMtIaTk9.mjs → config-CcefMY2Q.mjs} +93 -12
- package/dist/{deps-BLBsTNDS.mjs → deps-Spz8ozgo.mjs} +1 -1
- package/dist/{dev-client-RpvXxCXV.mjs → dev-client-BjisHDB5.mjs} +186 -9
- package/dist/{device-CoCPmazS.mjs → device-BkiW-gkk.mjs} +19 -11
- package/dist/{device-lease-Dpik41RY.mjs → device-lease-BJYybF-2.mjs} +107 -83
- package/dist/{device-pool-B5pEVa3t.mjs → device-pool-CPbLGfrB.mjs} +18 -12
- package/dist/{device-remote-E81KAdXf.mjs → device-remote-AkYCKS8U.mjs} +7 -7
- package/dist/{doctor-CqPeMsSm.mjs → doctor-Bmv86UUW.mjs} +11 -12
- package/dist/{doctor-D_CX7ib5.mjs → doctor-fAMXC4vl.mjs} +4 -4
- package/dist/{error-diagnostics-Dw8Gn3yw.mjs → error-diagnostics-DHC31rXA.mjs} +62 -52
- package/dist/{gc-B7KkDinF.mjs → gc-GGu-8ULN.mjs} +66 -57
- package/dist/{guide-BS2Xc24o.mjs → guide-92CPA21s.mjs} +196 -13
- package/dist/{ios-B7KLgIp5.mjs → ios-BSXFeO5H.mjs} +183 -54
- package/dist/{ios-D1h1Di39.mjs → ios-Deh5js5V.mjs} +78 -40
- package/dist/{ios-device-xNt0Lg6v.mjs → ios-device-CWOM_S44.mjs} +1 -1
- package/dist/{logs-DcX9AvRF.mjs → logs-DPNF4C9s.mjs} +9 -6
- package/dist/{logs-query-oqn47Ffx.mjs → logs-query-R-Cqc6Qv.mjs} +22 -5
- package/dist/{metro-COslRK_F.mjs → metro-CORqXyTL.mjs} +2 -2
- package/dist/{ndjson-DcAtEx_K.mjs → ndjson-BZwI1NBv.mjs} +5 -2
- package/dist/{ownership-CGPart6T.mjs → ownership-8O073GIC.mjs} +3 -3
- package/dist/{ownership-CYOyqxUo.mjs → ownership-BpCKiRlQ.mjs} +3 -3
- package/dist/{ownership-claim-7zRxxTfZ.mjs → ownership-claim-Bf2CAfw3.mjs} +4 -3
- package/dist/{project-BBTVABUz.mjs → project-fKmuIaMI.mjs} +2 -2
- package/dist/{reclaim-C5Jmuahx.mjs → reclaim-iW9Cad7k.mjs} +80 -74
- package/dist/{reload-Z8nKifAk.mjs → reload-DazHDSFA.mjs} +32 -24
- package/dist/{server-bare-DEk37Mz2.mjs → server-bare-DEzPZLvf.mjs} +2 -2
- package/dist/server-expo-44em5Pmg.mjs +2 -0
- package/dist/{server-expo-C3ktAwUG.mjs → server-expo-DizT4OMk.mjs} +4 -4
- package/dist/{settings-DwU1_U_q.mjs → settings-DEJf6_kJ.mjs} +2 -2
- package/dist/slot-launch-cci0xvmC.mjs +19 -0
- package/dist/{start-BrY4EuIg.mjs → start-mObjQH2W.mjs} +11 -11
- package/dist/{state-CZ-ER-8d.mjs → state-B3XA2wp2.mjs} +9 -9
- package/dist/{stats-BCWEVLrM.mjs → stats-BR-kwj0B.mjs} +1 -1
- package/dist/{stats-BsF15Bti.mjs → stats-CaaQr4yN.mjs} +3 -3
- package/dist/{status-DFI1Hfpg.mjs → status-BxQNqu9t.mjs} +55 -24
- package/dist/{stop-B7DR4awK.mjs → stop-CrHGcjrV.mjs} +2 -2
- package/dist/{stop-Btg770vV.mjs → stop-DimGXEKY.mjs} +93 -42
- package/dist/supervisor-run.d.mts +1 -2
- package/dist/supervisor-run.mjs +5 -5
- package/dist/{remote-cache-DDmJ1Roq.mjs → teardown-ClFp1Aax.mjs} +887 -888
- package/dist/{workspace-process-lock-BKfdys1q.mjs → workspace-process-lock-3NcQnZ0X.mjs} +1 -1
- package/dist/{worktree-BPTbI-LN.mjs → worktree-DxTgN1CJ.mjs} +12 -12
- package/dist/{worktree-C79xx_kM.mjs → worktree-Eq6TLH8N.mjs} +1 -1
- package/dist/{xcode-DeKCribl.mjs → xcode-D7sAp-Zy.mjs} +82 -56
- package/package.json +5 -5
- package/dist/android-K63_0UfF.mjs +0 -727
- package/dist/build-lock-BsVoLDaq.mjs +0 -645
- package/dist/server-expo-Drd7ksHu.mjs +0 -2
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { t as getExecutor } from "./exec-CyylIdq9.mjs";
|
|
2
|
-
import { C as withConfigLock, g as loadConfig, s as ensureConfig, y as saveConfig } from "./config-
|
|
3
|
-
import { r as createNdjsonWriter } from "./ndjson-
|
|
4
|
-
import { D as workspaceLogsDir } from "./state-
|
|
5
|
-
import { l as resolvePackageJson } from "./project-
|
|
2
|
+
import { B as removeSlotDevice, C as withConfigLock, F as assignSlotDevice, L as deviceSlotPlatforms, g as loadConfig, s as ensureConfig, y as saveConfig, z as projectDeviceSlots } from "./config-CcefMY2Q.mjs";
|
|
3
|
+
import { r as createNdjsonWriter } from "./ndjson-BZwI1NBv.mjs";
|
|
4
|
+
import { D as workspaceLogsDir } from "./state-B3XA2wp2.mjs";
|
|
5
|
+
import { l as resolvePackageJson } from "./project-fKmuIaMI.mjs";
|
|
6
6
|
import { n as stripAnsi } from "./process-output-C7IlIzd7.mjs";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
7
|
+
import { E as ownedAvdMatchesConfiguration, F as shutdownAndroidEmulator, I as waitForAndroidEmulatorShutdown, N as resolveOwnedAvdSerial, O as ownedAvdSystemImage, S as listOrphanedAvdDirectories, T as ownedAvdDirectory, l as avdStorageRoots, o as assertOwnedAvdStopped, p as deleteAvd, s as avdPathExists } from "./android-BqVIJoG7.mjs";
|
|
8
|
+
import { n as expoBinFromPackage, r as expoBinPath, s as findBinUpward } from "./server-expo-DizT4OMk.mjs";
|
|
9
|
+
import { C as shutdownIosSim, _ as renameIosSim, a as deleteIosSim, d as listIosDeviceTypes, g as parseRuntimeVersion, h as parkedSimName, o as deleteParkedIosSim, r as clearAppDataContainer, s as findAppDataContainer, x as resolveOwnedIosSim } from "./ios-Deh5js5V.mjs";
|
|
10
10
|
import { createRequire } from "node:module";
|
|
11
11
|
import { existsSync, lstatSync, readFileSync, renameSync, rmSync } from "node:fs";
|
|
12
12
|
import { randomUUID } from "node:crypto";
|
|
@@ -14,981 +14,980 @@ import { dirname, join } from "node:path";
|
|
|
14
14
|
import { pathToFileURL } from "node:url";
|
|
15
15
|
import { format } from "node:util";
|
|
16
16
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return
|
|
17
|
+
//#region src/engine/remote-cache.ts
|
|
18
|
+
const EAS_PROVIDER_PACKAGE = "eas-build-cache-provider";
|
|
19
|
+
const RESOLVE_TIMEOUT_MS = 3e4;
|
|
20
|
+
const UPLOAD_TIMEOUT_MS = 6e4;
|
|
21
|
+
const CONFIG_TIMEOUT_MS = 3e4;
|
|
22
|
+
const DYNAMIC_CONFIG_FILES = [
|
|
23
|
+
"app.config.ts",
|
|
24
|
+
"app.config.js",
|
|
25
|
+
"app.config.mjs",
|
|
26
|
+
"app.config.cjs"
|
|
27
|
+
];
|
|
28
|
+
const TIMED_OUT = Symbol("timed-out");
|
|
29
|
+
function dynamicConfigFile(root) {
|
|
30
|
+
for (const name of DYNAMIC_CONFIG_FILES) if (existsSync(join(root, name))) return name;
|
|
31
|
+
return null;
|
|
32
32
|
}
|
|
33
|
-
function
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
if (!explicit && pool !== void 0 && (pool === null || typeof pool !== "object" || Array.isArray(pool))) return {
|
|
39
|
-
max: 0,
|
|
40
|
-
error: "Invalid pool value. Expected an object with simulator and emulator bounds."
|
|
41
|
-
};
|
|
42
|
-
const fromConfig = pool !== null && typeof pool === "object" && !Array.isArray(pool) ? pool[key] : void 0;
|
|
43
|
-
const raw = explicit ? fromEnv : fromConfig;
|
|
44
|
-
if (raw === void 0) return {
|
|
45
|
-
max: env.STIM_HOME ? 0 : 3,
|
|
46
|
-
error: null
|
|
47
|
-
};
|
|
48
|
-
const parsed = parseMax(raw, explicit);
|
|
49
|
-
if (parsed === null) return {
|
|
50
|
-
max: 0,
|
|
51
|
-
error: `Invalid ${explicit ? envKey : `pool.${key}`} value ${JSON.stringify(raw)}. Expected a whole number of parked ${platform === "ios" ? "simulators" : "emulators"}, 0 or more.`
|
|
52
|
-
};
|
|
53
|
-
return {
|
|
54
|
-
max: explicit ? parsed : env.STIM_HOME ? 0 : parsed,
|
|
55
|
-
error: null
|
|
56
|
-
};
|
|
33
|
+
function providerFromConfig(config) {
|
|
34
|
+
if (!config || typeof config !== "object") return null;
|
|
35
|
+
const expoField = config.expo;
|
|
36
|
+
const exp = expoField && typeof expoField === "object" ? expoField : config;
|
|
37
|
+
return exp.buildCacheProvider ?? exp.experiments?.buildCacheProvider ?? null;
|
|
57
38
|
}
|
|
58
|
-
function
|
|
59
|
-
if (
|
|
60
|
-
const
|
|
61
|
-
|
|
39
|
+
function ownerFromConfig(config) {
|
|
40
|
+
if (!config || typeof config !== "object") return null;
|
|
41
|
+
const expoField = config.expo;
|
|
42
|
+
const owner = (expoField && typeof expoField === "object" ? expoField : config).owner;
|
|
43
|
+
return typeof owner === "string" && owner.trim() !== "" ? owner.trim() : null;
|
|
62
44
|
}
|
|
63
|
-
function
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
};
|
|
70
|
-
return {
|
|
71
|
-
ios: read("ios"),
|
|
72
|
-
android: read("android")
|
|
45
|
+
function normalizeProvider(raw) {
|
|
46
|
+
if (raw === null || raw === void 0 || raw === "") return null;
|
|
47
|
+
if (raw === "eas") return {
|
|
48
|
+
name: "eas",
|
|
49
|
+
reference: EAS_PROVIDER_PACKAGE,
|
|
50
|
+
options: {}
|
|
73
51
|
};
|
|
52
|
+
if (typeof raw === "object") {
|
|
53
|
+
const obj = raw;
|
|
54
|
+
if (typeof obj.plugin === "string" && obj.plugin.trim() !== "") {
|
|
55
|
+
const reference = obj.plugin.trim();
|
|
56
|
+
return {
|
|
57
|
+
name: reference,
|
|
58
|
+
reference,
|
|
59
|
+
options: obj.options && typeof obj.options === "object" ? obj.options : {}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return { invalid: typeof raw === "string" ? `"${raw}"` : JSON.stringify(raw) };
|
|
74
64
|
}
|
|
75
|
-
function
|
|
76
|
-
return
|
|
77
|
-
}
|
|
78
|
-
function writeParked(config, platform, records) {
|
|
79
|
-
config.parked = {
|
|
80
|
-
...poolBlock(config),
|
|
81
|
-
[platform]: records
|
|
82
|
-
};
|
|
65
|
+
function runOptionsFor(platform) {
|
|
66
|
+
return platform === "android" ? { variant: "debug" } : { configuration: "Debug" };
|
|
83
67
|
}
|
|
84
|
-
function
|
|
85
|
-
|
|
68
|
+
function isProviderPlugin(plugin) {
|
|
69
|
+
if (!plugin || typeof plugin !== "object") return false;
|
|
70
|
+
const p = plugin;
|
|
71
|
+
const resolves = typeof p.resolveBuildCache === "function" || typeof p.resolveRemoteBuildCache === "function";
|
|
72
|
+
const uploads = typeof p.uploadBuildCache === "function" || typeof p.uploadRemoteBuildCache === "function";
|
|
73
|
+
return resolves && uploads;
|
|
86
74
|
}
|
|
87
|
-
function
|
|
88
|
-
return
|
|
75
|
+
function cacheLevel(value) {
|
|
76
|
+
return value === "local" || value === "remote" ? value : false;
|
|
89
77
|
}
|
|
90
|
-
function
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const evicted = ordered.slice(0, ordered.length - max);
|
|
97
|
-
const dropped = new Set(evicted.map((r) => r.udid));
|
|
98
|
-
return {
|
|
99
|
-
keep: records.filter((r) => !dropped.has(r.udid)),
|
|
100
|
-
evicted
|
|
101
|
-
};
|
|
78
|
+
function exitAfterFlush(code = 0, { exit = (c) => process.exit(c), stream = process.stdout } = {}) {
|
|
79
|
+
try {
|
|
80
|
+
stream.write("", () => exit(code));
|
|
81
|
+
} catch {
|
|
82
|
+
exit(code);
|
|
83
|
+
}
|
|
102
84
|
}
|
|
103
|
-
function
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
85
|
+
function readProjectConfig(root, { run = null, timeoutMs = CONFIG_TIMEOUT_MS } = {}) {
|
|
86
|
+
const dynamic = dynamicConfigFile(root);
|
|
87
|
+
if (!dynamic) {
|
|
88
|
+
const file = join(root, "app.json");
|
|
89
|
+
if (!existsSync(file)) return {
|
|
90
|
+
config: null,
|
|
91
|
+
source: null
|
|
92
|
+
};
|
|
93
|
+
try {
|
|
94
|
+
return {
|
|
95
|
+
config: JSON.parse(readFileSync(file, "utf-8")),
|
|
96
|
+
source: "app.json"
|
|
97
|
+
};
|
|
98
|
+
} catch (err) {
|
|
99
|
+
return { unavailable: `app.json could not be parsed: ${firstLine(err)}` };
|
|
109
100
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
});
|
|
101
|
+
}
|
|
102
|
+
const bin = expoBinPath(root);
|
|
103
|
+
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)` };
|
|
104
|
+
const exec = run || ((file, args, opts) => getExecutor().runFile(file, args, opts));
|
|
105
|
+
let stdout;
|
|
106
|
+
try {
|
|
107
|
+
stdout = exec(bin, [
|
|
108
|
+
"config",
|
|
109
|
+
"--json",
|
|
110
|
+
root
|
|
111
|
+
], { timeoutMs });
|
|
112
|
+
} catch (err) {
|
|
113
|
+
return { unavailable: `\`expo config --json\` failed: ${firstLine(err)}` };
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
return {
|
|
117
|
+
config: JSON.parse(String(stdout)),
|
|
118
|
+
source: dynamic
|
|
119
|
+
};
|
|
120
|
+
} catch (err) {
|
|
121
|
+
return { unavailable: `\`expo config --json\` did not print JSON: ${firstLine(err)}` };
|
|
122
|
+
}
|
|
133
123
|
}
|
|
134
|
-
function
|
|
124
|
+
async function loadPlugin(projectRoot, reference, { requireFrom = null } = {}) {
|
|
125
|
+
const localRequire = requireFrom ? requireFrom(projectRoot) : createRequire(join(projectRoot, "package.json"));
|
|
126
|
+
const file = localRequire.resolve(reference);
|
|
127
|
+
let mod;
|
|
135
128
|
try {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
129
|
+
mod = localRequire(file);
|
|
130
|
+
} catch (err) {
|
|
131
|
+
if (err?.code !== "ERR_REQUIRE_ESM") throw err;
|
|
132
|
+
mod = await import(pathToFileURL(file).href);
|
|
140
133
|
}
|
|
134
|
+
const plugin = mod?.default ?? mod;
|
|
135
|
+
if (!isProviderPlugin(plugin)) throw new Error(`"${reference}" does not export resolveBuildCache and uploadBuildCache functions`);
|
|
136
|
+
return plugin;
|
|
141
137
|
}
|
|
142
|
-
function
|
|
143
|
-
if (
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
138
|
+
async function loadProjectProvider(projectRoot, { isExpo = true, run = null, requireFrom = null, timeoutMs = CONFIG_TIMEOUT_MS } = {}) {
|
|
139
|
+
if (!isExpo) return { none: true };
|
|
140
|
+
const read = readProjectConfig(projectRoot, {
|
|
141
|
+
run,
|
|
142
|
+
timeoutMs
|
|
143
|
+
});
|
|
144
|
+
if (read.unavailable) return { unavailable: read.unavailable };
|
|
145
|
+
const normalized = normalizeProvider(providerFromConfig(read.config));
|
|
146
|
+
if (!normalized) return { none: true };
|
|
147
|
+
if (normalized.invalid) return { unavailable: `buildCacheProvider is ${normalized.invalid}, which is not "eas" or { plugin: <module> }` };
|
|
148
|
+
if (normalized.reference === "@stim-cli/expo-build-cache") return { none: true };
|
|
149
|
+
let plugin;
|
|
150
|
+
try {
|
|
151
|
+
plugin = await loadPlugin(projectRoot, normalized.reference, { requireFrom });
|
|
152
|
+
} catch (err) {
|
|
153
|
+
const missing = err?.code === "MODULE_NOT_FOUND";
|
|
154
|
+
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)}` };
|
|
155
|
+
}
|
|
147
156
|
return {
|
|
148
|
-
|
|
149
|
-
|
|
157
|
+
provider: {
|
|
158
|
+
plugin,
|
|
159
|
+
options: normalized.options ?? {}
|
|
160
|
+
},
|
|
161
|
+
name: normalized.name,
|
|
162
|
+
owner: ownerFromConfig(read.config)
|
|
150
163
|
};
|
|
151
164
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
165
|
+
const WHOAMI_TIMEOUT_MS = 15e3;
|
|
166
|
+
const EAS_CLI_PACKAGE = "eas-cli";
|
|
167
|
+
const EAS_CLI_BIN = "eas";
|
|
168
|
+
function parseWhoami({ stdout = "", stderr = "", exitCode = 0 } = {}) {
|
|
169
|
+
const out = stripAnsi(stdout);
|
|
170
|
+
const combined = `${out}\n${stripAnsi(stderr)}`;
|
|
171
|
+
if (/^\s*Not logged in\s*$/m.test(combined)) return { loggedOut: true };
|
|
172
|
+
if (exitCode !== 0) return { unknown: firstLine(combined.trim() || `eas whoami exited ${exitCode}`) };
|
|
173
|
+
const lines = out.split("\n").map((line) => line.trim()).filter((line) => line !== "");
|
|
174
|
+
if (lines.length === 0) return { unknown: "eas whoami printed nothing" };
|
|
175
|
+
const raw = lines[0];
|
|
176
|
+
if (raw === void 0) return { unknown: "eas whoami printed nothing" };
|
|
177
|
+
const viaToken = /\(authenticated using EXPO_TOKEN\)\s*$/.test(raw);
|
|
178
|
+
const display = raw.replace(/\s*\(authenticated using EXPO_TOKEN\)\s*$/, "").trim();
|
|
179
|
+
const accounts = [];
|
|
180
|
+
for (const line of lines) {
|
|
181
|
+
const name = /^\u2022\s*(\S+)\s*\(Role:/.exec(line)?.[1];
|
|
182
|
+
if (name !== void 0) accounts.push(name);
|
|
183
|
+
}
|
|
184
|
+
if (accounts.length === 0 && !display.endsWith("(robot)") && display !== "robot") accounts.push(display);
|
|
185
|
+
return {
|
|
186
|
+
loggedIn: true,
|
|
187
|
+
account: display,
|
|
188
|
+
accounts: accounts.length ? accounts : null,
|
|
189
|
+
viaToken
|
|
190
|
+
};
|
|
178
191
|
}
|
|
179
|
-
function
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const records = readParked(platform, { config: cfg });
|
|
184
|
-
const record = records.find((candidate) => candidate.udid === udid);
|
|
185
|
-
if (!record || parseDeletionClaim(record.deletionClaim)?.token !== token) return;
|
|
186
|
-
const restored = { ...record };
|
|
187
|
-
delete restored.deletionClaim;
|
|
188
|
-
writeParked(cfg, platform, records.map((candidate) => candidate.udid === udid ? restored : candidate));
|
|
189
|
-
saveConfig(cfg);
|
|
190
|
-
});
|
|
192
|
+
function isEasAuthFailureText(text) {
|
|
193
|
+
const t = stripAnsi(text || "");
|
|
194
|
+
if (t.trim() === "") return false;
|
|
195
|
+
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);
|
|
191
196
|
}
|
|
192
|
-
function
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
try {
|
|
199
|
-
clearParkedRemovalClaim(platform, udid, claim.token);
|
|
200
|
-
} catch {}
|
|
201
|
-
throw error;
|
|
202
|
-
}
|
|
203
|
-
return withConfigLock(() => {
|
|
204
|
-
const cfg = loadConfig();
|
|
205
|
-
if (!cfg) return null;
|
|
206
|
-
const records = readParked(platform, { config: cfg });
|
|
207
|
-
if (parseDeletionClaim(records.find((candidate) => candidate.udid === udid)?.deletionClaim)?.token !== claim.token) return null;
|
|
208
|
-
writeParked(cfg, platform, records.filter((candidate) => candidate.udid !== udid));
|
|
209
|
-
saveConfig(cfg);
|
|
210
|
-
return claim.record;
|
|
211
|
-
});
|
|
197
|
+
function easAuthNote(status) {
|
|
198
|
+
const { code, account, owner, reason, phase = "resolve" } = status || {};
|
|
199
|
+
const because = reason ? ` (${reason})` : "";
|
|
200
|
+
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");
|
|
201
|
+
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`;
|
|
202
|
+
return null;
|
|
212
203
|
}
|
|
213
|
-
function
|
|
214
|
-
|
|
204
|
+
function resolveEasCliBin(projectRoot, { lookupPath = null, timeoutMs = 5e3 } = {}) {
|
|
205
|
+
const fromPackage = expoBinFromPackage(resolvePackageJson(projectRoot, EAS_CLI_PACKAGE), EAS_CLI_BIN);
|
|
206
|
+
if (fromPackage) return {
|
|
207
|
+
file: fromPackage,
|
|
208
|
+
source: "project"
|
|
209
|
+
};
|
|
210
|
+
const shim = findBinUpward(projectRoot, EAS_CLI_BIN);
|
|
211
|
+
if (shim) return {
|
|
212
|
+
file: shim,
|
|
213
|
+
source: "project"
|
|
214
|
+
};
|
|
215
|
+
const onPath = lookupPath ? lookupPath() : getExecutor().runQuiet(`command -v ${EAS_CLI_BIN}`, { timeoutMs });
|
|
216
|
+
const file = (String(onPath || "").split("\n")[0] ?? "").trim();
|
|
217
|
+
return file ? {
|
|
218
|
+
file,
|
|
219
|
+
source: "path"
|
|
220
|
+
} : null;
|
|
215
221
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
} : {
|
|
230
|
-
status: "skipped",
|
|
231
|
-
kind: "not-parked",
|
|
232
|
-
reason: "emulator is no longer parked"
|
|
233
|
-
};
|
|
234
|
-
} catch (error) {
|
|
235
|
-
return {
|
|
236
|
-
status: "failed",
|
|
237
|
-
reason: String(error?.message || error)
|
|
238
|
-
};
|
|
239
|
-
}
|
|
222
|
+
const easAuthCache = /* @__PURE__ */ new Map();
|
|
223
|
+
function checkEasAuth({ projectRoot, owner = null, run = null, resolveBin = null, timeoutMs = WHOAMI_TIMEOUT_MS, cache = easAuthCache }) {
|
|
224
|
+
const key = `${projectRoot}::${owner || ""}`;
|
|
225
|
+
if (cache?.has(key)) return cache.get(key);
|
|
226
|
+
const answer = probeEasAuth({
|
|
227
|
+
projectRoot,
|
|
228
|
+
owner,
|
|
229
|
+
run,
|
|
230
|
+
resolveBin,
|
|
231
|
+
timeoutMs
|
|
232
|
+
});
|
|
233
|
+
cache?.set(key, answer);
|
|
234
|
+
return answer;
|
|
240
235
|
}
|
|
241
|
-
function
|
|
236
|
+
function probeEasAuth({ projectRoot, owner, run, resolveBin, timeoutMs }) {
|
|
237
|
+
const bin = (resolveBin || resolveEasCliBin)(projectRoot);
|
|
238
|
+
if (!bin) return {
|
|
239
|
+
failed: true,
|
|
240
|
+
code: "no-cli",
|
|
241
|
+
reason: "no `eas` executable is resolvable from this project or on PATH",
|
|
242
|
+
remedy: "Install eas-cli (`npm i -g eas-cli`, or as a project devDependency), then run `eas login`."
|
|
243
|
+
};
|
|
244
|
+
const exec = run || ((file, args, opts) => getExecutor().runFile(file, args, opts));
|
|
245
|
+
let result;
|
|
242
246
|
try {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
kind: "not-parked",
|
|
247
|
-
reason: "simulator is no longer parked"
|
|
248
|
-
};
|
|
249
|
-
return {
|
|
250
|
-
status: "torn-down",
|
|
251
|
-
label: label ?? removed.name
|
|
247
|
+
result = {
|
|
248
|
+
stdout: String(exec(bin.file, ["whoami"], { timeoutMs }) ?? ""),
|
|
249
|
+
exitCode: 0
|
|
252
250
|
};
|
|
253
|
-
} catch (
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
251
|
+
} catch (e) {
|
|
252
|
+
const err = e;
|
|
253
|
+
if (err?.code === "ETIMEDOUT" || err?.signal === "SIGTERM" || err?.killed) return { unknown: `eas whoami timed out after ${timeoutMs}ms` };
|
|
254
|
+
result = {
|
|
255
|
+
stdout: String(err?.stdout ?? ""),
|
|
256
|
+
stderr: String(err?.stderr ?? err?.message ?? ""),
|
|
257
|
+
exitCode: typeof err?.status === "number" ? err.status : 1
|
|
257
258
|
};
|
|
258
259
|
}
|
|
260
|
+
const parsed = parseWhoami(result);
|
|
261
|
+
if (parsed.loggedOut) return {
|
|
262
|
+
failed: true,
|
|
263
|
+
code: "logged-out",
|
|
264
|
+
reason: "eas whoami says \"Not logged in\"",
|
|
265
|
+
remedy: "Run `eas login` (or set EXPO_TOKEN)."
|
|
266
|
+
};
|
|
267
|
+
if (parsed.unknown) return { unknown: parsed.unknown };
|
|
268
|
+
if (owner && Array.isArray(parsed.accounts) && !parsed.accounts.includes(owner)) return {
|
|
269
|
+
failed: true,
|
|
270
|
+
code: "wrong-account",
|
|
271
|
+
account: parsed.account,
|
|
272
|
+
accounts: parsed.accounts,
|
|
273
|
+
owner,
|
|
274
|
+
reason: `eas is authenticated as ${parsed.account} (accounts: ${parsed.accounts.join(", ")}), which does not include this project's owner ${owner}`,
|
|
275
|
+
remedy: `Run \`eas login\` as a member of ${owner}, or set EXPO_TOKEN to a token for it.`
|
|
276
|
+
};
|
|
277
|
+
return {
|
|
278
|
+
ok: true,
|
|
279
|
+
account: parsed.account,
|
|
280
|
+
accounts: parsed.accounts,
|
|
281
|
+
viaToken: parsed.viaToken,
|
|
282
|
+
source: bin.source
|
|
283
|
+
};
|
|
259
284
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
if (!sim.dataPath) throw new Error(`simulator ${udid} did not report a data path for app cleanup`);
|
|
270
|
-
const container = findAppDataContainer(sim.dataPath, park.bundleId);
|
|
271
|
-
if (container) clearAppDataContainer(container);
|
|
285
|
+
const capturing = [];
|
|
286
|
+
const abandoned = /* @__PURE__ */ new Set();
|
|
287
|
+
let patched = null;
|
|
288
|
+
const PROVIDER_CONTEXT = new AsyncLocalStorage();
|
|
289
|
+
function uploadDestination(lines) {
|
|
290
|
+
const text = (lines || []).map(stripAnsi);
|
|
291
|
+
for (const line of text) {
|
|
292
|
+
const url = /(https?:\/\/[^\s'"]+)/.exec(line)?.[1];
|
|
293
|
+
if (url !== void 0) return url.replace(/[.,)\]]+$/, "");
|
|
272
294
|
}
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
295
|
+
for (const line of text) {
|
|
296
|
+
const slug = /\bto\s+(@?[\w.-]+\/[\w.-]+)/i.exec(line)?.[1];
|
|
297
|
+
if (slug !== void 0) return slug.replace(/[.,)\]]+$/, "");
|
|
298
|
+
}
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
function providerNote(text) {
|
|
302
|
+
return `${"cache".padEnd(11)} ${text}`;
|
|
303
|
+
}
|
|
304
|
+
function currentFrame() {
|
|
305
|
+
const store = PROVIDER_CONTEXT.getStore();
|
|
306
|
+
return store && (capturing.includes(store) || abandoned.has(store)) ? store : null;
|
|
307
|
+
}
|
|
308
|
+
function install() {
|
|
309
|
+
if (patched && process.stdout.write === patched.patchedWrite) return;
|
|
310
|
+
const stdout = process.stdout;
|
|
311
|
+
const write = stdout.write.bind(stdout);
|
|
312
|
+
const log = console.log;
|
|
313
|
+
const passthrough = write;
|
|
314
|
+
const patchedWrite = ((chunk, encoding, callback) => {
|
|
315
|
+
const frame = currentFrame();
|
|
316
|
+
if (!frame) return passthrough(chunk, encoding, callback);
|
|
317
|
+
const cb = typeof encoding === "function" ? encoding : callback;
|
|
318
|
+
absorb(frame, typeof chunk === "string" ? chunk : String(chunk));
|
|
319
|
+
if (typeof cb === "function") cb();
|
|
320
|
+
return true;
|
|
276
321
|
});
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
deviceTypeIdentifier: sim.deviceTypeIdentifier,
|
|
282
|
-
runtimeIdentifier: sim.runtime,
|
|
283
|
-
parkedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
284
|
-
simslimManaged: Boolean(park.simslimManaged),
|
|
285
|
-
...park.bundleId ? { bundleId: park.bundleId } : {},
|
|
286
|
-
...park.cacheKey ? { cacheKey: park.cacheKey } : {}
|
|
322
|
+
const patchedLog = (...args) => {
|
|
323
|
+
const frame = currentFrame();
|
|
324
|
+
if (!frame) return log(...args);
|
|
325
|
+
absorb(frame, `${format(...args)}\n`);
|
|
287
326
|
};
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
327
|
+
stdout.write = patchedWrite;
|
|
328
|
+
console.log = patchedLog;
|
|
329
|
+
patched = {
|
|
330
|
+
stdout,
|
|
331
|
+
write,
|
|
332
|
+
log,
|
|
333
|
+
patchedWrite,
|
|
334
|
+
patchedLog
|
|
296
335
|
};
|
|
297
336
|
}
|
|
298
|
-
function
|
|
299
|
-
|
|
337
|
+
function uninstall() {
|
|
338
|
+
if (!patched || capturing.length || abandoned.size) return;
|
|
339
|
+
if (patched.stdout.write === patched.patchedWrite) patched.stdout.write = patched.write;
|
|
340
|
+
if (console.log === patched.patchedLog) console.log = patched.log;
|
|
341
|
+
patched = null;
|
|
342
|
+
}
|
|
343
|
+
function absorb(frame, chunk) {
|
|
300
344
|
try {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
const sim = resolved.sim;
|
|
310
|
-
if (del && park && park.max > 0) try {
|
|
311
|
-
const { record, evicted } = parkOwnedIosSim(udid, park);
|
|
312
|
-
const removed = [];
|
|
313
|
-
const failures = [];
|
|
314
|
-
for (const entry of evicted) {
|
|
315
|
-
const result = teardownParkedIosSim(entry.udid, { label: entry.name });
|
|
316
|
-
if (result.status === "torn-down") removed.push({
|
|
317
|
-
udid: entry.udid,
|
|
318
|
-
name: entry.name
|
|
319
|
-
});
|
|
320
|
-
else if (result.status === "failed") failures.push(`could not delete evicted ${entry.name} (${entry.udid}): ${result.reason}`);
|
|
321
|
-
}
|
|
322
|
-
return {
|
|
323
|
-
status: "torn-down",
|
|
324
|
-
label: label ?? sim.name ?? udid,
|
|
325
|
-
parked: {
|
|
326
|
-
udid: record.udid,
|
|
327
|
-
name: record.name
|
|
328
|
-
},
|
|
329
|
-
evicted: removed,
|
|
330
|
-
...failures.length ? { evictionFailures: failures } : {}
|
|
331
|
-
};
|
|
332
|
-
} catch (e) {
|
|
333
|
-
parkFallback = String(e?.message || e);
|
|
334
|
-
}
|
|
335
|
-
if (del) deleteIosSim(udid);
|
|
336
|
-
return {
|
|
337
|
-
status: "torn-down",
|
|
338
|
-
label: label ?? sim.name ?? udid,
|
|
339
|
-
...parkFallback ? { parkFallback } : {}
|
|
340
|
-
};
|
|
341
|
-
} catch (e) {
|
|
342
|
-
return {
|
|
343
|
-
status: "failed",
|
|
344
|
-
reason: String(e?.message || e),
|
|
345
|
-
...parkFallback ? { parkFallback } : {}
|
|
346
|
-
};
|
|
345
|
+
process.stderr.write(chunk);
|
|
346
|
+
} catch {}
|
|
347
|
+
frame.pending += chunk;
|
|
348
|
+
let index = frame.pending.indexOf("\n");
|
|
349
|
+
while (index !== -1) {
|
|
350
|
+
emitLine(frame, frame.pending.slice(0, index));
|
|
351
|
+
frame.pending = frame.pending.slice(index + 1);
|
|
352
|
+
index = frame.pending.indexOf("\n");
|
|
347
353
|
}
|
|
348
354
|
}
|
|
349
|
-
function
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
355
|
+
function emitLine(frame, raw) {
|
|
356
|
+
const line = stripAnsi(raw).trim();
|
|
357
|
+
if (line === "") return;
|
|
358
|
+
frame.lines.push(line);
|
|
359
|
+
const writer = frame.writer();
|
|
360
|
+
try {
|
|
361
|
+
writer?.write({
|
|
362
|
+
src: "build",
|
|
363
|
+
level: "debug",
|
|
364
|
+
event: "provider",
|
|
365
|
+
msg: line
|
|
366
|
+
});
|
|
367
|
+
} catch {}
|
|
368
|
+
frame.onLine?.(line);
|
|
369
|
+
}
|
|
370
|
+
function beginCapture({ logWriter = null, projectRoot = null, platform = null, onLine = null } = {}) {
|
|
371
|
+
let own = null;
|
|
372
|
+
const frame = {
|
|
373
|
+
lines: [],
|
|
374
|
+
pending: "",
|
|
375
|
+
onLine,
|
|
376
|
+
writer: () => {
|
|
377
|
+
if (logWriter) return logWriter;
|
|
378
|
+
if (own) return own;
|
|
379
|
+
if (!projectRoot || !platform) return null;
|
|
380
|
+
own = createNdjsonWriter(join(workspaceLogsDir(projectRoot), `build-${platform}.ndjson`));
|
|
381
|
+
return own;
|
|
382
|
+
},
|
|
383
|
+
close: () => {
|
|
384
|
+
own?.close?.();
|
|
385
|
+
own = null;
|
|
358
386
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
387
|
+
};
|
|
388
|
+
capturing.push(frame);
|
|
389
|
+
install();
|
|
390
|
+
return frame;
|
|
391
|
+
}
|
|
392
|
+
function endCapture(frame) {
|
|
393
|
+
if (!frame) return;
|
|
394
|
+
const at = capturing.lastIndexOf(frame);
|
|
395
|
+
if (at !== -1) capturing.splice(at, 1);
|
|
396
|
+
abandoned.delete(frame);
|
|
397
|
+
if (frame.pending !== "") {
|
|
398
|
+
const rest = frame.pending;
|
|
399
|
+
frame.pending = "";
|
|
400
|
+
emitLine(frame, rest);
|
|
401
|
+
}
|
|
402
|
+
frame.close();
|
|
403
|
+
uninstall();
|
|
404
|
+
}
|
|
405
|
+
function abandonCapture(frame, work) {
|
|
406
|
+
if (!frame) return;
|
|
407
|
+
const at = capturing.lastIndexOf(frame);
|
|
408
|
+
if (at !== -1) capturing.splice(at, 1);
|
|
409
|
+
abandoned.add(frame);
|
|
410
|
+
Promise.resolve(work).then(() => endCapture(frame), () => endCapture(frame));
|
|
411
|
+
}
|
|
412
|
+
async function resolveRemote({ provider, platform, projectRoot, fingerprintHash, runOptions = null, timeoutMs = RESOLVE_TIMEOUT_MS, logWriter = null } = {}) {
|
|
413
|
+
if (!provider?.plugin || !fingerprintHash) return null;
|
|
414
|
+
const opts = runOptions || runOptionsFor(platform);
|
|
415
|
+
const outcome = await withBudget(async () => {
|
|
416
|
+
const props = {
|
|
417
|
+
fingerprintHash: await providerFingerprint({
|
|
418
|
+
provider,
|
|
419
|
+
platform,
|
|
420
|
+
projectRoot,
|
|
421
|
+
runOptions: opts
|
|
422
|
+
}) ?? fingerprintHash,
|
|
423
|
+
platform,
|
|
424
|
+
runOptions: opts,
|
|
425
|
+
projectRoot
|
|
426
|
+
};
|
|
427
|
+
return typeof provider.plugin.resolveBuildCache === "function" ? provider.plugin.resolveBuildCache(props, provider.options) : provider.plugin.resolveRemoteBuildCache(props, provider.options);
|
|
428
|
+
}, timeoutMs, {
|
|
429
|
+
logWriter,
|
|
430
|
+
projectRoot,
|
|
431
|
+
platform
|
|
432
|
+
});
|
|
433
|
+
if (outcome.timedOut) return { timedOut: true };
|
|
434
|
+
if (outcome.error) return { failed: firstLine(outcome.error) };
|
|
435
|
+
const appPath = typeof outcome.value === "string" ? outcome.value.trim() : "";
|
|
436
|
+
if (!appPath) return null;
|
|
437
|
+
if (!existsSync(appPath)) return { failed: `returned ${appPath}, which does not exist` };
|
|
438
|
+
return { appPath };
|
|
439
|
+
}
|
|
440
|
+
async function uploadRemote({ provider, platform, projectRoot, fingerprintHash, buildPath, runOptions = null, timeoutMs = UPLOAD_TIMEOUT_MS, logWriter = null, note = (line) => console.error(line) } = {}) {
|
|
441
|
+
if (!provider?.plugin || !fingerprintHash || !buildPath) return { skipped: true };
|
|
442
|
+
const opts = runOptions || runOptionsFor(platform);
|
|
443
|
+
let announced = false;
|
|
444
|
+
const onLine = (line) => {
|
|
445
|
+
if (announced) return;
|
|
446
|
+
const dest = uploadDestination([line]);
|
|
447
|
+
if (!dest) return;
|
|
448
|
+
announced = true;
|
|
449
|
+
note(providerNote(`uploading to ${dest}`));
|
|
450
|
+
};
|
|
451
|
+
const outcome = await withBudget(async () => {
|
|
452
|
+
const props = {
|
|
453
|
+
projectRoot,
|
|
454
|
+
platform,
|
|
455
|
+
fingerprintHash: await providerFingerprint({
|
|
456
|
+
provider,
|
|
457
|
+
platform,
|
|
458
|
+
projectRoot,
|
|
459
|
+
runOptions: opts
|
|
460
|
+
}) ?? fingerprintHash,
|
|
461
|
+
buildPath,
|
|
462
|
+
runOptions: opts
|
|
463
|
+
};
|
|
464
|
+
return typeof provider.plugin.uploadBuildCache === "function" ? provider.plugin.uploadBuildCache(props, provider.options) : provider.plugin.uploadRemoteBuildCache(props, provider.options);
|
|
465
|
+
}, timeoutMs, {
|
|
466
|
+
logWriter,
|
|
467
|
+
projectRoot,
|
|
468
|
+
platform,
|
|
469
|
+
onLine
|
|
364
470
|
});
|
|
365
|
-
|
|
366
|
-
if (
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
}
|
|
471
|
+
if (outcome.timedOut) return { timedOut: true };
|
|
472
|
+
if (outcome.error) return { failed: firstLine(outcome.error) };
|
|
473
|
+
const destination = uploadDestination(outcome.lines);
|
|
474
|
+
return destination ? {
|
|
475
|
+
uploaded: true,
|
|
476
|
+
destination
|
|
477
|
+
} : { uploaded: true };
|
|
372
478
|
}
|
|
373
|
-
function
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
if (candidate.name !== avdName) throw new Error(`AVD data does not match ${avdName}; it was kept.`);
|
|
385
|
-
removeOrphanedAvdDirectory(candidate, owner);
|
|
479
|
+
async function providerFingerprint({ provider, platform, projectRoot, runOptions }) {
|
|
480
|
+
if (typeof provider.plugin.calculateFingerprintHash !== "function") return null;
|
|
481
|
+
try {
|
|
482
|
+
const hash = await provider.plugin.calculateFingerprintHash({
|
|
483
|
+
projectRoot,
|
|
484
|
+
platform,
|
|
485
|
+
runOptions
|
|
486
|
+
}, provider.options);
|
|
487
|
+
return typeof hash === "string" && hash.trim() !== "" ? hash.trim() : null;
|
|
488
|
+
} catch {
|
|
489
|
+
return null;
|
|
386
490
|
}
|
|
387
|
-
return {
|
|
388
|
-
status: "torn-down",
|
|
389
|
-
label: avdName
|
|
390
|
-
};
|
|
391
491
|
}
|
|
392
|
-
function
|
|
393
|
-
let
|
|
492
|
+
async function withBudget(factory, ms, capture) {
|
|
493
|
+
let timer = null;
|
|
494
|
+
const timeout = new Promise((resolve) => {
|
|
495
|
+
timer = setTimeout(() => resolve(TIMED_OUT), ms);
|
|
496
|
+
timer?.unref?.();
|
|
497
|
+
});
|
|
498
|
+
const frame = beginCapture(capture || {});
|
|
499
|
+
const work = Promise.resolve().then(() => PROVIDER_CONTEXT.run(frame, factory)).then((value) => ({ value }), (error) => ({ error }));
|
|
394
500
|
try {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
if (resolved.notOwned) return {
|
|
402
|
-
status: "skipped",
|
|
403
|
-
kind: "not-owned",
|
|
404
|
-
reason: `AVD ${avdName} is not Stim-owned by name`
|
|
405
|
-
};
|
|
406
|
-
if (resolved.missing) return teardownUnregisteredAvd(avdName, del, owner, orphanedDirectory);
|
|
407
|
-
if (orphanedDirectory) return {
|
|
408
|
-
status: "skipped",
|
|
409
|
-
reason: "AVD registration appeared; its data was kept."
|
|
410
|
-
};
|
|
411
|
-
const serial = resolved.serial;
|
|
412
|
-
if (serial) waitForShutdown(avdName, (timeoutMs) => shutdownAndroidEmulator(serial, timeoutMs));
|
|
413
|
-
else assertStopped(avdName);
|
|
414
|
-
if (del) {
|
|
415
|
-
const current = resolveAvd(avdName);
|
|
416
|
-
if (current.notOwned) return {
|
|
417
|
-
status: "skipped",
|
|
418
|
-
kind: "not-owned",
|
|
419
|
-
reason: `AVD ${avdName} is not Stim-owned by name`
|
|
501
|
+
const settled = await Promise.race([work, timeout]);
|
|
502
|
+
if (settled === TIMED_OUT) {
|
|
503
|
+
abandonCapture(frame, work);
|
|
504
|
+
return {
|
|
505
|
+
timedOut: true,
|
|
506
|
+
lines: frame.lines
|
|
420
507
|
};
|
|
421
|
-
if (current.missing) return teardownUnregisteredAvd(avdName, del, owner);
|
|
422
|
-
if (current.serial) throw new Error(`Owned AVD ${avdName} started again before deletion.`);
|
|
423
|
-
assertStopped(avdName);
|
|
424
|
-
if (park && park.max > 0) try {
|
|
425
|
-
const systemImage = ownedAvdSystemImage(avdName);
|
|
426
|
-
if (!systemImage || !park.configuration || !ownedAvdMatchesConfiguration(avdName, park.configuration)) throw new Error("the AVD has no verified creation configuration");
|
|
427
|
-
const evicted = parkSim({
|
|
428
|
-
platform: "android",
|
|
429
|
-
projectPath: park.projectPath,
|
|
430
|
-
max: park.max,
|
|
431
|
-
record: {
|
|
432
|
-
udid: avdName,
|
|
433
|
-
name: avdName,
|
|
434
|
-
systemImage,
|
|
435
|
-
configuration: park.configuration,
|
|
436
|
-
parkedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
437
|
-
}
|
|
438
|
-
});
|
|
439
|
-
const removed = [];
|
|
440
|
-
const failures = [];
|
|
441
|
-
for (const entry of evicted) {
|
|
442
|
-
const result = teardownParkedAvd(entry.name);
|
|
443
|
-
if (result.status === "torn-down") removed.push({
|
|
444
|
-
udid: entry.name,
|
|
445
|
-
name: entry.name,
|
|
446
|
-
platform: "android"
|
|
447
|
-
});
|
|
448
|
-
else if (result.status === "failed") failures.push(`could not delete evicted ${entry.name}: ${result.reason}`);
|
|
449
|
-
}
|
|
450
|
-
return {
|
|
451
|
-
status: "torn-down",
|
|
452
|
-
label: avdName,
|
|
453
|
-
parked: {
|
|
454
|
-
udid: avdName,
|
|
455
|
-
name: avdName,
|
|
456
|
-
platform: "android"
|
|
457
|
-
},
|
|
458
|
-
evicted: removed,
|
|
459
|
-
...failures.length ? { evictionFailures: failures } : {}
|
|
460
|
-
};
|
|
461
|
-
} catch (error) {
|
|
462
|
-
parkFallback = String(error?.message || error);
|
|
463
|
-
}
|
|
464
|
-
const directory = ownedAvdDirectory(avdName);
|
|
465
|
-
const paths = avdStorageRoots().map((root) => join(root, `${avdName}.ini`));
|
|
466
|
-
if (directory) paths.push(directory);
|
|
467
|
-
deleteAvd(avdName);
|
|
468
|
-
const remaining = paths.filter(avdPathExists);
|
|
469
|
-
if (remaining.length) throw new Error(`AVD ${avdName} was only partially deleted; data remains at ${remaining.join(", ")}.`);
|
|
470
508
|
}
|
|
509
|
+
endCapture(frame);
|
|
471
510
|
return {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
serial: resolved.serial ?? null,
|
|
475
|
-
...parkFallback ? { parkFallback } : {}
|
|
476
|
-
};
|
|
477
|
-
} catch (e) {
|
|
478
|
-
return {
|
|
479
|
-
status: "failed",
|
|
480
|
-
reason: String(e?.message || e)
|
|
511
|
+
...settled,
|
|
512
|
+
lines: frame.lines
|
|
481
513
|
};
|
|
514
|
+
} catch (err) {
|
|
515
|
+
endCapture(frame);
|
|
516
|
+
throw err;
|
|
517
|
+
} finally {
|
|
518
|
+
if (timer) clearTimeout(timer);
|
|
482
519
|
}
|
|
483
520
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
const RESOLVE_TIMEOUT_MS = 3e4;
|
|
488
|
-
const UPLOAD_TIMEOUT_MS = 6e4;
|
|
489
|
-
const CONFIG_TIMEOUT_MS = 3e4;
|
|
490
|
-
const DYNAMIC_CONFIG_FILES = [
|
|
491
|
-
"app.config.ts",
|
|
492
|
-
"app.config.js",
|
|
493
|
-
"app.config.mjs",
|
|
494
|
-
"app.config.cjs"
|
|
495
|
-
];
|
|
496
|
-
const TIMED_OUT = Symbol("timed-out");
|
|
497
|
-
function dynamicConfigFile(root) {
|
|
498
|
-
for (const name of DYNAMIC_CONFIG_FILES) if (existsSync(join(root, name))) return name;
|
|
499
|
-
return null;
|
|
521
|
+
function firstLine(err) {
|
|
522
|
+
const line = (String(err?.message || err || "unknown error").trim().split("\n")[0] ?? "").trim();
|
|
523
|
+
return line.length > 200 ? `${line.slice(0, 197)}...` : line;
|
|
500
524
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
525
|
+
const POOL_SETTING_REMEDY = "Run `stim guide settings` for the device pool bounds and where it can be set.";
|
|
526
|
+
const MAX_SETTING = {
|
|
527
|
+
ios: {
|
|
528
|
+
key: "iosParkedMax",
|
|
529
|
+
env: "STIM_POOL_IOS_PARKED_MAX"
|
|
530
|
+
},
|
|
531
|
+
android: {
|
|
532
|
+
key: "androidParkedMax",
|
|
533
|
+
env: "STIM_POOL_ANDROID_PARKED_MAX"
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
function parseMax(raw, strings) {
|
|
537
|
+
const value = strings && typeof raw === "string" ? /^\d+$/.test(raw.trim()) ? Number(raw.trim()) : NaN : raw;
|
|
538
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) return null;
|
|
539
|
+
return value;
|
|
506
540
|
}
|
|
507
|
-
function
|
|
508
|
-
|
|
509
|
-
const
|
|
510
|
-
const
|
|
511
|
-
|
|
541
|
+
function parkedMaxSetting(platform, { config, env = process.env } = {}) {
|
|
542
|
+
const { key, env: envKey } = MAX_SETTING[platform];
|
|
543
|
+
const fromEnv = env[envKey];
|
|
544
|
+
const explicit = fromEnv !== void 0 && fromEnv !== "";
|
|
545
|
+
const pool = (config === void 0 ? loadConfig() : config)?.pool;
|
|
546
|
+
if (!explicit && pool !== void 0 && (pool === null || typeof pool !== "object" || Array.isArray(pool))) return {
|
|
547
|
+
max: 0,
|
|
548
|
+
error: "Invalid pool value. Expected an object with simulator and emulator bounds."
|
|
549
|
+
};
|
|
550
|
+
const fromConfig = pool !== null && typeof pool === "object" && !Array.isArray(pool) ? pool[key] : void 0;
|
|
551
|
+
const raw = explicit ? fromEnv : fromConfig;
|
|
552
|
+
if (raw === void 0) return {
|
|
553
|
+
max: env.STIM_HOME ? 0 : 3,
|
|
554
|
+
error: null
|
|
555
|
+
};
|
|
556
|
+
const parsed = parseMax(raw, explicit);
|
|
557
|
+
if (parsed === null) return {
|
|
558
|
+
max: 0,
|
|
559
|
+
error: `Invalid ${explicit ? envKey : `pool.${key}`} value ${JSON.stringify(raw)}. Expected a whole number of parked ${platform === "ios" ? "simulators" : "emulators"}, 0 or more.`
|
|
560
|
+
};
|
|
561
|
+
return {
|
|
562
|
+
max: explicit ? parsed : env.STIM_HOME ? 0 : parsed,
|
|
563
|
+
error: null
|
|
564
|
+
};
|
|
512
565
|
}
|
|
513
|
-
function
|
|
514
|
-
if (
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
566
|
+
function isParkedRecord(value, platform) {
|
|
567
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
568
|
+
const record = value;
|
|
569
|
+
return typeof record.udid === "string" && typeof record.name === "string" && record.name.startsWith("stim-") && typeof record.parkedAt === "string" && (platform === "android" ? record.udid === record.name && /^stim-[A-Za-z0-9._-]+$/.test(record.name) && typeof record.systemImage === "string" && typeof record.configuration === "string" : typeof record.deviceTypeIdentifier === "string" && typeof record.runtimeIdentifier === "string" && typeof record.simslimManaged === "boolean" && (record.bundleId === void 0 || typeof record.bundleId === "string") && (record.cacheKey === void 0 || typeof record.cacheKey === "string"));
|
|
570
|
+
}
|
|
571
|
+
function poolBlock(config) {
|
|
572
|
+
const raw = config?.parked;
|
|
573
|
+
const block = raw !== null && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
574
|
+
const read = (platform) => {
|
|
575
|
+
const list = block[platform];
|
|
576
|
+
return Array.isArray(list) ? list.filter((record) => isParkedRecord(record, platform)) : [];
|
|
577
|
+
};
|
|
578
|
+
return {
|
|
579
|
+
ios: read("ios"),
|
|
580
|
+
android: read("android")
|
|
519
581
|
};
|
|
520
|
-
if (typeof raw === "object") {
|
|
521
|
-
const obj = raw;
|
|
522
|
-
if (typeof obj.plugin === "string" && obj.plugin.trim() !== "") {
|
|
523
|
-
const reference = obj.plugin.trim();
|
|
524
|
-
return {
|
|
525
|
-
name: reference,
|
|
526
|
-
reference,
|
|
527
|
-
options: obj.options && typeof obj.options === "object" ? obj.options : {}
|
|
528
|
-
};
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
return { invalid: typeof raw === "string" ? `"${raw}"` : JSON.stringify(raw) };
|
|
532
582
|
}
|
|
533
|
-
function
|
|
534
|
-
return
|
|
583
|
+
function readParked(platform, { config } = {}) {
|
|
584
|
+
return poolBlock(config === void 0 ? loadConfig() : config)[platform];
|
|
535
585
|
}
|
|
536
|
-
function
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
return resolves && uploads;
|
|
586
|
+
function writeParked(config, platform, records) {
|
|
587
|
+
config.parked = {
|
|
588
|
+
...poolBlock(config),
|
|
589
|
+
[platform]: records
|
|
590
|
+
};
|
|
542
591
|
}
|
|
543
|
-
function
|
|
544
|
-
return
|
|
592
|
+
function oldestFirst(records) {
|
|
593
|
+
return records.toSorted((a, b) => String(a.parkedAt).localeCompare(String(b.parkedAt)));
|
|
545
594
|
}
|
|
546
|
-
function
|
|
547
|
-
|
|
548
|
-
stream.write("", () => exit(code));
|
|
549
|
-
} catch {
|
|
550
|
-
exit(code);
|
|
551
|
-
}
|
|
595
|
+
function selectParked(records, { deviceTypeIdentifier, runtimeIdentifier }) {
|
|
596
|
+
return oldestFirst(records.filter((r) => r.deletionClaim === void 0 && r.deviceTypeIdentifier === deviceTypeIdentifier && r.runtimeIdentifier === runtimeIdentifier));
|
|
552
597
|
}
|
|
553
|
-
function
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
};
|
|
566
|
-
} catch (err) {
|
|
567
|
-
return { unavailable: `app.json could not be parsed: ${firstLine(err)}` };
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
const bin = expoBinPath(root);
|
|
571
|
-
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)` };
|
|
572
|
-
const exec = run || ((file, args, opts) => getExecutor().runFile(file, args, opts));
|
|
573
|
-
let stdout;
|
|
574
|
-
try {
|
|
575
|
-
stdout = exec(bin, [
|
|
576
|
-
"config",
|
|
577
|
-
"--json",
|
|
578
|
-
root
|
|
579
|
-
], { timeoutMs });
|
|
580
|
-
} catch (err) {
|
|
581
|
-
return { unavailable: `\`expo config --json\` failed: ${firstLine(err)}` };
|
|
582
|
-
}
|
|
583
|
-
try {
|
|
584
|
-
return {
|
|
585
|
-
config: JSON.parse(String(stdout)),
|
|
586
|
-
source: dynamic
|
|
587
|
-
};
|
|
588
|
-
} catch (err) {
|
|
589
|
-
return { unavailable: `\`expo config --json\` did not print JSON: ${firstLine(err)}` };
|
|
590
|
-
}
|
|
598
|
+
function evictOverflow(records, max) {
|
|
599
|
+
if (records.length <= max) return {
|
|
600
|
+
keep: [...records],
|
|
601
|
+
evicted: []
|
|
602
|
+
};
|
|
603
|
+
const ordered = oldestFirst(records);
|
|
604
|
+
const evicted = ordered.slice(0, ordered.length - max);
|
|
605
|
+
const dropped = new Set(evicted.map((r) => r.udid));
|
|
606
|
+
return {
|
|
607
|
+
keep: records.filter((r) => !dropped.has(r.udid)),
|
|
608
|
+
evicted
|
|
609
|
+
};
|
|
591
610
|
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
611
|
+
function parkSim({ platform, projectPath, slot = "default", record, max }) {
|
|
612
|
+
return withConfigLock(() => {
|
|
613
|
+
const cfg = ensureConfig();
|
|
614
|
+
const current = deviceSlotPlatforms(cfg.projects[projectPath], slot)?.[platform];
|
|
615
|
+
const currentId = platform === "ios" ? current?.deviceUdid : current?.avdName;
|
|
616
|
+
if (!current?.owned || currentId !== record.udid) throw new Error(`The ${platform === "ios" ? "simulator" : "emulator"} assignment changed before parking.`);
|
|
617
|
+
const { keep, evicted } = evictOverflow([...readParked(platform, { config: cfg }).filter((r) => r.udid !== record.udid), record], max);
|
|
618
|
+
writeParked(cfg, platform, [...keep, ...evicted]);
|
|
619
|
+
const project = cfg.projects[projectPath];
|
|
620
|
+
if (project) removeSlotDevice(project, platform, slot);
|
|
621
|
+
saveConfig(cfg);
|
|
622
|
+
return evicted;
|
|
623
|
+
});
|
|
605
624
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
625
|
+
function adoptParked({ platform, projectPath, slot = "default", udid, device }) {
|
|
626
|
+
return withConfigLock(() => {
|
|
627
|
+
const cfg = ensureConfig();
|
|
628
|
+
const records = readParked(platform, { config: cfg });
|
|
629
|
+
const taken = records.find((r) => r.udid === udid);
|
|
630
|
+
if (!taken || taken.deletionClaim !== void 0) return null;
|
|
631
|
+
if (deviceSlotPlatforms(cfg.projects[projectPath], slot)?.[platform]) return null;
|
|
632
|
+
writeParked(cfg, platform, records.filter((r) => r.udid !== udid));
|
|
633
|
+
const project = cfg.projects[projectPath];
|
|
634
|
+
if (!project) throw new Error(`Project not registered: ${projectPath}`);
|
|
635
|
+
assignSlotDevice(project, platform, device, slot);
|
|
636
|
+
saveConfig(cfg);
|
|
637
|
+
return taken;
|
|
611
638
|
});
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
if (!normalized) return { none: true };
|
|
615
|
-
if (normalized.invalid) return { unavailable: `buildCacheProvider is ${normalized.invalid}, which is not "eas" or { plugin: <module> }` };
|
|
616
|
-
if (normalized.reference === "@stim-cli/expo-build-cache") return { none: true };
|
|
617
|
-
let plugin;
|
|
639
|
+
}
|
|
640
|
+
function isProcessAlive(pid) {
|
|
618
641
|
try {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
return
|
|
642
|
+
process.kill(pid, 0);
|
|
643
|
+
return true;
|
|
644
|
+
} catch (error) {
|
|
645
|
+
return error.code === "EPERM";
|
|
623
646
|
}
|
|
624
|
-
return {
|
|
625
|
-
provider: {
|
|
626
|
-
plugin,
|
|
627
|
-
options: normalized.options ?? {}
|
|
628
|
-
},
|
|
629
|
-
name: normalized.name,
|
|
630
|
-
owner: ownerFromConfig(read.config)
|
|
631
|
-
};
|
|
632
647
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
const
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
const combined = `${out}\n${stripAnsi(stderr)}`;
|
|
639
|
-
if (/^\s*Not logged in\s*$/m.test(combined)) return { loggedOut: true };
|
|
640
|
-
if (exitCode !== 0) return { unknown: firstLine(combined.trim() || `eas whoami exited ${exitCode}`) };
|
|
641
|
-
const lines = out.split("\n").map((line) => line.trim()).filter((line) => line !== "");
|
|
642
|
-
if (lines.length === 0) return { unknown: "eas whoami printed nothing" };
|
|
643
|
-
const raw = lines[0];
|
|
644
|
-
if (raw === void 0) return { unknown: "eas whoami printed nothing" };
|
|
645
|
-
const viaToken = /\(authenticated using EXPO_TOKEN\)\s*$/.test(raw);
|
|
646
|
-
const display = raw.replace(/\s*\(authenticated using EXPO_TOKEN\)\s*$/, "").trim();
|
|
647
|
-
const accounts = [];
|
|
648
|
-
for (const line of lines) {
|
|
649
|
-
const name = /^\u2022\s*(\S+)\s*\(Role:/.exec(line)?.[1];
|
|
650
|
-
if (name !== void 0) accounts.push(name);
|
|
651
|
-
}
|
|
652
|
-
if (accounts.length === 0 && !display.endsWith("(robot)") && display !== "robot") accounts.push(display);
|
|
648
|
+
function parseDeletionClaim(value) {
|
|
649
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return null;
|
|
650
|
+
const claim = value;
|
|
651
|
+
if (!Number.isSafeInteger(claim.pid) || claim.pid <= 0) return null;
|
|
652
|
+
if (typeof claim.token !== "string" || claim.token.length === 0) return null;
|
|
653
653
|
return {
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
accounts: accounts.length ? accounts : null,
|
|
657
|
-
viaToken
|
|
654
|
+
pid: claim.pid,
|
|
655
|
+
token: claim.token
|
|
658
656
|
};
|
|
659
657
|
}
|
|
660
|
-
function
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
658
|
+
function claimParkedRemoval(platform, udid) {
|
|
659
|
+
return withConfigLock(() => {
|
|
660
|
+
const cfg = loadConfig();
|
|
661
|
+
if (!cfg) return null;
|
|
662
|
+
const records = readParked(platform, { config: cfg });
|
|
663
|
+
const record = records.find((candidate) => candidate.udid === udid);
|
|
664
|
+
if (!record) return null;
|
|
665
|
+
const existingClaim = parseDeletionClaim(record.deletionClaim);
|
|
666
|
+
if (record.deletionClaim !== void 0 && (!existingClaim || isProcessAlive(existingClaim.pid))) return null;
|
|
667
|
+
const token = randomUUID();
|
|
668
|
+
const claimedRecord = {
|
|
669
|
+
...record,
|
|
670
|
+
deletionClaim: {
|
|
671
|
+
pid: process.pid,
|
|
672
|
+
token
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
writeParked(cfg, platform, records.map((candidate) => candidate.udid === udid ? claimedRecord : candidate));
|
|
676
|
+
saveConfig(cfg);
|
|
677
|
+
const claimed = { ...record };
|
|
678
|
+
delete claimed.deletionClaim;
|
|
679
|
+
return {
|
|
680
|
+
record: claimed,
|
|
681
|
+
token
|
|
682
|
+
};
|
|
683
|
+
});
|
|
671
684
|
}
|
|
672
|
-
function
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
const file = (String(onPath || "").split("\n")[0] ?? "").trim();
|
|
685
|
-
return file ? {
|
|
686
|
-
file,
|
|
687
|
-
source: "path"
|
|
688
|
-
} : null;
|
|
685
|
+
function clearParkedRemovalClaim(platform, udid, token) {
|
|
686
|
+
withConfigLock(() => {
|
|
687
|
+
const cfg = loadConfig();
|
|
688
|
+
if (!cfg) return;
|
|
689
|
+
const records = readParked(platform, { config: cfg });
|
|
690
|
+
const record = records.find((candidate) => candidate.udid === udid);
|
|
691
|
+
if (!record || parseDeletionClaim(record.deletionClaim)?.token !== token) return;
|
|
692
|
+
const restored = { ...record };
|
|
693
|
+
delete restored.deletionClaim;
|
|
694
|
+
writeParked(cfg, platform, records.map((candidate) => candidate.udid === udid ? restored : candidate));
|
|
695
|
+
saveConfig(cfg);
|
|
696
|
+
});
|
|
689
697
|
}
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
698
|
+
function removeParkedAfter(platform, udid, beforeRemove) {
|
|
699
|
+
const claim = claimParkedRemoval(platform, udid);
|
|
700
|
+
if (!claim) return null;
|
|
701
|
+
try {
|
|
702
|
+
beforeRemove(claim.record);
|
|
703
|
+
} catch (error) {
|
|
704
|
+
try {
|
|
705
|
+
clearParkedRemovalClaim(platform, udid, claim.token);
|
|
706
|
+
} catch {}
|
|
707
|
+
throw error;
|
|
708
|
+
}
|
|
709
|
+
return withConfigLock(() => {
|
|
710
|
+
const cfg = loadConfig();
|
|
711
|
+
if (!cfg) return null;
|
|
712
|
+
const records = readParked(platform, { config: cfg });
|
|
713
|
+
if (parseDeletionClaim(records.find((candidate) => candidate.udid === udid)?.deletionClaim)?.token !== claim.token) return null;
|
|
714
|
+
writeParked(cfg, platform, records.filter((candidate) => candidate.udid !== udid));
|
|
715
|
+
saveConfig(cfg);
|
|
716
|
+
return claim.record;
|
|
700
717
|
});
|
|
701
|
-
cache?.set(key, answer);
|
|
702
|
-
return answer;
|
|
703
718
|
}
|
|
704
|
-
function
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
remedy: "Install eas-cli (`npm i -g eas-cli`, or as a project devDependency), then run `eas login`."
|
|
711
|
-
};
|
|
712
|
-
const exec = run || ((file, args, opts) => getExecutor().runFile(file, args, opts));
|
|
713
|
-
let result;
|
|
719
|
+
function dropParked(platform, udid) {
|
|
720
|
+
return removeParkedAfter(platform, udid, () => {}) !== null;
|
|
721
|
+
}
|
|
722
|
+
//#endregion
|
|
723
|
+
//#region src/teardown.ts
|
|
724
|
+
function teardownParkedAvd(avdName) {
|
|
714
725
|
try {
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
726
|
+
return removeParkedAfter("android", avdName, () => {
|
|
727
|
+
const result = teardownOwnedAvd(avdName, {
|
|
728
|
+
del: true,
|
|
729
|
+
owner: { pool: true }
|
|
730
|
+
});
|
|
731
|
+
if (result.status !== "torn-down" && result.status !== "missing") throw new Error(result.reason);
|
|
732
|
+
}) ? {
|
|
733
|
+
status: "torn-down",
|
|
734
|
+
label: avdName
|
|
735
|
+
} : {
|
|
736
|
+
status: "skipped",
|
|
737
|
+
kind: "not-parked",
|
|
738
|
+
reason: "emulator is no longer parked"
|
|
718
739
|
};
|
|
719
|
-
} catch (
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
stdout: String(err?.stdout ?? ""),
|
|
724
|
-
stderr: String(err?.stderr ?? err?.message ?? ""),
|
|
725
|
-
exitCode: typeof err?.status === "number" ? err.status : 1
|
|
740
|
+
} catch (error) {
|
|
741
|
+
return {
|
|
742
|
+
status: "failed",
|
|
743
|
+
reason: String(error?.message || error)
|
|
726
744
|
};
|
|
727
745
|
}
|
|
728
|
-
const parsed = parseWhoami(result);
|
|
729
|
-
if (parsed.loggedOut) return {
|
|
730
|
-
failed: true,
|
|
731
|
-
code: "logged-out",
|
|
732
|
-
reason: "eas whoami says \"Not logged in\"",
|
|
733
|
-
remedy: "Run `eas login` (or set EXPO_TOKEN)."
|
|
734
|
-
};
|
|
735
|
-
if (parsed.unknown) return { unknown: parsed.unknown };
|
|
736
|
-
if (owner && Array.isArray(parsed.accounts) && !parsed.accounts.includes(owner)) return {
|
|
737
|
-
failed: true,
|
|
738
|
-
code: "wrong-account",
|
|
739
|
-
account: parsed.account,
|
|
740
|
-
accounts: parsed.accounts,
|
|
741
|
-
owner,
|
|
742
|
-
reason: `eas is authenticated as ${parsed.account} (accounts: ${parsed.accounts.join(", ")}), which does not include this project's owner ${owner}`,
|
|
743
|
-
remedy: `Run \`eas login\` as a member of ${owner}, or set EXPO_TOKEN to a token for it.`
|
|
744
|
-
};
|
|
745
|
-
return {
|
|
746
|
-
ok: true,
|
|
747
|
-
account: parsed.account,
|
|
748
|
-
accounts: parsed.accounts,
|
|
749
|
-
viaToken: parsed.viaToken,
|
|
750
|
-
source: bin.source
|
|
751
|
-
};
|
|
752
746
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
747
|
+
function teardownParkedIosSim(udid, { label, deleteSim = deleteParkedIosSim } = {}) {
|
|
748
|
+
try {
|
|
749
|
+
const removed = removeParkedAfter("ios", udid, () => deleteSim(udid));
|
|
750
|
+
if (!removed) return {
|
|
751
|
+
status: "skipped",
|
|
752
|
+
kind: "not-parked",
|
|
753
|
+
reason: "simulator is no longer parked"
|
|
754
|
+
};
|
|
755
|
+
return {
|
|
756
|
+
status: "torn-down",
|
|
757
|
+
label: label ?? removed.name
|
|
758
|
+
};
|
|
759
|
+
} catch (error) {
|
|
760
|
+
return {
|
|
761
|
+
status: "failed",
|
|
762
|
+
reason: String(error?.message || error)
|
|
763
|
+
};
|
|
766
764
|
}
|
|
767
|
-
return null;
|
|
768
|
-
}
|
|
769
|
-
function providerNote(text) {
|
|
770
|
-
return `${"cache".padEnd(11)} ${text}`;
|
|
771
|
-
}
|
|
772
|
-
function currentFrame() {
|
|
773
|
-
const store = PROVIDER_CONTEXT.getStore();
|
|
774
|
-
return store && (capturing.includes(store) || abandoned.has(store)) ? store : null;
|
|
775
765
|
}
|
|
776
|
-
function
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
const
|
|
781
|
-
|
|
782
|
-
const
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
if (
|
|
788
|
-
|
|
766
|
+
function parkOwnedIosSim(udid, park) {
|
|
767
|
+
const resolved = resolveOwnedIosSim(udid);
|
|
768
|
+
if (resolved.missing) throw new Error(`simulator ${udid} disappeared after shutdown`);
|
|
769
|
+
if (resolved.notOwned) throw new Error(`simulator ${udid} is now named ${JSON.stringify(resolved.notOwned)}`);
|
|
770
|
+
const sim = resolved.sim;
|
|
771
|
+
if (sim.state !== "Shutdown") throw new Error(`simulator ${udid} is still ${sim.state} after shutdown`);
|
|
772
|
+
const model = listIosDeviceTypes().find((d) => d.identifier === sim.deviceTypeIdentifier)?.name ?? null;
|
|
773
|
+
const runtime = parseRuntimeVersion(sim.runtime);
|
|
774
|
+
if (park.bundleId) {
|
|
775
|
+
if (!sim.dataPath) throw new Error(`simulator ${udid} did not report a data path for app cleanup`);
|
|
776
|
+
const container = findAppDataContainer(sim.dataPath, park.bundleId);
|
|
777
|
+
if (container) clearAppDataContainer(container);
|
|
778
|
+
}
|
|
779
|
+
const name = parkedSimName(sim.udid, {
|
|
780
|
+
model,
|
|
781
|
+
runtime
|
|
789
782
|
});
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
783
|
+
renameIosSim(sim.udid, name);
|
|
784
|
+
const record = {
|
|
785
|
+
udid: sim.udid,
|
|
786
|
+
name,
|
|
787
|
+
deviceTypeIdentifier: sim.deviceTypeIdentifier,
|
|
788
|
+
runtimeIdentifier: sim.runtime,
|
|
789
|
+
parkedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
790
|
+
simslimManaged: Boolean(park.simslimManaged),
|
|
791
|
+
...park.bundleId ? { bundleId: park.bundleId } : {},
|
|
792
|
+
...park.cacheKey ? { cacheKey: park.cacheKey } : {}
|
|
794
793
|
};
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
794
|
+
return {
|
|
795
|
+
record,
|
|
796
|
+
evicted: parkSim({
|
|
797
|
+
platform: "ios",
|
|
798
|
+
projectPath: park.projectPath,
|
|
799
|
+
slot: park.slot,
|
|
800
|
+
record,
|
|
801
|
+
max: park.max
|
|
802
|
+
})
|
|
803
803
|
};
|
|
804
804
|
}
|
|
805
|
-
function
|
|
806
|
-
|
|
807
|
-
if (patched.stdout.write === patched.patchedWrite) patched.stdout.write = patched.write;
|
|
808
|
-
if (console.log === patched.patchedLog) console.log = patched.log;
|
|
809
|
-
patched = null;
|
|
810
|
-
}
|
|
811
|
-
function absorb(frame, chunk) {
|
|
812
|
-
try {
|
|
813
|
-
process.stderr.write(chunk);
|
|
814
|
-
} catch {}
|
|
815
|
-
frame.pending += chunk;
|
|
816
|
-
let index = frame.pending.indexOf("\n");
|
|
817
|
-
while (index !== -1) {
|
|
818
|
-
emitLine(frame, frame.pending.slice(0, index));
|
|
819
|
-
frame.pending = frame.pending.slice(index + 1);
|
|
820
|
-
index = frame.pending.indexOf("\n");
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
function emitLine(frame, raw) {
|
|
824
|
-
const line = stripAnsi(raw).trim();
|
|
825
|
-
if (line === "") return;
|
|
826
|
-
frame.lines.push(line);
|
|
827
|
-
const writer = frame.writer();
|
|
805
|
+
function teardownOwnedIosSim(udid, { del = false, label, park } = {}) {
|
|
806
|
+
let parkFallback;
|
|
828
807
|
try {
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
808
|
+
const resolved = resolveOwnedIosSim(udid);
|
|
809
|
+
if (resolved.notOwned) return {
|
|
810
|
+
status: "skipped",
|
|
811
|
+
kind: "not-owned",
|
|
812
|
+
reason: `sim is now named "${resolved.notOwned}", not Stim-owned by name`
|
|
813
|
+
};
|
|
814
|
+
if (resolved.missing) return { status: "missing" };
|
|
815
|
+
shutdownIosSim(udid);
|
|
816
|
+
const sim = resolved.sim;
|
|
817
|
+
if (del && park && park.max > 0) try {
|
|
818
|
+
const { record, evicted } = parkOwnedIosSim(udid, park);
|
|
819
|
+
const removed = [];
|
|
820
|
+
const failures = [];
|
|
821
|
+
for (const entry of evicted) {
|
|
822
|
+
const result = teardownParkedIosSim(entry.udid, { label: entry.name });
|
|
823
|
+
if (result.status === "torn-down") removed.push({
|
|
824
|
+
udid: entry.udid,
|
|
825
|
+
name: entry.name
|
|
826
|
+
});
|
|
827
|
+
else if (result.status === "failed") failures.push(`could not delete evicted ${entry.name} (${entry.udid}): ${result.reason}`);
|
|
828
|
+
}
|
|
829
|
+
return {
|
|
830
|
+
status: "torn-down",
|
|
831
|
+
label: label ?? sim.name ?? udid,
|
|
832
|
+
parked: {
|
|
833
|
+
udid: record.udid,
|
|
834
|
+
name: record.name
|
|
835
|
+
},
|
|
836
|
+
evicted: removed,
|
|
837
|
+
...failures.length ? { evictionFailures: failures } : {}
|
|
838
|
+
};
|
|
839
|
+
} catch (e) {
|
|
840
|
+
parkFallback = String(e?.message || e);
|
|
854
841
|
}
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
frame.pending = "";
|
|
868
|
-
emitLine(frame, rest);
|
|
842
|
+
if (del) deleteIosSim(udid);
|
|
843
|
+
return {
|
|
844
|
+
status: "torn-down",
|
|
845
|
+
label: label ?? sim.name ?? udid,
|
|
846
|
+
...parkFallback ? { parkFallback } : {}
|
|
847
|
+
};
|
|
848
|
+
} catch (e) {
|
|
849
|
+
return {
|
|
850
|
+
status: "failed",
|
|
851
|
+
reason: String(e?.message || e),
|
|
852
|
+
...parkFallback ? { parkFallback } : {}
|
|
853
|
+
};
|
|
869
854
|
}
|
|
870
|
-
frame.close();
|
|
871
|
-
uninstall();
|
|
872
|
-
}
|
|
873
|
-
function abandonCapture(frame, work) {
|
|
874
|
-
if (!frame) return;
|
|
875
|
-
const at = capturing.lastIndexOf(frame);
|
|
876
|
-
if (at !== -1) capturing.splice(at, 1);
|
|
877
|
-
abandoned.add(frame);
|
|
878
|
-
Promise.resolve(work).then(() => endCapture(frame), () => endCapture(frame));
|
|
879
855
|
}
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
const
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
return typeof provider.plugin.resolveBuildCache === "function" ? provider.plugin.resolveBuildCache(props, provider.options) : provider.plugin.resolveRemoteBuildCache(props, provider.options);
|
|
896
|
-
}, timeoutMs, {
|
|
897
|
-
logWriter,
|
|
898
|
-
projectRoot,
|
|
899
|
-
platform
|
|
856
|
+
function removeOrphanedAvdDirectory(candidate, owner) {
|
|
857
|
+
const detached = withConfigLock(() => {
|
|
858
|
+
const current = listOrphanedAvdDirectories(candidate.name).find((entry) => entry.directory === candidate.directory);
|
|
859
|
+
if (!current || current.name !== candidate.name || current.dev !== candidate.dev || current.ino !== candidate.ino) throw new Error(`AVD data at ${candidate.directory} changed or is registered; it was kept.`);
|
|
860
|
+
const config = loadConfig();
|
|
861
|
+
if (!config) throw new Error("Cannot verify AVD references without a Stim config; the data was kept.");
|
|
862
|
+
for (const [projectPath, project] of Object.entries(config.projects)) for (const { platforms } of projectDeviceSlots(project)) {
|
|
863
|
+
if (platforms.android?.avdName !== candidate.name) continue;
|
|
864
|
+
if (!owner || !("projectPath" in owner) || owner.projectPath !== projectPath || !platforms.android.owned) throw new Error(`AVD ${candidate.name} is referenced by ${projectPath}; its data was kept.`);
|
|
865
|
+
}
|
|
866
|
+
if (readParked("android", { config }).some((entry) => entry.name === candidate.name) && !(owner && "pool" in owner)) throw new Error(`AVD ${candidate.name} is in the emulator pool; its data was kept.`);
|
|
867
|
+
assertOwnedAvdStopped(candidate.name, { resolveDirectory: () => candidate.directory });
|
|
868
|
+
const destination = join(dirname(candidate.directory), `stim-gc-${randomUUID()}.avd`);
|
|
869
|
+
renameSync(candidate.directory, destination);
|
|
870
|
+
return destination;
|
|
900
871
|
});
|
|
901
|
-
|
|
902
|
-
if (
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
872
|
+
const current = lstatSync(detached);
|
|
873
|
+
if (!current.isDirectory() || current.dev !== candidate.dev || current.ino !== candidate.ino) throw new Error(`Detached AVD data changed at ${detached}; it was kept.`);
|
|
874
|
+
try {
|
|
875
|
+
rmSync(detached, { recursive: true });
|
|
876
|
+
} catch (error) {
|
|
877
|
+
throw new Error(`Could not remove AVD data at ${detached}; retry gc --delete: ${error.message}`, { cause: error });
|
|
878
|
+
}
|
|
907
879
|
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
note(providerNote(`uploading to ${dest}`));
|
|
880
|
+
function teardownUnregisteredAvd(avdName, del, owner, surveyed) {
|
|
881
|
+
const candidates = surveyed ? [surveyed] : listOrphanedAvdDirectories(avdName);
|
|
882
|
+
if (candidates.length === 0) {
|
|
883
|
+
if (avdStorageRoots().some((root) => avdPathExists(join(root, `${avdName}.ini`)) || avdPathExists(join(root, `${avdName}.avd`)))) throw new Error(`AVD ${avdName} is not listed but its storage remains; it was kept.`);
|
|
884
|
+
return { status: "missing" };
|
|
885
|
+
}
|
|
886
|
+
if (!del) return {
|
|
887
|
+
status: "skipped",
|
|
888
|
+
reason: "AVD registration is missing; its data was kept."
|
|
918
889
|
};
|
|
919
|
-
const
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
platform,
|
|
923
|
-
fingerprintHash: await providerFingerprint({
|
|
924
|
-
provider,
|
|
925
|
-
platform,
|
|
926
|
-
projectRoot,
|
|
927
|
-
runOptions: opts
|
|
928
|
-
}) ?? fingerprintHash,
|
|
929
|
-
buildPath,
|
|
930
|
-
runOptions: opts
|
|
931
|
-
};
|
|
932
|
-
return typeof provider.plugin.uploadBuildCache === "function" ? provider.plugin.uploadBuildCache(props, provider.options) : provider.plugin.uploadRemoteBuildCache(props, provider.options);
|
|
933
|
-
}, timeoutMs, {
|
|
934
|
-
logWriter,
|
|
935
|
-
projectRoot,
|
|
936
|
-
platform,
|
|
937
|
-
onLine
|
|
938
|
-
});
|
|
939
|
-
if (outcome.timedOut) return { timedOut: true };
|
|
940
|
-
if (outcome.error) return { failed: firstLine(outcome.error) };
|
|
941
|
-
const destination = uploadDestination(outcome.lines);
|
|
942
|
-
return destination ? {
|
|
943
|
-
uploaded: true,
|
|
944
|
-
destination
|
|
945
|
-
} : { uploaded: true };
|
|
946
|
-
}
|
|
947
|
-
async function providerFingerprint({ provider, platform, projectRoot, runOptions }) {
|
|
948
|
-
if (typeof provider.plugin.calculateFingerprintHash !== "function") return null;
|
|
949
|
-
try {
|
|
950
|
-
const hash = await provider.plugin.calculateFingerprintHash({
|
|
951
|
-
projectRoot,
|
|
952
|
-
platform,
|
|
953
|
-
runOptions
|
|
954
|
-
}, provider.options);
|
|
955
|
-
return typeof hash === "string" && hash.trim() !== "" ? hash.trim() : null;
|
|
956
|
-
} catch {
|
|
957
|
-
return null;
|
|
890
|
+
for (const candidate of candidates) {
|
|
891
|
+
if (candidate.name !== avdName) throw new Error(`AVD data does not match ${avdName}; it was kept.`);
|
|
892
|
+
removeOrphanedAvdDirectory(candidate, owner);
|
|
958
893
|
}
|
|
894
|
+
return {
|
|
895
|
+
status: "torn-down",
|
|
896
|
+
label: avdName
|
|
897
|
+
};
|
|
959
898
|
}
|
|
960
|
-
|
|
961
|
-
let
|
|
962
|
-
const timeout = new Promise((resolve) => {
|
|
963
|
-
timer = setTimeout(() => resolve(TIMED_OUT), ms);
|
|
964
|
-
timer?.unref?.();
|
|
965
|
-
});
|
|
966
|
-
const frame = beginCapture(capture || {});
|
|
967
|
-
const work = Promise.resolve().then(() => PROVIDER_CONTEXT.run(frame, factory)).then((value) => ({ value }), (error) => ({ error }));
|
|
899
|
+
function teardownOwnedAvd(avdName, { del = false, park, owner, orphanedDirectory, waitForShutdown = waitForAndroidEmulatorShutdown, assertStopped = assertOwnedAvdStopped, resolveAvd = resolveOwnedAvdSerial } = {}) {
|
|
900
|
+
let parkFallback;
|
|
968
901
|
try {
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
902
|
+
if (!/^stim-[A-Za-z0-9._-]+$/.test(avdName)) return {
|
|
903
|
+
status: "skipped",
|
|
904
|
+
kind: "not-owned",
|
|
905
|
+
reason: `AVD ${avdName} is not Stim-owned by name`
|
|
906
|
+
};
|
|
907
|
+
const resolved = resolveAvd(avdName);
|
|
908
|
+
if (resolved.notOwned) return {
|
|
909
|
+
status: "skipped",
|
|
910
|
+
kind: "not-owned",
|
|
911
|
+
reason: `AVD ${avdName} is not Stim-owned by name`
|
|
912
|
+
};
|
|
913
|
+
if (resolved.missing) return teardownUnregisteredAvd(avdName, del, owner, orphanedDirectory);
|
|
914
|
+
if (orphanedDirectory) return {
|
|
915
|
+
status: "skipped",
|
|
916
|
+
reason: "AVD registration appeared; its data was kept."
|
|
917
|
+
};
|
|
918
|
+
const serial = resolved.serial;
|
|
919
|
+
if (serial) waitForShutdown(avdName, (timeoutMs) => shutdownAndroidEmulator(serial, timeoutMs));
|
|
920
|
+
else assertStopped(avdName);
|
|
921
|
+
if (del) {
|
|
922
|
+
const current = resolveAvd(avdName);
|
|
923
|
+
if (current.notOwned) return {
|
|
924
|
+
status: "skipped",
|
|
925
|
+
kind: "not-owned",
|
|
926
|
+
reason: `AVD ${avdName} is not Stim-owned by name`
|
|
975
927
|
};
|
|
928
|
+
if (current.missing) return teardownUnregisteredAvd(avdName, del, owner);
|
|
929
|
+
if (current.serial) throw new Error(`Owned AVD ${avdName} started again before deletion.`);
|
|
930
|
+
assertStopped(avdName);
|
|
931
|
+
if (park && park.max > 0) try {
|
|
932
|
+
const systemImage = ownedAvdSystemImage(avdName);
|
|
933
|
+
if (!systemImage || !park.configuration || !ownedAvdMatchesConfiguration(avdName, park.configuration)) throw new Error("the AVD has no verified creation configuration");
|
|
934
|
+
const evicted = parkSim({
|
|
935
|
+
platform: "android",
|
|
936
|
+
projectPath: park.projectPath,
|
|
937
|
+
slot: park.slot,
|
|
938
|
+
max: park.max,
|
|
939
|
+
record: {
|
|
940
|
+
udid: avdName,
|
|
941
|
+
name: avdName,
|
|
942
|
+
systemImage,
|
|
943
|
+
configuration: park.configuration,
|
|
944
|
+
parkedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
945
|
+
}
|
|
946
|
+
});
|
|
947
|
+
const removed = [];
|
|
948
|
+
const failures = [];
|
|
949
|
+
for (const entry of evicted) {
|
|
950
|
+
const result = teardownParkedAvd(entry.name);
|
|
951
|
+
if (result.status === "torn-down") removed.push({
|
|
952
|
+
udid: entry.name,
|
|
953
|
+
name: entry.name,
|
|
954
|
+
platform: "android"
|
|
955
|
+
});
|
|
956
|
+
else if (result.status === "failed") failures.push(`could not delete evicted ${entry.name}: ${result.reason}`);
|
|
957
|
+
}
|
|
958
|
+
return {
|
|
959
|
+
status: "torn-down",
|
|
960
|
+
label: avdName,
|
|
961
|
+
parked: {
|
|
962
|
+
udid: avdName,
|
|
963
|
+
name: avdName,
|
|
964
|
+
platform: "android"
|
|
965
|
+
},
|
|
966
|
+
evicted: removed,
|
|
967
|
+
...failures.length ? { evictionFailures: failures } : {}
|
|
968
|
+
};
|
|
969
|
+
} catch (error) {
|
|
970
|
+
parkFallback = String(error?.message || error);
|
|
971
|
+
}
|
|
972
|
+
const directory = ownedAvdDirectory(avdName);
|
|
973
|
+
const paths = avdStorageRoots().map((root) => join(root, `${avdName}.ini`));
|
|
974
|
+
if (directory) paths.push(directory);
|
|
975
|
+
deleteAvd(avdName);
|
|
976
|
+
const remaining = paths.filter(avdPathExists);
|
|
977
|
+
if (remaining.length) throw new Error(`AVD ${avdName} was only partially deleted; data remains at ${remaining.join(", ")}.`);
|
|
976
978
|
}
|
|
977
|
-
endCapture(frame);
|
|
978
979
|
return {
|
|
979
|
-
|
|
980
|
-
|
|
980
|
+
status: "torn-down",
|
|
981
|
+
label: avdName,
|
|
982
|
+
serial: resolved.serial ?? null,
|
|
983
|
+
...parkFallback ? { parkFallback } : {}
|
|
984
|
+
};
|
|
985
|
+
} catch (e) {
|
|
986
|
+
return {
|
|
987
|
+
status: "failed",
|
|
988
|
+
reason: String(e?.message || e)
|
|
981
989
|
};
|
|
982
|
-
} catch (err) {
|
|
983
|
-
endCapture(frame);
|
|
984
|
-
throw err;
|
|
985
|
-
} finally {
|
|
986
|
-
if (timer) clearTimeout(timer);
|
|
987
990
|
}
|
|
988
991
|
}
|
|
989
|
-
function firstLine(err) {
|
|
990
|
-
const line = (String(err?.message || err || "unknown error").trim().split("\n")[0] ?? "").trim();
|
|
991
|
-
return line.length > 200 ? `${line.slice(0, 197)}...` : line;
|
|
992
|
-
}
|
|
993
992
|
//#endregion
|
|
994
|
-
export {
|
|
993
|
+
export { uploadRemote as C, resolveRemote as S, isEasAuthFailureText as _, POOL_SETTING_REMEDY as a, providerFromConfig as b, parkedMaxSetting as c, RESOLVE_TIMEOUT_MS as d, UPLOAD_TIMEOUT_MS as f, exitAfterFlush as g, easAuthNote as h, teardownParkedIosSim as i, readParked as l, checkEasAuth as m, teardownOwnedIosSim as n, adoptParked as o, cacheLevel as p, teardownParkedAvd as r, dropParked as s, teardownOwnedAvd as t, selectParked as u, loadProjectProvider as v, resolveEasCliBin as x, ownerFromConfig as y };
|