stim 1.3.1 → 1.5.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 +16 -3
- package/dist/{android-hMj83VpN.mjs → android-DJWiTBkz.mjs} +65 -72
- package/dist/{android-cas-DXorPCi_.mjs → android-cas-CSS8gjR7.mjs} +15 -12
- package/dist/{android-CT-PkXUh.mjs → android-sobvO9ia.mjs} +197 -114
- package/dist/{app-install-DIp9riNx.mjs → app-install--PAocWvq.mjs} +2 -2
- package/dist/{build-slots-C4AouyNz.mjs → build-slots-CuPszJDu.mjs} +7 -8
- package/dist/{cache-manifest-CXR-Y8pH.mjs → cache-manifest-uoOKhgUD.mjs} +1 -1
- package/dist/cache-manifest.mjs +1 -1
- package/dist/cli.mjs +14 -13
- package/dist/collector-run.mjs +4 -4
- package/dist/{command-output-8wHMEde0.mjs → command-output-BuIb_ZOI.mjs} +1 -4
- package/dist/{config-CcefMY2Q.mjs → config-DMP7L7H6.mjs} +52 -52
- package/dist/dependency-state-CTL8A9p3.mjs +76 -0
- package/dist/{deps-0Yt0Inly.mjs → deps-BAGOnrc7.mjs} +1 -1
- package/dist/{dev-client-VfSZNLT4.mjs → dev-client-BoifdqkU.mjs} +12 -12
- package/dist/{device-Bbv45Z9Z.mjs → device-BUw4JBuH.mjs} +7 -7
- package/dist/{xcode-xnxyvJkD.mjs → device-DvKJ454A.mjs} +208 -967
- package/dist/{device-lease-CJwK7OZr.mjs → device-lease-r7yEQ1R8.mjs} +3 -3
- package/dist/{device-pool-CcBNTvM7.mjs → device-pool-DU4FQKp5.mjs} +4 -4
- package/dist/{device-remote-DAA0Zm6Z.mjs → device-remote-BTUqbl0i.mjs} +7 -6
- package/dist/{doctor-B4EITgQB.mjs → doctor-Di_B26uh.mjs} +396 -117
- package/dist/{error-diagnostics-DwhNBFDU.mjs → error-diagnostics-CTePWAzW.mjs} +7 -6
- package/dist/{gc-BRu62v4o.mjs → gc-C20dWw_8.mjs} +61 -27
- package/dist/{guide-D95E0u3i.mjs → guide-sAnoty2y.mjs} +151 -20
- package/dist/guide-status-BF6RsJJp.mjs +271 -0
- package/dist/{ios-DAju3TVy.mjs → ios-BdpkncL6.mjs} +2667 -2607
- package/dist/{ios-device-CWOM_S44.mjs → ios-device-DJOi9FR5.mjs} +1 -1
- package/dist/{logs-CAdwW6c2.mjs → logs-BfQdIhiF.mjs} +5 -5
- package/dist/metro-ato6ef3J.mjs +99 -0
- package/dist/named-ports-DH9SXd0f.mjs +137 -0
- package/dist/{ownership-8O073GIC.mjs → ownership-CD_XnpDu.mjs} +5 -5
- package/dist/{ownership-DXcol4oU.mjs → ownership-CVY9cqCr.mjs} +6 -6
- package/dist/ports-A6uTJSPj.mjs +39 -0
- package/dist/ports-BZprTJRa.mjs +90 -0
- package/dist/{project-fKmuIaMI.mjs → project-B_SDt4ip.mjs} +2 -2
- package/dist/{reclaim-CUmwucWG.mjs → reclaim-C_rB25f4.mjs} +20 -13
- package/dist/{reload-CWpXoOgN.mjs → reload-CYBJbJfI.mjs} +8 -8
- package/dist/rolldown-runtime-BsOwBuO_.mjs +30 -0
- package/dist/{server-bare-DEzPZLvf.mjs → server-bare-H3pAhbMs.mjs} +11 -35
- package/dist/server-expo-CSwUVYOa.mjs +2 -0
- package/dist/{server-expo-DizT4OMk.mjs → server-expo-Ds8_mfng.mjs} +6 -14
- package/dist/{settings-DEJf6_kJ.mjs → settings-kO2ZqIKy.mjs} +2 -2
- package/dist/{slot-launch-CZklomj6.mjs → slot-launch-BpHiHH6A.mjs} +4 -4
- package/dist/{start-B5G4NjiA.mjs → start-DiEfqEWk.mjs} +20 -22
- package/dist/state-CnWzvCWP.mjs +131 -0
- package/dist/{stats-CaaQr4yN.mjs → stats-DHrO0frH.mjs} +4 -4
- package/dist/{stats-BR-kwj0B.mjs → stats-Fo9ACIv7.mjs} +1 -1
- package/dist/{status-CI9clRSb.mjs → status-J5LAXpag.mjs} +15 -12
- package/dist/{stop-CjlgDk0a.mjs → stop-0jJNahwN.mjs} +15 -14
- package/dist/{stop-CQhffpmX.mjs → stop-CM1fejKW.mjs} +2 -2
- package/dist/supervisor-run.d.mts +7 -28
- package/dist/supervisor-run.mjs +20 -11
- package/dist/{teardown--gESs9ck.mjs → teardown-CAVcWeRD.mjs} +148 -109
- package/dist/{workspace-process-lock-IBj1Towr.mjs → workspace-process-lock-Bz19_R9R.mjs} +4 -4
- package/dist/workspace-state-WVwWw_hB.mjs +146 -0
- package/dist/{worktree-DtQGO9-s.mjs → worktree-CeUKDAQw.mjs} +38 -34
- package/dist/{worktree-Eq6TLH8N.mjs → worktree-DJnBbQZ9.mjs} +79 -22
- package/dist/xcode-BqDeRkpT.mjs +860 -0
- package/package.json +7 -7
- package/shim/expo-metro-config.cjs +11 -17
- package/dist/doctor-BueDfLYG.mjs +0 -530
- package/dist/metro-CORqXyTL.mjs +0 -184
- package/dist/ownership-claim-BtPtJVmq.mjs +0 -584
- package/dist/server-expo-44em5Pmg.mjs +0 -2
- package/dist/state-B3XA2wp2.mjs +0 -268
- package/shim/metro-cache-generation.cjs +0 -20
- package/shim/metro-cache-generation.d.cts +0 -5
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
import { t as getExecutor } from "./exec-CyylIdq9.mjs";
|
|
2
|
-
import { C as withConfigLock, E as
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { s as ownedDeviceLabel } from "./project-fKmuIaMI.mjs";
|
|
2
|
+
import { C as withConfigLock, E as ownership_claim_exports, F as deviceSlotPlatforms, L as projectDeviceSlots, T as acquireAvdClaim, _ as releaseAndroidConsolePort, b as setDevice, g as loadConfig, i as clearDevice, t as allConsolePortsAndSerials, u as getConfigDir, w as process_identity_exports, y as saveConfig } from "./config-DMP7L7H6.mjs";
|
|
3
|
+
import { g as workspaceId } from "./workspace-state-WVwWw_hB.mjs";
|
|
4
|
+
import { s as ownedDeviceLabel } from "./project-B_SDt4ip.mjs";
|
|
6
5
|
import { n as stripAnsi, r as waitForChild, t as createLineReader } from "./process-output-C7IlIzd7.mjs";
|
|
7
|
-
import { r as pidExists } from "./metro-
|
|
8
|
-
import {
|
|
9
|
-
import { c as
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { t as
|
|
13
|
-
import { c as parkedMaxSetting, i as teardownParkedIosSim, l as readParked, o as adoptParked, r as teardownParkedAvd, s as dropParked, t as teardownOwnedAvd, u as selectParked } from "./teardown--gESs9ck.mjs";
|
|
14
|
-
import { t as withWorkspaceProcessLock } from "./workspace-process-lock-IBj1Towr.mjs";
|
|
6
|
+
import { r as pidExists } from "./metro-ato6ef3J.mjs";
|
|
7
|
+
import { l as phaseLine } from "./command-output-BuIb_ZOI.mjs";
|
|
8
|
+
import { A as pickDefaultSystemImage, D as ownedAvdName, E as ownedAvdMatchesConfiguration, L as waitForBoot, N as resolveOwnedAvdSerial, O as ownedAvdSystemImage, b as listAvds, c as avdPoolConfiguration, d as configureNewOwnedAvd, f as createOwnedAvd, o as assertOwnedAvdStopped, u as bootAndroidEmulator, v as getAvdNameForSerial, w as nextConsolePort, x as listInstalledSystemImages, y as listAdbDevices } from "./android-DJWiTBkz.mjs";
|
|
9
|
+
import { l as androidDataPartitionSizeGbSetting, o as androidAvdConfigSetting, v as iosSimSlimProfileSetting } from "./settings-kO2ZqIKy.mjs";
|
|
10
|
+
import { c as isLegacyDeletionClaim, d as selectParked, i as teardownParkedIosSim, l as parkedMaxSetting, o as adoptParked, r as teardownParkedAvd, s as dropParked, t as teardownOwnedAvd, u as readParked } from "./teardown-CAVcWeRD.mjs";
|
|
11
|
+
import { t as withWorkspaceProcessLock } from "./workspace-process-lock-Bz19_R9R.mjs";
|
|
15
12
|
import { E as readHostMemoryPressure, T as hostMemoryPressureAdvice, _ as renameIosSim, b as resolveIosCreation, c as iosRuntimeMatches, d as listIosDeviceTypes, g as parseRuntimeVersion, i as createOwnedIosSim, l as iosSimulatorFailureAdvice, m as ownedSimName, n as bootIosSim, u as listAllIosSims, v as resetIosKeychain, x as resolveOwnedIosSim, y as resetIosPrivacy } from "./ios-Deh5js5V.mjs";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
13
|
+
import { randomUUID } from "node:crypto";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { claimRemoveCommand, clearClaimChild, markClaimChildPending, processGroupAlive, releaseClaim, setClaimChild } from "@stim-cli/core/ownership-claim";
|
|
16
|
+
import { captureProcessIdentity } from "@stim-cli/core/process-identity";
|
|
17
|
+
import { isDeepStrictEqual } from "node:util";
|
|
19
18
|
import chalk from "chalk";
|
|
20
19
|
//#region src/engine/simslim.ts
|
|
21
20
|
async function reconcileSimSlim({ udid, profile, previouslyManaged = false, out = () => {}, spawn, timeoutMs = 72e4, cleanupMs = 1e4 }) {
|
|
@@ -42,14 +41,14 @@ async function reconcileSimSlim({ udid, profile, previouslyManaged = false, out
|
|
|
42
41
|
const stderr = createLineReader(onLine);
|
|
43
42
|
const root = join(getConfigDir(), "simslim-locks", `${encodeURIComponent(udid.toLowerCase())}.lock`);
|
|
44
43
|
const label = `SimSlim for ${udid}`;
|
|
45
|
-
const attempt = tryAcquireClaim({
|
|
44
|
+
const attempt = (0, ownership_claim_exports.tryAcquireClaim)({
|
|
46
45
|
root,
|
|
47
46
|
mode: "exclusive",
|
|
48
47
|
label
|
|
49
48
|
});
|
|
50
|
-
if (attempt.pending) releaseClaim(attempt.pending);
|
|
49
|
+
if (attempt.pending) (0, ownership_claim_exports.releaseClaim)(attempt.pending);
|
|
51
50
|
const claim = attempt.acquired;
|
|
52
|
-
if (!claim) throw new ClaimRefusedError({
|
|
51
|
+
if (!claim) throw new ownership_claim_exports.ClaimRefusedError({
|
|
53
52
|
root,
|
|
54
53
|
claimPath: attempt.held?.path ?? root,
|
|
55
54
|
label,
|
|
@@ -60,7 +59,7 @@ async function reconcileSimSlim({ udid, profile, previouslyManaged = false, out
|
|
|
60
59
|
let identity;
|
|
61
60
|
let cancellation;
|
|
62
61
|
const stopGroup = () => {
|
|
63
|
-
if (child?.pid !== void 0 && identity && inspectProcessIdentity(identity) === "same") try {
|
|
62
|
+
if (child?.pid !== void 0 && identity && (0, process_identity_exports.inspectProcessIdentity)(identity) === "same") try {
|
|
64
63
|
process.kill(-child.pid, "SIGKILL");
|
|
65
64
|
} catch {}
|
|
66
65
|
};
|
|
@@ -70,7 +69,7 @@ async function reconcileSimSlim({ udid, profile, previouslyManaged = false, out
|
|
|
70
69
|
};
|
|
71
70
|
const onInterrupt = () => cancel("SIGINT");
|
|
72
71
|
const onTerminate = () => cancel("SIGTERM");
|
|
73
|
-
const groupAlive = () => child?.pid !== void 0 && processGroupAlive(child.pid);
|
|
72
|
+
const groupAlive = () => child?.pid !== void 0 && (0, ownership_claim_exports.processGroupAlive)(child.pid);
|
|
74
73
|
const settled = () => result !== void 0 && !groupAlive();
|
|
75
74
|
const waitUntil = async (deadline, cancellable = false) => {
|
|
76
75
|
while (!settled() && Date.now() < deadline) {
|
|
@@ -83,7 +82,7 @@ async function reconcileSimSlim({ udid, profile, previouslyManaged = false, out
|
|
|
83
82
|
process.on("SIGTERM", onTerminate);
|
|
84
83
|
process.on("exit", stopGroup);
|
|
85
84
|
try {
|
|
86
|
-
markClaimChildPending(claim);
|
|
85
|
+
(0, ownership_claim_exports.markClaimChildPending)(claim);
|
|
87
86
|
try {
|
|
88
87
|
child = (spawn ?? ((cmd, childArgs, opts) => getExecutor().spawn(cmd, childArgs, opts)))("simslim", args, {
|
|
89
88
|
stdio: [
|
|
@@ -102,14 +101,14 @@ async function reconcileSimSlim({ udid, profile, previouslyManaged = false, out
|
|
|
102
101
|
result = value;
|
|
103
102
|
});
|
|
104
103
|
if (child.pid !== void 0) {
|
|
105
|
-
const captured = captureProcessIdentity(child.pid);
|
|
104
|
+
const captured = (0, process_identity_exports.captureProcessIdentity)(child.pid);
|
|
106
105
|
if (captured.ok) {
|
|
107
106
|
identity = {
|
|
108
107
|
pid: child.pid,
|
|
109
108
|
processToken: captured.token
|
|
110
109
|
};
|
|
111
110
|
try {
|
|
112
|
-
setClaimChild(claim, identity);
|
|
111
|
+
(0, ownership_claim_exports.setClaimChild)(claim, identity);
|
|
113
112
|
} catch {}
|
|
114
113
|
}
|
|
115
114
|
}
|
|
@@ -118,13 +117,13 @@ async function reconcileSimSlim({ udid, profile, previouslyManaged = false, out
|
|
|
118
117
|
stopGroup();
|
|
119
118
|
const stopped = await waitUntil(Date.now() + cleanupMs);
|
|
120
119
|
const detail = lines.length ? ` ${lines.join(" | ")}` : "";
|
|
121
|
-
const recovery = stopped ? "Its process group has stopped. Retry stim ios to reconcile the retained simulator settings." : `Its process group could not be confirmed stopped. The claim remains at ${claim.path}. Inspect the SimSlim process group ${child.pid ?? "unknown"} before retrying. Only when nothing is using it, remove the claim: ${claimRemoveCommand(claim.path)}`;
|
|
120
|
+
const recovery = stopped ? "Its process group has stopped. Retry stim ios to reconcile the retained simulator settings." : `Its process group could not be confirmed stopped. The claim remains at ${claim.path}. Inspect the SimSlim process group ${child.pid ?? "unknown"} before retrying. Only when nothing is using it, remove the claim: ${(0, ownership_claim_exports.claimRemoveCommand)(claim.path)}`;
|
|
122
121
|
const message = `SimSlim ${action} ${cancellation ? `interrupted by ${cancellation}` : `exceeded ${Math.round(timeoutMs / 1e3)}s`}. ${recovery}${detail}`;
|
|
123
122
|
if (cancellation) out(message);
|
|
124
123
|
throw Object.assign(new Error(message), { code: cancellation ? "EINTR" : "ETIMEDOUT" });
|
|
125
124
|
}
|
|
126
125
|
} finally {
|
|
127
|
-
if (!child || settled()) releaseClaim(claim);
|
|
126
|
+
if (!child || settled()) (0, ownership_claim_exports.releaseClaim)(claim);
|
|
128
127
|
else {
|
|
129
128
|
child.stdout?.destroy?.();
|
|
130
129
|
child.stderr?.destroy?.();
|
|
@@ -160,6 +159,157 @@ function simslimIsOnPath() {
|
|
|
160
159
|
}
|
|
161
160
|
}
|
|
162
161
|
//#endregion
|
|
162
|
+
//#region src/engine/android-avd-setup.ts
|
|
163
|
+
var AvdBootError = class extends Error {
|
|
164
|
+
remedy;
|
|
165
|
+
constructor(message, remedy, cause) {
|
|
166
|
+
super(message, { cause });
|
|
167
|
+
this.remedy = remedy;
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
var AvdRecoveryError = class extends AvdBootError {};
|
|
171
|
+
function findOtherOwner(avdName, projectPath, selectedSlot = "default") {
|
|
172
|
+
for (const [path, project] of Object.entries(loadConfig()?.projects ?? {})) if (projectDeviceSlots(project).some(({ slot, platforms }) => (path !== projectPath || slot !== selectedSlot) && platforms.android?.avdName?.toLowerCase() === avdName.toLowerCase())) return path;
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
async function prepareOwnedAvd({ projectPath, slot, label, previousAvdName, systemImage, configuration, configure, teardown = teardownOwnedAvd }) {
|
|
176
|
+
const { claim, reservation, previous, avdName } = withConfigLock(() => {
|
|
177
|
+
const previous = deviceSlotPlatforms(loadConfig()?.projects?.[projectPath], slot)?.android;
|
|
178
|
+
if (previous?.avdName && previous.avdName !== previousAvdName) throw new Error(`Another Stim run assigned AVD ${previous.avdName} to this workspace. Retry to use it.`);
|
|
179
|
+
if (readParked("android").some((entry) => entry.name === ownedAvdName(label)) || findOtherOwner(ownedAvdName(label), projectPath, slot)) label = `${label}-${randomUUID().slice(0, 8)}`;
|
|
180
|
+
const avdName = ownedAvdName(label);
|
|
181
|
+
const claim = acquireAvdClaim(avdName);
|
|
182
|
+
const reservation = {
|
|
183
|
+
avdName,
|
|
184
|
+
owned: true,
|
|
185
|
+
deviceName: avdName,
|
|
186
|
+
setupIncomplete: true,
|
|
187
|
+
poolConfiguration: configuration
|
|
188
|
+
};
|
|
189
|
+
try {
|
|
190
|
+
setDevice(projectPath, "android", reservation, slot);
|
|
191
|
+
return {
|
|
192
|
+
claim,
|
|
193
|
+
reservation,
|
|
194
|
+
previous,
|
|
195
|
+
avdName
|
|
196
|
+
};
|
|
197
|
+
} catch (error) {
|
|
198
|
+
releaseClaim(claim);
|
|
199
|
+
throw error;
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
const currentRecord = () => deviceSlotPlatforms(loadConfig()?.projects?.[projectPath], slot)?.android;
|
|
203
|
+
const assertReservation = () => {
|
|
204
|
+
if (readParked("android").some((entry) => entry.name === avdName)) throw new Error(`AVD ${avdName} was parked by another Stim run. Retry to adopt it safely.`);
|
|
205
|
+
const owner = findOtherOwner(avdName, projectPath, slot);
|
|
206
|
+
if (owner) throw new Error(`AVD ${avdName} already exists and is owned by another project (${owner}). Retry to allocate a distinct owned emulator.`);
|
|
207
|
+
const current = currentRecord();
|
|
208
|
+
if (!isDeepStrictEqual(current, reservation)) {
|
|
209
|
+
const state = current?.setupIncomplete ? "has incomplete setup" : "was registered or removed";
|
|
210
|
+
throw new Error(`AVD ${current?.avdName ?? avdName} ${state} by another concurrent Stim run. Retry after that run finishes so the recorded device is resolved safely.`);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
let prepared;
|
|
214
|
+
let child;
|
|
215
|
+
const creationRunning = () => child?.pid !== void 0 && processGroupAlive(child.pid);
|
|
216
|
+
let configurationFailure;
|
|
217
|
+
try {
|
|
218
|
+
try {
|
|
219
|
+
const creation = await createOwnedAvd(label, {
|
|
220
|
+
systemImage,
|
|
221
|
+
spawn: (...args) => {
|
|
222
|
+
markClaimChildPending(claim);
|
|
223
|
+
child = getExecutor().spawn(...args);
|
|
224
|
+
if (child.pid !== void 0) {
|
|
225
|
+
const captured = captureProcessIdentity(child.pid);
|
|
226
|
+
if (captured.ok) try {
|
|
227
|
+
setClaimChild(claim, {
|
|
228
|
+
pid: child.pid,
|
|
229
|
+
processToken: captured.token
|
|
230
|
+
});
|
|
231
|
+
} catch {}
|
|
232
|
+
}
|
|
233
|
+
return child;
|
|
234
|
+
}
|
|
235
|
+
}).then((result) => ({ result }), (error) => ({ error }));
|
|
236
|
+
if (creationRunning()) throw new AvdRecoveryError(`AVD ${avdName} creation left its process group running (pid ${child.pid}); its reservation remains incomplete.`, `Wait for process group ${child.pid} to finish before retrying stim android. If its identity could not be recorded, inspect the group and remove the claim only after it stops: ${claimRemoveCommand(claim.path)}`);
|
|
237
|
+
clearClaimChild(claim);
|
|
238
|
+
if ("error" in creation) throw creation.error;
|
|
239
|
+
prepared = {
|
|
240
|
+
...creation.result,
|
|
241
|
+
created: true
|
|
242
|
+
};
|
|
243
|
+
} catch (error) {
|
|
244
|
+
const message = String(error?.message || error);
|
|
245
|
+
if (!message.includes("already exists")) throw error;
|
|
246
|
+
try {
|
|
247
|
+
if (!listAvds().includes(avdName)) throw new AvdRecoveryError(`AVD ${avdName} already exists on disk but is not listed by the emulator. ${message}`, "Run `npx stim gc` to inspect orphaned owned AVDs, then `npx stim gc --delete` to reclaim those safe to delete. Retry `stim android` after cleanup; keep any AVD that GC cannot verify.", error);
|
|
248
|
+
withConfigLock(assertReservation);
|
|
249
|
+
const resolved = resolveOwnedAvdSerial(avdName);
|
|
250
|
+
if (resolved.missing || resolved.notOwned) throw new Error(`AVD ${avdName} could not be verified for recovery. Retry after checking its registration.`, { cause: error });
|
|
251
|
+
if (!resolved.serial) assertOwnedAvdStopped(avdName);
|
|
252
|
+
prepared = {
|
|
253
|
+
avdName,
|
|
254
|
+
created: false,
|
|
255
|
+
systemImage: ownedAvdSystemImage(avdName),
|
|
256
|
+
...resolved.serial ? {
|
|
257
|
+
serial: resolved.serial,
|
|
258
|
+
consolePort: Number(resolved.serial.replace(/^emulator-/, ""))
|
|
259
|
+
} : {}
|
|
260
|
+
};
|
|
261
|
+
} catch (recoveryError) {
|
|
262
|
+
withConfigLock(() => {
|
|
263
|
+
if (!isDeepStrictEqual(currentRecord(), reservation)) return;
|
|
264
|
+
if (previous) setDevice(projectPath, "android", previous, slot);
|
|
265
|
+
else clearDevice(projectPath, "android", slot, avdName);
|
|
266
|
+
});
|
|
267
|
+
if (recoveryError instanceof AvdRecoveryError) throw recoveryError;
|
|
268
|
+
throw new AvdRecoveryError(`Could not recover owned AVD ${avdName}: ${String(recoveryError?.message || recoveryError)}`, "Inspect `npx stim status` and `adb devices`. Wait for any other Stim run using this AVD to finish, then retry `stim android`. Keep the AVD and its process locks while its state is unverified.", recoveryError);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (prepared.created) try {
|
|
272
|
+
configure(avdName);
|
|
273
|
+
} catch (error) {
|
|
274
|
+
configurationFailure = { error };
|
|
275
|
+
}
|
|
276
|
+
if (!configurationFailure) withConfigLock(() => {
|
|
277
|
+
assertReservation();
|
|
278
|
+
setDevice(projectPath, "android", {
|
|
279
|
+
avdName,
|
|
280
|
+
owned: true,
|
|
281
|
+
deviceName: avdName,
|
|
282
|
+
...prepared.created ? { poolConfiguration: configuration } : {},
|
|
283
|
+
...prepared.consolePort ? { consolePort: prepared.consolePort } : {}
|
|
284
|
+
}, slot);
|
|
285
|
+
});
|
|
286
|
+
} finally {
|
|
287
|
+
if (!creationRunning()) releaseClaim(claim);
|
|
288
|
+
else {
|
|
289
|
+
child?.stdout?.destroy();
|
|
290
|
+
child?.stderr?.destroy();
|
|
291
|
+
child?.unref();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (configurationFailure) {
|
|
295
|
+
const { error } = configurationFailure;
|
|
296
|
+
const cleanup = teardown(avdName, {
|
|
297
|
+
del: true,
|
|
298
|
+
owner: {
|
|
299
|
+
projectPath,
|
|
300
|
+
slot,
|
|
301
|
+
expectedRecord: reservation
|
|
302
|
+
},
|
|
303
|
+
onRemoved: () => {
|
|
304
|
+
clearDevice(projectPath, "android", slot, avdName);
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
const orphan = cleanup.status === "failed" || cleanup.status === "skipped" ? ` The owned AVD remains tracked for cleanup (${cleanup.reason || cleanup.status}); fix the cause, then retry or run \`stim gc --delete\`.` : "";
|
|
308
|
+
throw new Error(`Created owned AVD ${avdName}, but could not configure its AVD settings: ${String(error?.message || error)}${orphan}`, { cause: error });
|
|
309
|
+
}
|
|
310
|
+
return prepared;
|
|
311
|
+
}
|
|
312
|
+
//#endregion
|
|
163
313
|
//#region src/engine/device.ts
|
|
164
314
|
function startIosBoot(udid, configure, label, out) {
|
|
165
315
|
const done = (async () => {
|
|
@@ -455,29 +605,23 @@ async function configureOwnedIosSim({ record, projectPath, slot, profile, out, r
|
|
|
455
605
|
setDevice(projectPath, "ios", updated, slot);
|
|
456
606
|
return updated;
|
|
457
607
|
}
|
|
458
|
-
function findOtherProjectOwningAvd(avdName, projectPath, selectedSlot = "default") {
|
|
459
|
-
const cfg = loadConfig();
|
|
460
|
-
for (const [path, proj] of Object.entries(cfg?.projects || {})) if (projectDeviceSlots(proj).some(({ slot, platforms }) => (path !== projectPath || slot !== selectedSlot) && platforms.android?.avdName?.toLowerCase() === avdName.toLowerCase())) return path;
|
|
461
|
-
return null;
|
|
462
|
-
}
|
|
463
|
-
var AvdBootError = class extends Error {
|
|
464
|
-
remedy;
|
|
465
|
-
constructor(message, remedy, cause) {
|
|
466
|
-
super(message, { cause });
|
|
467
|
-
this.remedy = remedy;
|
|
468
|
-
}
|
|
469
|
-
};
|
|
470
|
-
var AvdRecoveryError = class extends AvdBootError {};
|
|
471
608
|
async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoot, label, settings, flags, note, out, logFile, alive, configureAvd, teardownAvd }) {
|
|
472
609
|
const avdConfig = androidAvdConfigSetting(settings, settingsRoot);
|
|
473
610
|
const configuration = avdPoolConfiguration(androidDataPartitionSizeGbSetting(settings), avdConfig);
|
|
474
611
|
if (record?.setupIncomplete && record.avdName) {
|
|
475
|
-
const
|
|
612
|
+
const avdName = record.avdName;
|
|
613
|
+
const cleanup = teardownAvd(avdName, {
|
|
476
614
|
del: true,
|
|
477
|
-
owner: {
|
|
615
|
+
owner: {
|
|
616
|
+
projectPath,
|
|
617
|
+
slot,
|
|
618
|
+
expectedRecord: record
|
|
619
|
+
},
|
|
620
|
+
onRemoved: () => {
|
|
621
|
+
clearDevice(projectPath, "android", slot, avdName);
|
|
622
|
+
}
|
|
478
623
|
});
|
|
479
|
-
if (cleanup.status === "failed" || cleanup.status === "skipped") throw new
|
|
480
|
-
clearDevice(projectPath, "android", slot);
|
|
624
|
+
if (cleanup.status === "failed" || cleanup.status === "skipped") throw new AvdRecoveryError(`Owned AVD ${record.avdName} has incomplete setup and could not be deleted (${cleanup.reason || cleanup.status}). Fix the cause, then retry; Stim kept the device record for cleanup.`, "Wait for any active AVD operation to finish. If the refusal names an unresolved claim, inspect its creator and native processes before removing only that claim. Retry `stim android` to reconcile the incomplete device.");
|
|
481
625
|
record = null;
|
|
482
626
|
}
|
|
483
627
|
if (record?.avdName) {
|
|
@@ -530,7 +674,7 @@ async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoo
|
|
|
530
674
|
const systemImage = pickDefaultSystemImage(listInstalledSystemImages(), { systemImage: flags.systemImage || settings.android?.systemImage })?.pkg;
|
|
531
675
|
const candidates = readParked("android").filter((entry) => entry.systemImage === systemImage && entry.configuration === configuration).toSorted((a, b) => a.parkedAt.localeCompare(b.parkedAt));
|
|
532
676
|
for (const parked of candidates) {
|
|
533
|
-
if (parked.deletionClaim
|
|
677
|
+
if (isLegacyDeletionClaim(parked.deletionClaim)) continue;
|
|
534
678
|
const resolved = resolveOwnedAvdSerial(parked.name);
|
|
535
679
|
if (resolved.missing) {
|
|
536
680
|
const result = teardownParkedAvd(parked.name);
|
|
@@ -578,87 +722,31 @@ async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoo
|
|
|
578
722
|
};
|
|
579
723
|
}
|
|
580
724
|
}
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
setDevice(projectPath, "android", {
|
|
590
|
-
avdName: result.avdName,
|
|
591
|
-
owned: true,
|
|
592
|
-
deviceName: result.avdName,
|
|
593
|
-
setupIncomplete: true,
|
|
594
|
-
poolConfiguration: configuration
|
|
595
|
-
}, slot);
|
|
596
|
-
return result;
|
|
597
|
-
});
|
|
598
|
-
fresh = true;
|
|
599
|
-
} catch (e) {
|
|
600
|
-
const message = String(e?.message || e);
|
|
601
|
-
const avdName = ownedAvdName(label);
|
|
602
|
-
if (message.includes("already exists")) {
|
|
603
|
-
try {
|
|
604
|
-
if (!listAvds().includes(avdName)) throw new AvdRecoveryError(`AVD ${avdName} already exists on disk but is not listed by the emulator. ${message}`, "Run `npx stim gc` to inspect orphaned owned AVDs, then `npx stim gc --delete` to reclaim those safe to delete. Retry `stim android` after cleanup; keep any AVD that GC cannot verify.", e);
|
|
605
|
-
created = withConfigLock(() => {
|
|
606
|
-
if (readParked("android").some((entry) => entry.name === avdName)) throw new Error(`AVD ${avdName} was parked by another Stim run. Retry to adopt it safely.`, { cause: e });
|
|
607
|
-
const owner = findOtherProjectOwningAvd(avdName, projectPath, slot);
|
|
608
|
-
if (owner) throw new Error(`AVD ${avdName} already exists and is owned by another project (${owner}). Retry to allocate a distinct owned emulator.`, { cause: e });
|
|
609
|
-
const current = deviceSlotPlatforms(loadConfig()?.projects?.[projectPath], slot)?.android;
|
|
610
|
-
if (current?.avdName) {
|
|
611
|
-
const state = current.setupIncomplete ? "has incomplete setup" : "was registered";
|
|
612
|
-
throw new Error(`AVD ${current.avdName} ${state} by another concurrent Stim run. Retry after that run finishes so the recorded device is resolved safely.`, { cause: e });
|
|
613
|
-
}
|
|
614
|
-
const resolved = resolveOwnedAvdSerial(avdName);
|
|
615
|
-
if (resolved.missing || resolved.notOwned) throw new Error(`AVD ${avdName} could not be verified for recovery. Retry after checking its registration.`, { cause: e });
|
|
616
|
-
if (!resolved.serial) assertOwnedAvdStopped(avdName);
|
|
617
|
-
const recovered = {
|
|
618
|
-
avdName,
|
|
619
|
-
owned: true,
|
|
620
|
-
deviceName: avdName,
|
|
621
|
-
...resolved.serial ? { consolePort: Number(resolved.serial.replace(/^emulator-/, "")) } : {}
|
|
622
|
-
};
|
|
623
|
-
setDevice(projectPath, "android", recovered, slot);
|
|
624
|
-
return {
|
|
625
|
-
...recovered,
|
|
626
|
-
systemImage: ownedAvdSystemImage(avdName),
|
|
627
|
-
serial: resolved.serial
|
|
628
|
-
};
|
|
629
|
-
});
|
|
630
|
-
} catch (error) {
|
|
631
|
-
if (error instanceof AvdRecoveryError) throw error;
|
|
632
|
-
throw new AvdRecoveryError(`Could not recover owned AVD ${avdName}: ${String(error?.message || error)}`, "Inspect `npx stim status` and `adb devices`. Wait for any other Stim run using this AVD to finish, then retry `stim android`. Keep the AVD and its process locks while its state is unverified.", error);
|
|
633
|
-
}
|
|
634
|
-
out(chalk.dim(phaseLine("device", `recovered ${avdName} (unrecorded from a prior run)`)));
|
|
635
|
-
if (created.serial) return {
|
|
636
|
-
...created,
|
|
637
|
-
owned: true,
|
|
638
|
-
deviceName: avdName,
|
|
639
|
-
created: false
|
|
640
|
-
};
|
|
641
|
-
} else throw e;
|
|
642
|
-
}
|
|
643
|
-
if (fresh) try {
|
|
644
|
-
configureAvd(created.avdName, {
|
|
725
|
+
const created = await prepareOwnedAvd({
|
|
726
|
+
projectPath,
|
|
727
|
+
slot,
|
|
728
|
+
label,
|
|
729
|
+
previousAvdName: record?.avdName,
|
|
730
|
+
systemImage: flags.systemImage || settings.android?.systemImage || void 0,
|
|
731
|
+
configuration,
|
|
732
|
+
configure: (avdName) => configureAvd(avdName, {
|
|
645
733
|
dataPartitionSizeGb: androidDataPartitionSizeGbSetting(settings),
|
|
646
734
|
avdConfig
|
|
647
|
-
})
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
735
|
+
}),
|
|
736
|
+
teardown: teardownAvd
|
|
737
|
+
});
|
|
738
|
+
if (!created.created) {
|
|
739
|
+
out(chalk.dim(phaseLine("device", `recovered ${created.avdName} (unrecorded from a prior run)`)));
|
|
740
|
+
if (created.serial) return {
|
|
741
|
+
...created,
|
|
742
|
+
owned: true,
|
|
743
|
+
deviceName: created.avdName
|
|
744
|
+
};
|
|
657
745
|
}
|
|
658
746
|
return {
|
|
659
747
|
...await bootOwnedAvdOnFreshPort({
|
|
660
748
|
avdName: created.avdName,
|
|
661
|
-
metadata:
|
|
749
|
+
metadata: created.created ? { poolConfiguration: configuration } : void 0,
|
|
662
750
|
projectPath,
|
|
663
751
|
slot,
|
|
664
752
|
deviceName: created.avdName,
|
|
@@ -666,7 +754,7 @@ async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoo
|
|
|
666
754
|
logFile,
|
|
667
755
|
alive
|
|
668
756
|
}),
|
|
669
|
-
created:
|
|
757
|
+
created: created.created,
|
|
670
758
|
systemImage: created.systemImage
|
|
671
759
|
};
|
|
672
760
|
}
|
|
@@ -1045,851 +1133,4 @@ function sleep(ms) {
|
|
|
1045
1133
|
return new Promise((r) => setTimeout(r, ms));
|
|
1046
1134
|
}
|
|
1047
1135
|
//#endregion
|
|
1048
|
-
|
|
1049
|
-
const COCOAPODS_SPEC = /^ {4}cocoapods \(/m;
|
|
1050
|
-
function bundlerPin(root) {
|
|
1051
|
-
const gemfile = join(root, "Gemfile");
|
|
1052
|
-
const lockfile = join(root, "Gemfile.lock");
|
|
1053
|
-
if (!existsSync(gemfile)) return null;
|
|
1054
|
-
let lock;
|
|
1055
|
-
try {
|
|
1056
|
-
lock = readFileSync(lockfile, "utf-8");
|
|
1057
|
-
} catch {
|
|
1058
|
-
return null;
|
|
1059
|
-
}
|
|
1060
|
-
if (!COCOAPODS_SPEC.test(lock)) return null;
|
|
1061
|
-
return {
|
|
1062
|
-
gemfile,
|
|
1063
|
-
lockfile
|
|
1064
|
-
};
|
|
1065
|
-
}
|
|
1066
|
-
function podInstallCommand(root, ...args) {
|
|
1067
|
-
const pod = [
|
|
1068
|
-
"pod",
|
|
1069
|
-
"install",
|
|
1070
|
-
...args
|
|
1071
|
-
].join(" ");
|
|
1072
|
-
return bundlerPin(root) ? `bundle exec ${pod}` : pod;
|
|
1073
|
-
}
|
|
1074
|
-
const BUILD_FAILED = "** BUILD FAILED **";
|
|
1075
|
-
const POSITIONED = /^(\S[^\t]*?):(\d+):(?:(\d+):)?\s*(?:fatal\s+)?error:\s+(.+)$/;
|
|
1076
|
-
const UNPOSITIONED = /^(\S[^\t]*?)?\berror:\s+(.+)$/;
|
|
1077
|
-
const LD_ERROR = /^ld:\s+(?!warning:)(.+)$/;
|
|
1078
|
-
const UNDEFINED_HEADER = /^(?:ld:\s+)?Undefined symbols?(?:\s+for architecture\s+\S+)?:\s*$/;
|
|
1079
|
-
const UNDEFINED_SYMBOL = /^\s+"?([^",]+)"?,\s+referenced from:\s*$/;
|
|
1080
|
-
function undefinedSymbolMessage(symbol) {
|
|
1081
|
-
return `Undefined symbol: ${symbol}`;
|
|
1082
|
-
}
|
|
1083
|
-
const PODS_OUT_OF_SYNC = /The sandbox is not in sync with the Podfile\.lock/;
|
|
1084
|
-
const SIGNING = [
|
|
1085
|
-
/No profiles for '.*' were found/,
|
|
1086
|
-
/requires a development team/i,
|
|
1087
|
-
/[Cc]ode ?[Ss]igning [Ee]rror/,
|
|
1088
|
-
/No signing certificate/i,
|
|
1089
|
-
/code signing is required/i,
|
|
1090
|
-
/errSecInternalComponent/,
|
|
1091
|
-
/Provisioning profile .* doesn't (?:include|match)/i
|
|
1092
|
-
];
|
|
1093
|
-
const NO_SUCH_SCHEME = /does not contain a scheme named/;
|
|
1094
|
-
const CAS_CORRUPT = /not a IncludeTreeRoot node kind/;
|
|
1095
|
-
const DEVICE_SIGNING_REMEDY = "Set a team and a Development profile for the target's configuration in Xcode > Signing & Capabilities, then build once from Xcode to install the profile. Stim never passes -allowProvisioningUpdates, because registering a device or minting a profile changes your Apple Developer account.";
|
|
1096
|
-
const SIMULATOR_SIGNING_REMEDY = "Stim builds for the simulator here, which needs no signing. Check CODE_SIGNING_REQUIRED / DEVELOPMENT_TEAM in the target's configuration, or build for a phone with `stim ios --device`.";
|
|
1097
|
-
function remedyFor(message, root, sdk) {
|
|
1098
|
-
if (CAS_CORRUPT.test(message)) return "The compilation cache holds a damaged object. Run `stim gc --delete --cache \"compilation cache\"` to empty that cache, then build again. The next build is a cold one.";
|
|
1099
|
-
if (PODS_OUT_OF_SYNC.test(message)) return `Run \`${root ? podInstallCommand(root) : "pod install"}\` in ios/ (stim ios does this when Podfile.lock and Pods/Manifest.lock disagree), then build again.`;
|
|
1100
|
-
if (NO_SUCH_SCHEME.test(message)) return "Run `xcodebuild -list` in ios/ to see the schemes this project defines, and share the app scheme so it is visible to the build.";
|
|
1101
|
-
for (const pattern of SIGNING) if (pattern.test(message)) return sdk.startsWith("iphoneos") ? DEVICE_SIGNING_REMEDY : SIMULATOR_SIGNING_REMEDY;
|
|
1102
|
-
return null;
|
|
1103
|
-
}
|
|
1104
|
-
function fileFromPrefix(prefix) {
|
|
1105
|
-
const trimmed = String(prefix).trim().replace(/:$/, "");
|
|
1106
|
-
if (!trimmed) return null;
|
|
1107
|
-
const head = (trimmed.split(": ")[0] ?? "").trim();
|
|
1108
|
-
if (!head.includes("/")) return null;
|
|
1109
|
-
return head;
|
|
1110
|
-
}
|
|
1111
|
-
function makeDiagnostic({ file = null, line = null, column = null, message }, root, sdk) {
|
|
1112
|
-
const text = String(message).trim();
|
|
1113
|
-
const out = { message: text };
|
|
1114
|
-
if (file) out.file = file;
|
|
1115
|
-
if (line !== null && line !== void 0) out.line = line;
|
|
1116
|
-
if (column !== null && column !== void 0) out.column = column;
|
|
1117
|
-
out.message = text;
|
|
1118
|
-
const remedy = remedyFor(text, root, sdk);
|
|
1119
|
-
if (remedy) out.remedy = remedy;
|
|
1120
|
-
return out;
|
|
1121
|
-
}
|
|
1122
|
-
function dedupeKey(d) {
|
|
1123
|
-
return `${d.file || ""}|${d.line || ""}|${d.column || ""}|${d.message}`;
|
|
1124
|
-
}
|
|
1125
|
-
function extractXcodeDiagnostics(transcript, root = null, sdk = "iphonesimulator") {
|
|
1126
|
-
if (typeof transcript !== "string" || transcript === "") return [];
|
|
1127
|
-
const lines = transcript.split("\n");
|
|
1128
|
-
const out = [];
|
|
1129
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1130
|
-
const push = (d) => {
|
|
1131
|
-
const key = dedupeKey(d);
|
|
1132
|
-
if (seen.has(key)) return;
|
|
1133
|
-
seen.add(key);
|
|
1134
|
-
out.push(d);
|
|
1135
|
-
};
|
|
1136
|
-
for (let i = 0; i < lines.length; i += 1) {
|
|
1137
|
-
const rawLine = lines[i];
|
|
1138
|
-
if (rawLine === void 0) continue;
|
|
1139
|
-
const raw = rawLine.replace(/\r$/, "");
|
|
1140
|
-
if (raw.includes(BUILD_FAILED)) break;
|
|
1141
|
-
if (UNDEFINED_HEADER.test(raw)) {
|
|
1142
|
-
for (let j = i + 1; j < lines.length; j += 1) {
|
|
1143
|
-
const symLine = lines[j];
|
|
1144
|
-
if (symLine === void 0) continue;
|
|
1145
|
-
const sym = UNDEFINED_SYMBOL.exec(symLine.replace(/\r$/, ""));
|
|
1146
|
-
if (sym) {
|
|
1147
|
-
const symbol = sym[1];
|
|
1148
|
-
if (symbol !== void 0) push(makeDiagnostic({ message: undefinedSymbolMessage(symbol) }, root, sdk));
|
|
1149
|
-
continue;
|
|
1150
|
-
}
|
|
1151
|
-
if (/^\s+\S/.test(symLine) && !/^\S/.test(symLine)) continue;
|
|
1152
|
-
i = j - 1;
|
|
1153
|
-
break;
|
|
1154
|
-
}
|
|
1155
|
-
continue;
|
|
1156
|
-
}
|
|
1157
|
-
const ld = LD_ERROR.exec(raw);
|
|
1158
|
-
if (ld) {
|
|
1159
|
-
push(makeDiagnostic({ message: `ld: ${ld[1]}` }, root, sdk));
|
|
1160
|
-
continue;
|
|
1161
|
-
}
|
|
1162
|
-
const positioned = POSITIONED.exec(raw);
|
|
1163
|
-
if (positioned) {
|
|
1164
|
-
const posMsg = positioned[4];
|
|
1165
|
-
if (posMsg === void 0) continue;
|
|
1166
|
-
push(makeDiagnostic({
|
|
1167
|
-
file: positioned[1],
|
|
1168
|
-
line: Number(positioned[2]),
|
|
1169
|
-
column: positioned[3] === void 0 ? null : Number(positioned[3]),
|
|
1170
|
-
message: posMsg
|
|
1171
|
-
}, root, sdk));
|
|
1172
|
-
continue;
|
|
1173
|
-
}
|
|
1174
|
-
const plain = UNPOSITIONED.exec(raw);
|
|
1175
|
-
if (plain) {
|
|
1176
|
-
const plainMsg = plain[2];
|
|
1177
|
-
if (plainMsg === void 0) continue;
|
|
1178
|
-
push(makeDiagnostic({
|
|
1179
|
-
file: fileFromPrefix(plain[1] || ""),
|
|
1180
|
-
message: plainMsg
|
|
1181
|
-
}, root, sdk));
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
return out;
|
|
1185
|
-
}
|
|
1186
|
-
function capDiagnostics(diagnostics, max = 10) {
|
|
1187
|
-
const list = Array.isArray(diagnostics) ? diagnostics : [];
|
|
1188
|
-
if (list.length <= max) return {
|
|
1189
|
-
diagnostics: list.slice(),
|
|
1190
|
-
truncated: 0
|
|
1191
|
-
};
|
|
1192
|
-
return {
|
|
1193
|
-
diagnostics: list.slice(0, max),
|
|
1194
|
-
truncated: list.length - max
|
|
1195
|
-
};
|
|
1196
|
-
}
|
|
1197
|
-
function describeDiagnostic(diagnostic) {
|
|
1198
|
-
if (!diagnostic || typeof diagnostic !== "object") return "";
|
|
1199
|
-
const { file, line, column, message } = diagnostic;
|
|
1200
|
-
if (!file) return String(message ?? "");
|
|
1201
|
-
return `${file}${line ? column ? `:${line}:${column}` : `:${line}` : ""}: ${message}`;
|
|
1202
|
-
}
|
|
1203
|
-
//#endregion
|
|
1204
|
-
//#region src/engine/xcode.ts
|
|
1205
|
-
const IOS_DIR = "ios";
|
|
1206
|
-
const PREBUILD_REMEDY = "Generate it with `npx expo prebuild -p ios` (stim ios does this automatically for an Expo project with no ios/ directory), or commit the native project.";
|
|
1207
|
-
function buildFailure(message, remedy) {
|
|
1208
|
-
return { error: {
|
|
1209
|
-
code: "STIM_BUILD_FAILED",
|
|
1210
|
-
message,
|
|
1211
|
-
remedy
|
|
1212
|
-
} };
|
|
1213
|
-
}
|
|
1214
|
-
function pickXcodeProject(entries) {
|
|
1215
|
-
const names = (Array.isArray(entries) ? entries : []).filter((e) => typeof e === "string");
|
|
1216
|
-
const workspaces = names.filter((e) => e.endsWith(".xcworkspace")).toSorted();
|
|
1217
|
-
const projects = names.filter((e) => e.endsWith(".xcodeproj")).toSorted();
|
|
1218
|
-
if (workspaces.length > 0) {
|
|
1219
|
-
const projectNames = new Set(projects.map((p) => basename(p, ".xcodeproj")));
|
|
1220
|
-
const file = workspaces.find((w) => projectNames.has(basename(w, ".xcworkspace"))) || workspaces[0];
|
|
1221
|
-
if (file === void 0) return null;
|
|
1222
|
-
return {
|
|
1223
|
-
kind: "workspace",
|
|
1224
|
-
flag: "-workspace",
|
|
1225
|
-
file,
|
|
1226
|
-
name: basename(file, ".xcworkspace")
|
|
1227
|
-
};
|
|
1228
|
-
}
|
|
1229
|
-
if (projects.length > 0) {
|
|
1230
|
-
const file = projects[0];
|
|
1231
|
-
if (file === void 0) return null;
|
|
1232
|
-
return {
|
|
1233
|
-
kind: "project",
|
|
1234
|
-
flag: "-project",
|
|
1235
|
-
file,
|
|
1236
|
-
name: basename(file, ".xcodeproj")
|
|
1237
|
-
};
|
|
1238
|
-
}
|
|
1239
|
-
return null;
|
|
1240
|
-
}
|
|
1241
|
-
function discoverXcodeProject(root) {
|
|
1242
|
-
const dir = join(root, IOS_DIR);
|
|
1243
|
-
let entries;
|
|
1244
|
-
try {
|
|
1245
|
-
entries = readdirSync(dir);
|
|
1246
|
-
} catch {
|
|
1247
|
-
return buildFailure(`No ${IOS_DIR}/ directory in ${root}.`, PREBUILD_REMEDY);
|
|
1248
|
-
}
|
|
1249
|
-
const picked = pickXcodeProject(entries);
|
|
1250
|
-
if (!picked) return buildFailure(`${dir} contains no .xcworkspace and no .xcodeproj.`, PREBUILD_REMEDY);
|
|
1251
|
-
return {
|
|
1252
|
-
...picked,
|
|
1253
|
-
dir,
|
|
1254
|
-
path: join(dir, picked.file)
|
|
1255
|
-
};
|
|
1256
|
-
}
|
|
1257
|
-
function parseSchemeList(text) {
|
|
1258
|
-
const empty = {
|
|
1259
|
-
name: null,
|
|
1260
|
-
schemes: []
|
|
1261
|
-
};
|
|
1262
|
-
if (typeof text !== "string") return empty;
|
|
1263
|
-
const start = text.indexOf("{");
|
|
1264
|
-
const end = text.lastIndexOf("}");
|
|
1265
|
-
if (start < 0 || end <= start) return empty;
|
|
1266
|
-
let data;
|
|
1267
|
-
try {
|
|
1268
|
-
data = JSON.parse(text.slice(start, end + 1));
|
|
1269
|
-
} catch {
|
|
1270
|
-
return empty;
|
|
1271
|
-
}
|
|
1272
|
-
if (!data || typeof data !== "object") return empty;
|
|
1273
|
-
const record = data;
|
|
1274
|
-
const container = record.workspace || record.project;
|
|
1275
|
-
if (!container || typeof container !== "object") return empty;
|
|
1276
|
-
const info = container;
|
|
1277
|
-
const schemes = Array.isArray(info.schemes) ? info.schemes.filter((s) => typeof s === "string" && s.trim() !== "") : [];
|
|
1278
|
-
return {
|
|
1279
|
-
name: typeof info.name === "string" ? info.name : null,
|
|
1280
|
-
schemes
|
|
1281
|
-
};
|
|
1282
|
-
}
|
|
1283
|
-
const TEST_SCHEME = /(?:UI)?Tests$/;
|
|
1284
|
-
function pickScheme(schemes, containerName) {
|
|
1285
|
-
const list = (Array.isArray(schemes) ? schemes : []).filter((s) => typeof s === "string" && s.trim() !== "");
|
|
1286
|
-
if (list.length === 0) return null;
|
|
1287
|
-
const name = typeof containerName === "string" ? containerName.trim() : "";
|
|
1288
|
-
if (name) {
|
|
1289
|
-
const exact = list.find((s) => s === name);
|
|
1290
|
-
if (exact) return exact;
|
|
1291
|
-
const insensitive = list.find((s) => s.toLowerCase() === name.toLowerCase());
|
|
1292
|
-
if (insensitive) return insensitive;
|
|
1293
|
-
}
|
|
1294
|
-
const app = list.filter((s) => !TEST_SCHEME.test(s));
|
|
1295
|
-
return app.length === 1 ? app[0] : null;
|
|
1296
|
-
}
|
|
1297
|
-
const LIST_TIMEOUT_MS = 18e4;
|
|
1298
|
-
function listSchemes(project, { exec = null } = {}) {
|
|
1299
|
-
const executor = exec || getExecutor();
|
|
1300
|
-
try {
|
|
1301
|
-
return parseSchemeList(executor.runFile("xcodebuild", [
|
|
1302
|
-
project.flag,
|
|
1303
|
-
project.path,
|
|
1304
|
-
"-list",
|
|
1305
|
-
"-json"
|
|
1306
|
-
], { timeoutMs: LIST_TIMEOUT_MS }));
|
|
1307
|
-
} catch {
|
|
1308
|
-
return null;
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
function resolveScheme(project, { exec = null, scheme: requested = null } = {}) {
|
|
1312
|
-
const listing = listSchemes(project, { exec });
|
|
1313
|
-
if (listing === null) return { error: {
|
|
1314
|
-
code: "STIM_NO_SCHEME",
|
|
1315
|
-
message: `Could not list schemes for ${project.path}.`,
|
|
1316
|
-
remedy: `Run \`xcodebuild ${project.flag} ${project.path} -list\` to see what it reports.`
|
|
1317
|
-
} };
|
|
1318
|
-
if (requested !== null) {
|
|
1319
|
-
if (listing.schemes.includes(requested)) return {
|
|
1320
|
-
scheme: requested,
|
|
1321
|
-
schemes: listing.schemes
|
|
1322
|
-
};
|
|
1323
|
-
return { error: {
|
|
1324
|
-
code: "STIM_NO_SCHEME",
|
|
1325
|
-
message: `No shared Xcode scheme named ${JSON.stringify(requested)} in ${project.path}. Available schemes: ${listing.schemes.join(", ") || "none"}.`,
|
|
1326
|
-
remedy: "Pass an exact available name with --scheme, or share the intended app scheme in Xcode."
|
|
1327
|
-
} };
|
|
1328
|
-
}
|
|
1329
|
-
let scheme = pickScheme(listing.schemes, listing.name || project.name);
|
|
1330
|
-
if (!scheme && project.dir) try {
|
|
1331
|
-
const app = JSON.parse(readFileSync(join(project.dir, "..", "app.json"), "utf8"));
|
|
1332
|
-
scheme = pickScheme(listing.schemes, app?.name);
|
|
1333
|
-
} catch {}
|
|
1334
|
-
if (!scheme) {
|
|
1335
|
-
const found = listing.schemes.length ? listing.schemes.join(", ") : "none";
|
|
1336
|
-
return { error: {
|
|
1337
|
-
code: "STIM_NO_SCHEME",
|
|
1338
|
-
message: `Could not select an app scheme in ${project.path} (schemes: ${found}).`,
|
|
1339
|
-
remedy: listing.schemes.length > 0 ? "Pass --scheme <name> to select the intended shared app scheme. Stim does not guess between unmatched schemes." : "Share the app scheme in Xcode (Product > Scheme > Manage Schemes, tick Shared) so xcodebuild can see it."
|
|
1340
|
-
} };
|
|
1341
|
-
}
|
|
1342
|
-
return {
|
|
1343
|
-
scheme,
|
|
1344
|
-
schemes: listing.schemes
|
|
1345
|
-
};
|
|
1346
|
-
}
|
|
1347
|
-
const PREFIX_MAP_TARGET = "/^src";
|
|
1348
|
-
function prefixMapping(workspaceRoot) {
|
|
1349
|
-
return `${String(workspaceRoot).replace(/\/+$/, "")}=${PREFIX_MAP_TARGET}`;
|
|
1350
|
-
}
|
|
1351
|
-
function compilationPrefixMappings(workspaceRoot, derivedDataPath) {
|
|
1352
|
-
const source = resolve(workspaceRoot);
|
|
1353
|
-
const derived = resolve(derivedDataPath);
|
|
1354
|
-
const mappings = [prefixMapping(source)];
|
|
1355
|
-
const rel = relative(source, derived);
|
|
1356
|
-
if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) mappings.push(`${derived}=/^derived-data`);
|
|
1357
|
-
return mappings.join(" ");
|
|
1358
|
-
}
|
|
1359
|
-
function ccacheEnabled(podfileProperties) {
|
|
1360
|
-
if (!podfileProperties || typeof podfileProperties !== "object") return false;
|
|
1361
|
-
return podfileProperties["apple.ccacheEnabled"] === "true";
|
|
1362
|
-
}
|
|
1363
|
-
function readPodfileProperties(root) {
|
|
1364
|
-
try {
|
|
1365
|
-
const parsed = JSON.parse(readFileSync(join(root, IOS_DIR, "Podfile.properties.json"), "utf-8"));
|
|
1366
|
-
return parsed && typeof parsed === "object" ? parsed : null;
|
|
1367
|
-
} catch {
|
|
1368
|
-
return null;
|
|
1369
|
-
}
|
|
1370
|
-
}
|
|
1371
|
-
function compilationCacheSettings({ workspaceRoot, derivedDataPath, casPath, xcodeMajor, ccache = false, optimizations = resolveOptimizations({}, {}).ios }) {
|
|
1372
|
-
if (xcodeMajor === null || xcodeMajor === void 0) return [];
|
|
1373
|
-
if (xcodeMajor < 26) return [];
|
|
1374
|
-
if (ccache) return [];
|
|
1375
|
-
return [
|
|
1376
|
-
`COMPILATION_CACHE_ENABLE_CACHING=${optimizations.compilationCache ? "YES" : "NO"}`,
|
|
1377
|
-
`COMPILATION_CACHE_CAS_PATH=${casPath}`,
|
|
1378
|
-
`SWIFT_ENABLE_COMPILE_CACHE=${optimizations.compilationCache && optimizations.swiftCompilationCache ? "YES" : "NO"}`,
|
|
1379
|
-
`CLANG_ENABLE_PREFIX_MAPPING=${optimizations.prefixMapping ? "YES" : "NO"}`,
|
|
1380
|
-
`CLANG_OTHER_PREFIX_MAPPINGS=${optimizations.prefixMapping ? compilationPrefixMappings(workspaceRoot, derivedDataPath) : ""}`
|
|
1381
|
-
];
|
|
1382
|
-
}
|
|
1383
|
-
function parseXcodeMajor(output) {
|
|
1384
|
-
const m = /^Xcode\s+(\d+)/m.exec(String(output || ""));
|
|
1385
|
-
if (!m) return null;
|
|
1386
|
-
const digits = m[1];
|
|
1387
|
-
if (digits === void 0) return null;
|
|
1388
|
-
const major = parseInt(digits, 10);
|
|
1389
|
-
return Number.isFinite(major) ? major : null;
|
|
1390
|
-
}
|
|
1391
|
-
function detectXcodeMajor(exec = null) {
|
|
1392
|
-
return parseXcodeMajor((exec || getExecutor()).runQuiet("xcodebuild -version", { timeoutMs: 1e4 }));
|
|
1393
|
-
}
|
|
1394
|
-
function resolveCompilationCacheSettings({ root, derivedDataPath, exec = null, casPath = sharedCompilationCache(), optimizations = resolveOptimizations({}, {}).ios, onNote = (line) => console.error(line) }) {
|
|
1395
|
-
const settings = compilationCacheSettings({
|
|
1396
|
-
workspaceRoot: root,
|
|
1397
|
-
derivedDataPath,
|
|
1398
|
-
casPath,
|
|
1399
|
-
xcodeMajor: detectXcodeMajor(exec),
|
|
1400
|
-
ccache: ccacheEnabled(readPodfileProperties(root)),
|
|
1401
|
-
optimizations
|
|
1402
|
-
});
|
|
1403
|
-
if (settings.length > 0 && optimizations.compilationCache) {
|
|
1404
|
-
register({
|
|
1405
|
-
dir: casPath,
|
|
1406
|
-
name: "Xcode compilation cache",
|
|
1407
|
-
prune: "atomic",
|
|
1408
|
-
note: "shared Xcode compilation cache"
|
|
1409
|
-
});
|
|
1410
|
-
onNote(chalk.dim(phaseLine("cache", `compilation cache on (CAS at ${casPath})`)));
|
|
1411
|
-
}
|
|
1412
|
-
return settings;
|
|
1413
|
-
}
|
|
1414
|
-
function xcodebuildArgs({ project, scheme, udid = null, destination = null, configuration = "Debug", sdk = "iphonesimulator", derivedDataPath, extraArgs = [], buildSettings = [] }) {
|
|
1415
|
-
return [
|
|
1416
|
-
project.flag,
|
|
1417
|
-
project.path,
|
|
1418
|
-
"-scheme",
|
|
1419
|
-
scheme,
|
|
1420
|
-
"-configuration",
|
|
1421
|
-
configuration,
|
|
1422
|
-
"-sdk",
|
|
1423
|
-
sdk,
|
|
1424
|
-
"-destination",
|
|
1425
|
-
destination || `id=${udid}`,
|
|
1426
|
-
"-derivedDataPath",
|
|
1427
|
-
derivedDataPath,
|
|
1428
|
-
...extraArgs,
|
|
1429
|
-
"build",
|
|
1430
|
-
...buildSettings
|
|
1431
|
-
];
|
|
1432
|
-
}
|
|
1433
|
-
function productsDir(derivedDataPath, { configuration = "Debug", sdk = "iphonesimulator" } = {}) {
|
|
1434
|
-
return join(derivedDataPath, "Build", "Products", `${configuration}-${sdk}`);
|
|
1435
|
-
}
|
|
1436
|
-
function pickAppBundle(entries, preferredName = null) {
|
|
1437
|
-
const apps = (Array.isArray(entries) ? entries : []).filter((e) => typeof e === "string" && e.endsWith(".app")).toSorted();
|
|
1438
|
-
if (apps.length === 0) return null;
|
|
1439
|
-
if (preferredName) {
|
|
1440
|
-
const wanted = `${preferredName}.app`;
|
|
1441
|
-
const exact = apps.find((a) => a === wanted);
|
|
1442
|
-
if (exact) return exact;
|
|
1443
|
-
}
|
|
1444
|
-
return apps[0];
|
|
1445
|
-
}
|
|
1446
|
-
function findAppBundle(dir, preferredName = null) {
|
|
1447
|
-
let entries;
|
|
1448
|
-
try {
|
|
1449
|
-
entries = readdirSync(dir);
|
|
1450
|
-
} catch {
|
|
1451
|
-
return null;
|
|
1452
|
-
}
|
|
1453
|
-
const app = pickAppBundle(entries, preferredName);
|
|
1454
|
-
return app ? join(dir, app) : null;
|
|
1455
|
-
}
|
|
1456
|
-
function parseBundleId(plistJson) {
|
|
1457
|
-
if (typeof plistJson !== "string") return null;
|
|
1458
|
-
let data;
|
|
1459
|
-
try {
|
|
1460
|
-
data = JSON.parse(plistJson);
|
|
1461
|
-
} catch {
|
|
1462
|
-
return null;
|
|
1463
|
-
}
|
|
1464
|
-
if (!data || typeof data !== "object") return null;
|
|
1465
|
-
const id = data.CFBundleIdentifier;
|
|
1466
|
-
return typeof id === "string" && id.trim() !== "" ? id.trim() : null;
|
|
1467
|
-
}
|
|
1468
|
-
function readBundleId(appPath, { exec = null } = {}) {
|
|
1469
|
-
const executor = exec || getExecutor();
|
|
1470
|
-
try {
|
|
1471
|
-
const id = parseBundleId(executor.runFile("plutil", [
|
|
1472
|
-
"-convert",
|
|
1473
|
-
"json",
|
|
1474
|
-
"-o",
|
|
1475
|
-
"-",
|
|
1476
|
-
join(appPath, "Info.plist")
|
|
1477
|
-
]));
|
|
1478
|
-
if (id) return id;
|
|
1479
|
-
} catch {}
|
|
1480
|
-
try {
|
|
1481
|
-
const value = executor.runFile("defaults", [
|
|
1482
|
-
"read",
|
|
1483
|
-
join(appPath, "Info"),
|
|
1484
|
-
"CFBundleIdentifier"
|
|
1485
|
-
]);
|
|
1486
|
-
const trimmed = String(value).trim();
|
|
1487
|
-
return trimmed === "" ? null : trimmed;
|
|
1488
|
-
} catch {
|
|
1489
|
-
return null;
|
|
1490
|
-
}
|
|
1491
|
-
}
|
|
1492
|
-
function parseBundleExecutable(plistJson) {
|
|
1493
|
-
if (typeof plistJson !== "string") return null;
|
|
1494
|
-
let data;
|
|
1495
|
-
try {
|
|
1496
|
-
data = JSON.parse(plistJson);
|
|
1497
|
-
} catch {
|
|
1498
|
-
return null;
|
|
1499
|
-
}
|
|
1500
|
-
if (!data || typeof data !== "object") return null;
|
|
1501
|
-
const name = data.CFBundleExecutable;
|
|
1502
|
-
return typeof name === "string" && name.trim() !== "" ? name.trim() : null;
|
|
1503
|
-
}
|
|
1504
|
-
function readBundleExecutable(appPath, { exec = null } = {}) {
|
|
1505
|
-
const executor = exec || getExecutor();
|
|
1506
|
-
try {
|
|
1507
|
-
const name = parseBundleExecutable(executor.runFile("plutil", [
|
|
1508
|
-
"-convert",
|
|
1509
|
-
"json",
|
|
1510
|
-
"-o",
|
|
1511
|
-
"-",
|
|
1512
|
-
join(appPath, "Info.plist")
|
|
1513
|
-
]));
|
|
1514
|
-
if (name) return name;
|
|
1515
|
-
} catch {}
|
|
1516
|
-
try {
|
|
1517
|
-
const value = executor.runFile("defaults", [
|
|
1518
|
-
"read",
|
|
1519
|
-
join(appPath, "Info"),
|
|
1520
|
-
"CFBundleExecutable"
|
|
1521
|
-
]);
|
|
1522
|
-
const trimmed = String(value).trim();
|
|
1523
|
-
return trimmed === "" ? null : trimmed;
|
|
1524
|
-
} catch {
|
|
1525
|
-
return null;
|
|
1526
|
-
}
|
|
1527
|
-
}
|
|
1528
|
-
function tailLines(lines, count = 5) {
|
|
1529
|
-
return (Array.isArray(lines) ? lines : []).filter((l) => typeof l === "string" && l.trim() !== "").slice(-count);
|
|
1530
|
-
}
|
|
1531
|
-
const HEARTBEAT_INTERVAL_MS = 3e4;
|
|
1532
|
-
const HEARTBEAT_ACTIVITY = {
|
|
1533
|
-
build: "compiling",
|
|
1534
|
-
deps: "installing",
|
|
1535
|
-
pods: "installing"
|
|
1536
|
-
};
|
|
1537
|
-
function heartbeatLine(elapsedMs, label = "build", estimateMs = null) {
|
|
1538
|
-
const activity = HEARTBEAT_ACTIVITY[label] ?? "running";
|
|
1539
|
-
const elapsed = formatElapsed(elapsedMs);
|
|
1540
|
-
const inside = estimateMs && estimateMs > 0 ? elapsedMs > estimateMs ? `${elapsed}, usually ~${formatElapsed(estimateMs)}` : `${elapsed} of ~${formatElapsed(estimateMs)}` : elapsed;
|
|
1541
|
-
return phaseLine(label, `still ${activity} (${inside})`);
|
|
1542
|
-
}
|
|
1543
|
-
const defaultSchedule = (run, delayMs) => {
|
|
1544
|
-
const timer = setTimeout(run, delayMs);
|
|
1545
|
-
timer.unref?.();
|
|
1546
|
-
return () => clearTimeout(timer);
|
|
1547
|
-
};
|
|
1548
|
-
function startBuildHeartbeat({ intervalMs, elapsed, emit, label = "build", estimateMs = null, schedule = defaultSchedule }) {
|
|
1549
|
-
if (!(intervalMs > 0)) return () => {};
|
|
1550
|
-
let stopped = false;
|
|
1551
|
-
let lastBeat = 0;
|
|
1552
|
-
let cancel = null;
|
|
1553
|
-
function tick() {
|
|
1554
|
-
if (stopped) return;
|
|
1555
|
-
const ms = elapsed();
|
|
1556
|
-
const beat = Math.floor(ms / intervalMs) * intervalMs;
|
|
1557
|
-
if (beat > lastBeat) {
|
|
1558
|
-
lastBeat = beat;
|
|
1559
|
-
emit(heartbeatLine(beat, label, estimateMs));
|
|
1560
|
-
}
|
|
1561
|
-
cancel = schedule(tick, intervalMs - ms % intervalMs);
|
|
1562
|
-
}
|
|
1563
|
-
cancel = schedule(tick, intervalMs);
|
|
1564
|
-
return () => {
|
|
1565
|
-
stopped = true;
|
|
1566
|
-
cancel?.();
|
|
1567
|
-
};
|
|
1568
|
-
}
|
|
1569
|
-
function failedResult({ code, diagnostics, durationMs, exitCode = null, transcriptLines = 0, tail = [], compilationCache = COMPILATION_CACHE_UNAVAILABLE }) {
|
|
1570
|
-
const capped = capDiagnostics(diagnostics);
|
|
1571
|
-
return {
|
|
1572
|
-
failed: true,
|
|
1573
|
-
code,
|
|
1574
|
-
diagnostics: capped.diagnostics,
|
|
1575
|
-
truncated: capped.truncated,
|
|
1576
|
-
durationMs,
|
|
1577
|
-
exitCode,
|
|
1578
|
-
transcriptLines,
|
|
1579
|
-
tail,
|
|
1580
|
-
compilationCache
|
|
1581
|
-
};
|
|
1582
|
-
}
|
|
1583
|
-
const COMPILATION_CACHE_UNAVAILABLE = {
|
|
1584
|
-
status: "unavailable",
|
|
1585
|
-
hits: null,
|
|
1586
|
-
cacheableTasks: null,
|
|
1587
|
-
hitRatePercent: null
|
|
1588
|
-
};
|
|
1589
|
-
const COMPILATION_CACHE_NOT_RUN = {
|
|
1590
|
-
status: "not-run",
|
|
1591
|
-
hits: null,
|
|
1592
|
-
cacheableTasks: null,
|
|
1593
|
-
hitRatePercent: null
|
|
1594
|
-
};
|
|
1595
|
-
function parseCompilationCacheActivity(line) {
|
|
1596
|
-
if (typeof line !== "string") return null;
|
|
1597
|
-
const match = line.match(/(?:note:\s*)?(\d+) hits \/ (\d+) cacheable tasks \((\d+(?:\.\d+)?)%\)/);
|
|
1598
|
-
if (!match) return null;
|
|
1599
|
-
return {
|
|
1600
|
-
status: "reported",
|
|
1601
|
-
hits: Number(match[1]),
|
|
1602
|
-
cacheableTasks: Number(match[2]),
|
|
1603
|
-
hitRatePercent: Number(match[3])
|
|
1604
|
-
};
|
|
1605
|
-
}
|
|
1606
|
-
function compilationCacheActivityLine(activity) {
|
|
1607
|
-
if (activity.status === "reported") return `${activity.hits}/${activity.cacheableTasks} hits (${activity.hitRatePercent}%)`;
|
|
1608
|
-
if (activity.status === "not-run") return "not run; artifact cache supplied the app";
|
|
1609
|
-
return "unavailable; Xcode did not report reliable statistics";
|
|
1610
|
-
}
|
|
1611
|
-
async function buildIos({ root, udid = null, logWriter, project = null, scheme = null, configuration = "Debug", sdk = "iphonesimulator", destination = null, derivedDataPath = null, extraArgs = [], compilationCache = void 0, optimizations = resolveOptimizations({}, {}).ios, now = () => Date.now(), exec = null, heartbeatMs = HEARTBEAT_INTERVAL_MS, estimateMs = null, onHeartbeat = (line) => console.error(line), onNote = (line) => console.error(line) }) {
|
|
1612
|
-
if (!root || typeof root !== "string") throw new TypeError("buildIos requires {root}");
|
|
1613
|
-
if (!logWriter || typeof logWriter.write !== "function") throw new TypeError("buildIos requires {logWriter} with a write() method");
|
|
1614
|
-
if (!udid && !destination) throw new TypeError("buildIos requires {udid} (or an explicit {destination})");
|
|
1615
|
-
const executor = exec || getExecutor();
|
|
1616
|
-
const dd = derivedDataPath || (scheme ? join(workspaceDerivedData(root), `scheme-${createHash("sha256").update(scheme).digest("hex")}`) : workspaceDerivedData(root));
|
|
1617
|
-
const startedAt = now();
|
|
1618
|
-
const elapsed = () => now() - startedAt;
|
|
1619
|
-
const reportError = (message, remedy) => {
|
|
1620
|
-
logWriter.write({
|
|
1621
|
-
src: "build",
|
|
1622
|
-
level: "error",
|
|
1623
|
-
msg: message,
|
|
1624
|
-
event: "build_diagnostic",
|
|
1625
|
-
...remedy ? { remedy } : {}
|
|
1626
|
-
});
|
|
1627
|
-
};
|
|
1628
|
-
let target = project;
|
|
1629
|
-
if (!target) {
|
|
1630
|
-
const discovered = discoverXcodeProject(root);
|
|
1631
|
-
if (discovered.error) {
|
|
1632
|
-
reportError(discovered.error.message, discovered.error.remedy);
|
|
1633
|
-
return failedResult({
|
|
1634
|
-
code: discovered.error.code,
|
|
1635
|
-
diagnostics: [{
|
|
1636
|
-
message: discovered.error.message,
|
|
1637
|
-
remedy: discovered.error.remedy ?? void 0
|
|
1638
|
-
}],
|
|
1639
|
-
durationMs: elapsed()
|
|
1640
|
-
});
|
|
1641
|
-
}
|
|
1642
|
-
target = discovered;
|
|
1643
|
-
}
|
|
1644
|
-
const resolvedTarget = target;
|
|
1645
|
-
const selected = resolveScheme(resolvedTarget, {
|
|
1646
|
-
exec: executor,
|
|
1647
|
-
scheme
|
|
1648
|
-
});
|
|
1649
|
-
if (selected.error) {
|
|
1650
|
-
reportError(selected.error.message, selected.error.remedy);
|
|
1651
|
-
return failedResult({
|
|
1652
|
-
code: selected.error.code,
|
|
1653
|
-
diagnostics: [{
|
|
1654
|
-
message: selected.error.message,
|
|
1655
|
-
remedy: selected.error.remedy
|
|
1656
|
-
}],
|
|
1657
|
-
durationMs: elapsed()
|
|
1658
|
-
});
|
|
1659
|
-
}
|
|
1660
|
-
const buildScheme = selected.scheme;
|
|
1661
|
-
const buildSettings = compilationCache === void 0 ? resolveCompilationCacheSettings({
|
|
1662
|
-
root,
|
|
1663
|
-
derivedDataPath: dd,
|
|
1664
|
-
exec: executor,
|
|
1665
|
-
onNote,
|
|
1666
|
-
optimizations
|
|
1667
|
-
}) : compilationCache || [];
|
|
1668
|
-
const args = xcodebuildArgs({
|
|
1669
|
-
project: resolvedTarget,
|
|
1670
|
-
scheme: buildScheme,
|
|
1671
|
-
udid,
|
|
1672
|
-
destination,
|
|
1673
|
-
configuration,
|
|
1674
|
-
sdk,
|
|
1675
|
-
derivedDataPath: dd,
|
|
1676
|
-
extraArgs,
|
|
1677
|
-
buildSettings
|
|
1678
|
-
});
|
|
1679
|
-
logWriter.write({
|
|
1680
|
-
src: "build",
|
|
1681
|
-
level: "info",
|
|
1682
|
-
msg: `xcodebuild ${args.join(" ")}`,
|
|
1683
|
-
event: "build_start"
|
|
1684
|
-
});
|
|
1685
|
-
const transcript = [];
|
|
1686
|
-
let compilationCacheActivity = COMPILATION_CACHE_UNAVAILABLE;
|
|
1687
|
-
const onLine = (line) => {
|
|
1688
|
-
const msg = cleanLine(line);
|
|
1689
|
-
transcript.push(msg);
|
|
1690
|
-
if (msg.trim() === "") return;
|
|
1691
|
-
logWriter.write({
|
|
1692
|
-
src: "build",
|
|
1693
|
-
level: "debug",
|
|
1694
|
-
msg
|
|
1695
|
-
});
|
|
1696
|
-
const activity = parseCompilationCacheActivity(msg);
|
|
1697
|
-
if (activity) {
|
|
1698
|
-
compilationCacheActivity = activity;
|
|
1699
|
-
logWriter.write({
|
|
1700
|
-
src: "build",
|
|
1701
|
-
level: "info",
|
|
1702
|
-
msg: compilationCacheActivityLine(activity),
|
|
1703
|
-
event: "compilation_cache",
|
|
1704
|
-
hits: activity.hits,
|
|
1705
|
-
cacheableTasks: activity.cacheableTasks,
|
|
1706
|
-
hitRatePercent: activity.hitRatePercent
|
|
1707
|
-
});
|
|
1708
|
-
}
|
|
1709
|
-
};
|
|
1710
|
-
let child;
|
|
1711
|
-
try {
|
|
1712
|
-
child = executor.spawn("xcodebuild", args, {
|
|
1713
|
-
cwd: resolvedTarget.dir || root,
|
|
1714
|
-
stdio: [
|
|
1715
|
-
"ignore",
|
|
1716
|
-
"pipe",
|
|
1717
|
-
"pipe"
|
|
1718
|
-
],
|
|
1719
|
-
detached: false,
|
|
1720
|
-
env: {
|
|
1721
|
-
...process.env,
|
|
1722
|
-
NSUnbufferedIO: "YES",
|
|
1723
|
-
FORCE_COLOR: "0"
|
|
1724
|
-
}
|
|
1725
|
-
});
|
|
1726
|
-
} catch (err) {
|
|
1727
|
-
const message = `Could not run xcodebuild: ${err.message}`;
|
|
1728
|
-
const remedy = "Install Xcode and select it with `sudo xcode-select -s /Applications/Xcode.app`.";
|
|
1729
|
-
reportError(message, remedy);
|
|
1730
|
-
return failedResult({
|
|
1731
|
-
code: "STIM_BUILD_FAILED",
|
|
1732
|
-
diagnostics: [{
|
|
1733
|
-
message,
|
|
1734
|
-
remedy
|
|
1735
|
-
}],
|
|
1736
|
-
durationMs: elapsed()
|
|
1737
|
-
});
|
|
1738
|
-
}
|
|
1739
|
-
const outReader = createLineReader(onLine);
|
|
1740
|
-
const errReader = createLineReader(onLine);
|
|
1741
|
-
child.stdout?.setEncoding?.("utf-8");
|
|
1742
|
-
child.stderr?.setEncoding?.("utf-8");
|
|
1743
|
-
child.stdout?.on("data", (chunk) => outReader.push(chunk));
|
|
1744
|
-
child.stderr?.on("data", (chunk) => errReader.push(chunk));
|
|
1745
|
-
const stopHeartbeat = startBuildHeartbeat({
|
|
1746
|
-
intervalMs: heartbeatMs,
|
|
1747
|
-
elapsed,
|
|
1748
|
-
emit: onHeartbeat,
|
|
1749
|
-
estimateMs
|
|
1750
|
-
});
|
|
1751
|
-
const outcome = await new Promise((settlePromise) => {
|
|
1752
|
-
let settled = false;
|
|
1753
|
-
const finish = (value) => {
|
|
1754
|
-
if (settled) return;
|
|
1755
|
-
settled = true;
|
|
1756
|
-
outReader.flush();
|
|
1757
|
-
errReader.flush();
|
|
1758
|
-
settlePromise(value);
|
|
1759
|
-
};
|
|
1760
|
-
child.on("close", (code, signal) => finish({
|
|
1761
|
-
code,
|
|
1762
|
-
signal
|
|
1763
|
-
}));
|
|
1764
|
-
child.on("error", (error) => finish({
|
|
1765
|
-
code: null,
|
|
1766
|
-
signal: null,
|
|
1767
|
-
error
|
|
1768
|
-
}));
|
|
1769
|
-
});
|
|
1770
|
-
stopHeartbeat();
|
|
1771
|
-
const durationMs = elapsed();
|
|
1772
|
-
const text = transcript.join("\n");
|
|
1773
|
-
if (outcome.error) {
|
|
1774
|
-
const message = `Could not run xcodebuild: ${outcome.error.message}`;
|
|
1775
|
-
const remedy = "Install Xcode and select it with `sudo xcode-select -s /Applications/Xcode.app`.";
|
|
1776
|
-
reportError(message, remedy);
|
|
1777
|
-
return failedResult({
|
|
1778
|
-
code: "STIM_BUILD_FAILED",
|
|
1779
|
-
diagnostics: [{
|
|
1780
|
-
message,
|
|
1781
|
-
remedy
|
|
1782
|
-
}],
|
|
1783
|
-
durationMs,
|
|
1784
|
-
transcriptLines: transcript.length,
|
|
1785
|
-
tail: tailLines(transcript),
|
|
1786
|
-
compilationCache: compilationCacheActivity
|
|
1787
|
-
});
|
|
1788
|
-
}
|
|
1789
|
-
if (outcome.code !== 0) {
|
|
1790
|
-
const diagnostics = extractXcodeDiagnostics(text, root, sdk);
|
|
1791
|
-
const capped = capDiagnostics(diagnostics);
|
|
1792
|
-
for (const d of capped.diagnostics) reportError(describeDiagnostic(d), d.remedy);
|
|
1793
|
-
if (capped.diagnostics.length === 0) reportError(`xcodebuild exited ${outcome.code ?? `on ${outcome.signal}`} with no recognizable diagnostic; see the transcript above.`, null);
|
|
1794
|
-
return failedResult({
|
|
1795
|
-
code: "STIM_BUILD_FAILED",
|
|
1796
|
-
diagnostics,
|
|
1797
|
-
durationMs,
|
|
1798
|
-
exitCode: outcome.code,
|
|
1799
|
-
transcriptLines: transcript.length,
|
|
1800
|
-
tail: tailLines(transcript),
|
|
1801
|
-
compilationCache: compilationCacheActivity
|
|
1802
|
-
});
|
|
1803
|
-
}
|
|
1804
|
-
const products = productsDir(dd, {
|
|
1805
|
-
configuration,
|
|
1806
|
-
sdk
|
|
1807
|
-
});
|
|
1808
|
-
let appPath = null;
|
|
1809
|
-
if (scheme) try {
|
|
1810
|
-
const targets = JSON.parse(executor.runFile("xcodebuild", [
|
|
1811
|
-
resolvedTarget.flag,
|
|
1812
|
-
resolvedTarget.path,
|
|
1813
|
-
"-scheme",
|
|
1814
|
-
buildScheme,
|
|
1815
|
-
"-configuration",
|
|
1816
|
-
configuration,
|
|
1817
|
-
"-sdk",
|
|
1818
|
-
sdk,
|
|
1819
|
-
"-destination",
|
|
1820
|
-
destination || `id=${udid}`,
|
|
1821
|
-
"-derivedDataPath",
|
|
1822
|
-
dd,
|
|
1823
|
-
...extraArgs,
|
|
1824
|
-
...buildSettings,
|
|
1825
|
-
"-showBuildSettings",
|
|
1826
|
-
"-json"
|
|
1827
|
-
], { timeoutMs: 3e4 }));
|
|
1828
|
-
const paths = /* @__PURE__ */ new Set();
|
|
1829
|
-
for (const item of Array.isArray(targets) ? targets : []) {
|
|
1830
|
-
const settings = item?.buildSettings;
|
|
1831
|
-
if (settings?.PRODUCT_TYPE !== "com.apple.product-type.application" || settings.PLATFORM_NAME !== sdk) continue;
|
|
1832
|
-
const dir = settings.TARGET_BUILD_DIR;
|
|
1833
|
-
const name = settings.FULL_PRODUCT_NAME;
|
|
1834
|
-
if (typeof dir !== "string" || !isAbsolute(dir) || typeof name !== "string" || basename(name) !== name || !name.endsWith(".app")) continue;
|
|
1835
|
-
const path = join(dir, name);
|
|
1836
|
-
if (statSync(path).isDirectory()) paths.add(path);
|
|
1837
|
-
}
|
|
1838
|
-
if (paths.size === 1) appPath = [...paths][0];
|
|
1839
|
-
} catch {}
|
|
1840
|
-
else appPath = findAppBundle(products, buildScheme);
|
|
1841
|
-
if (!appPath) {
|
|
1842
|
-
const message = scheme ? `xcodebuild succeeded, but the built app for scheme ${JSON.stringify(buildScheme)} could not be identified unambiguously from its build settings.` : `xcodebuild reported success but no .app is in ${products}.`;
|
|
1843
|
-
const remedy = "Check that the scheme builds one application target for this platform, not a library, test bundle, or several apps.";
|
|
1844
|
-
reportError(message, remedy);
|
|
1845
|
-
return failedResult({
|
|
1846
|
-
code: "STIM_BUILD_FAILED",
|
|
1847
|
-
diagnostics: [{
|
|
1848
|
-
message,
|
|
1849
|
-
remedy
|
|
1850
|
-
}],
|
|
1851
|
-
durationMs,
|
|
1852
|
-
exitCode: 0,
|
|
1853
|
-
transcriptLines: transcript.length,
|
|
1854
|
-
tail: tailLines(transcript),
|
|
1855
|
-
compilationCache: compilationCacheActivity
|
|
1856
|
-
});
|
|
1857
|
-
}
|
|
1858
|
-
const bundleId = readBundleId(appPath, { exec: executor });
|
|
1859
|
-
if (!bundleId) {
|
|
1860
|
-
const message = `No readable CFBundleIdentifier in ${join(appPath, "Info.plist")}.`;
|
|
1861
|
-
const remedy = "Check PRODUCT_BUNDLE_IDENTIFIER in the target's Debug configuration.";
|
|
1862
|
-
reportError(message, remedy);
|
|
1863
|
-
return failedResult({
|
|
1864
|
-
code: "STIM_BUILD_FAILED",
|
|
1865
|
-
diagnostics: [{
|
|
1866
|
-
message,
|
|
1867
|
-
remedy
|
|
1868
|
-
}],
|
|
1869
|
-
durationMs,
|
|
1870
|
-
exitCode: 0,
|
|
1871
|
-
transcriptLines: transcript.length,
|
|
1872
|
-
tail: tailLines(transcript),
|
|
1873
|
-
compilationCache: compilationCacheActivity
|
|
1874
|
-
});
|
|
1875
|
-
}
|
|
1876
|
-
logWriter.write({
|
|
1877
|
-
src: "build",
|
|
1878
|
-
level: "info",
|
|
1879
|
-
msg: `BUILD SUCCEEDED ${appPath} (${bundleId}) in ${durationMs}ms`,
|
|
1880
|
-
event: "build_done"
|
|
1881
|
-
});
|
|
1882
|
-
return {
|
|
1883
|
-
appPath,
|
|
1884
|
-
bundleId,
|
|
1885
|
-
durationMs,
|
|
1886
|
-
scheme: buildScheme,
|
|
1887
|
-
project: resolvedTarget,
|
|
1888
|
-
derivedDataPath: dd,
|
|
1889
|
-
productsDir: products,
|
|
1890
|
-
transcriptLines: transcript.length,
|
|
1891
|
-
compilationCache: compilationCacheActivity
|
|
1892
|
-
};
|
|
1893
|
-
}
|
|
1894
|
-
//#endregion
|
|
1895
|
-
export { liveOwnedDeviceCount as C, simslimIsOnPath as D, unknownIosRuntimeRefusal as E, ensureOwnedDevice as S, unknownIosDeviceTypeRefusal as T, AvdBootError as _, ccacheEnabled as a, clearIosAdoptionPending as b, discoverXcodeProject as c, resolveScheme as d, startBuildHeartbeat as f, podInstallCommand as g, bundlerPin as h, buildIos as i, readBundleExecutable as l, describeDiagnostic as m, COMPILATION_CACHE_UNAVAILABLE as n, compilationCacheActivityLine as o, tailLines as p, HEARTBEAT_INTERVAL_MS as r, detectXcodeMajor as s, COMPILATION_CACHE_NOT_RUN as t, readBundleId as u, AvdRecoveryError as v, unknownAndroidSystemImageRefusal as w, ensureBooted as x, checkDeviceCapacity as y };
|
|
1136
|
+
export { liveOwnedDeviceCount as a, unknownIosRuntimeRefusal as c, simslimIsOnPath as d, ensureOwnedDevice as i, AvdBootError as l, clearIosAdoptionPending as n, unknownAndroidSystemImageRefusal as o, ensureBooted as r, unknownIosDeviceTypeRefusal as s, checkDeviceCapacity as t, AvdRecoveryError as u };
|