stim 1.3.1 → 1.4.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 +13 -0
- package/dist/{android-CT-PkXUh.mjs → android-BTR1KTMP.mjs} +197 -114
- package/dist/{android-hMj83VpN.mjs → android-BkM4qTaN.mjs} +65 -72
- package/dist/{android-cas-DXorPCi_.mjs → android-cas-BjLRZyks.mjs} +15 -12
- package/dist/{app-install-DIp9riNx.mjs → app-install--PAocWvq.mjs} +2 -2
- package/dist/{build-slots-C4AouyNz.mjs → build-slots-D6sDzOe-.mjs} +7 -8
- package/dist/{cache-manifest-CXR-Y8pH.mjs → cache-manifest-D02TpYjA.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-6vdGngKa.mjs} +51 -51
- package/dist/dependency-state-BMirHEPD.mjs +76 -0
- package/dist/{deps-0Yt0Inly.mjs → deps-Ck4wuTwa.mjs} +1 -1
- package/dist/{dev-client-VfSZNLT4.mjs → dev-client-Cd0v7iZk.mjs} +12 -12
- package/dist/{device-Bbv45Z9Z.mjs → device-CKXLnRRb.mjs} +7 -7
- package/dist/{xcode-xnxyvJkD.mjs → device-CMDFzx7C.mjs} +208 -967
- package/dist/{device-lease-CJwK7OZr.mjs → device-lease-DDJr85OK.mjs} +3 -3
- package/dist/{device-pool-CcBNTvM7.mjs → device-pool-DJvbIBGW.mjs} +4 -4
- package/dist/{device-remote-DAA0Zm6Z.mjs → device-remote-CMfHhoc8.mjs} +7 -6
- package/dist/{doctor-B4EITgQB.mjs → doctor-B_mfURdt.mjs} +536 -114
- package/dist/{error-diagnostics-DwhNBFDU.mjs → error-diagnostics-_VmkqRZo.mjs} +7 -6
- package/dist/{gc-BRu62v4o.mjs → gc-0AyxmECv.mjs} +61 -27
- package/dist/{guide-D95E0u3i.mjs → guide-CGxdmQ0K.mjs} +126 -7
- package/dist/{ios-DAju3TVy.mjs → ios-CtmGLx3X.mjs} +2667 -2607
- package/dist/{ios-device-CWOM_S44.mjs → ios-device-DJOi9FR5.mjs} +1 -1
- package/dist/{logs-CAdwW6c2.mjs → logs-BF3qPA7w.mjs} +5 -5
- package/dist/metro-DFLmaVz8.mjs +99 -0
- package/dist/named-ports-BO7Zpjp0.mjs +137 -0
- package/dist/{ownership-8O073GIC.mjs → ownership-BvSpZVBk.mjs} +5 -5
- package/dist/{ownership-DXcol4oU.mjs → ownership-C08Yzrp7.mjs} +6 -6
- package/dist/ports-BiKqP92J.mjs +39 -0
- package/dist/ports-ClqG8GPt.mjs +90 -0
- package/dist/{project-fKmuIaMI.mjs → project-CBFCwnN5.mjs} +2 -2
- package/dist/{reclaim-CUmwucWG.mjs → reclaim-DpDVo-AP.mjs} +20 -13
- package/dist/{reload-CWpXoOgN.mjs → reload-ByTqg6ZI.mjs} +8 -8
- package/dist/rolldown-runtime-BsOwBuO_.mjs +30 -0
- package/dist/{server-bare-DEzPZLvf.mjs → server-bare-Buk-k8tI.mjs} +3 -6
- package/dist/{server-expo-DizT4OMk.mjs → server-expo-D4P7iTvf.mjs} +3 -3
- package/dist/server-expo-Dho5Raiw.mjs +2 -0
- package/dist/{settings-DEJf6_kJ.mjs → settings-DwfltIXQ.mjs} +2 -2
- package/dist/{slot-launch-CZklomj6.mjs → slot-launch-DeKen9e5.mjs} +4 -4
- package/dist/{start-B5G4NjiA.mjs → start-BpKUHcU_.mjs} +15 -13
- package/dist/state-CDbvmZdt.mjs +131 -0
- package/dist/{stats-BR-kwj0B.mjs → stats-C59QLKm2.mjs} +1 -1
- package/dist/{stats-CaaQr4yN.mjs → stats-k5qMjyxo.mjs} +4 -4
- package/dist/{status-CI9clRSb.mjs → status-Y2ij3dm6.mjs} +12 -11
- package/dist/{stop-CjlgDk0a.mjs → stop-_P2OQwmZ.mjs} +15 -14
- package/dist/{stop-CQhffpmX.mjs → stop-vKmmxhea.mjs} +2 -2
- package/dist/supervisor-run.d.mts +3 -27
- package/dist/supervisor-run.mjs +7 -6
- package/dist/{teardown--gESs9ck.mjs → teardown-KsPpvldq.mjs} +148 -109
- package/dist/{workspace-process-lock-IBj1Towr.mjs → workspace-process-lock-UEIrvZMJ.mjs} +4 -4
- package/dist/workspace-state-Do1e2mOH.mjs +146 -0
- package/dist/{worktree-DtQGO9-s.mjs → worktree-BvcAIMdJ.mjs} +24 -25
- package/dist/{worktree-Eq6TLH8N.mjs → worktree-DWNx4tUg.mjs} +32 -2
- package/dist/xcode-D9tVItl-.mjs +860 -0
- package/package.json +7 -7
- 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
|
@@ -0,0 +1,860 @@
|
|
|
1
|
+
import { t as getExecutor } from "./exec-CyylIdq9.mjs";
|
|
2
|
+
import { r as register } from "./cache-manifest-D02TpYjA.mjs";
|
|
3
|
+
import { d as sharedCompilationCache, m as workspaceDerivedData } from "./workspace-state-Do1e2mOH.mjs";
|
|
4
|
+
import { t as createLineReader } from "./process-output-C7IlIzd7.mjs";
|
|
5
|
+
import { a as formatElapsed, l as phaseLine } from "./command-output-BuIb_ZOI.mjs";
|
|
6
|
+
import { U as resolveOptimizations } from "./settings-DwfltIXQ.mjs";
|
|
7
|
+
import { t as cleanLine } from "./server-expo-D4P7iTvf.mjs";
|
|
8
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
10
|
+
import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
11
|
+
import chalk from "chalk";
|
|
12
|
+
//#region src/engine/bundler.ts
|
|
13
|
+
const COCOAPODS_SPEC = /^ {4}cocoapods \(/m;
|
|
14
|
+
function bundlerPin(root) {
|
|
15
|
+
const gemfile = join(root, "Gemfile");
|
|
16
|
+
const lockfile = join(root, "Gemfile.lock");
|
|
17
|
+
if (!existsSync(gemfile)) return null;
|
|
18
|
+
let lock;
|
|
19
|
+
try {
|
|
20
|
+
lock = readFileSync(lockfile, "utf-8");
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
if (!COCOAPODS_SPEC.test(lock)) return null;
|
|
25
|
+
return {
|
|
26
|
+
gemfile,
|
|
27
|
+
lockfile
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function podInstallCommand(root, ...args) {
|
|
31
|
+
const pod = [
|
|
32
|
+
"pod",
|
|
33
|
+
"install",
|
|
34
|
+
...args
|
|
35
|
+
].join(" ");
|
|
36
|
+
return bundlerPin(root) ? `bundle exec ${pod}` : pod;
|
|
37
|
+
}
|
|
38
|
+
const BUILD_FAILED = "** BUILD FAILED **";
|
|
39
|
+
const POSITIONED = /^(\S[^\t]*?):(\d+):(?:(\d+):)?\s*(?:fatal\s+)?error:\s+(.+)$/;
|
|
40
|
+
const UNPOSITIONED = /^(\S[^\t]*?)?\berror:\s+(.+)$/;
|
|
41
|
+
const LD_ERROR = /^ld:\s+(?!warning:)(.+)$/;
|
|
42
|
+
const UNDEFINED_HEADER = /^(?:ld:\s+)?Undefined symbols?(?:\s+for architecture\s+\S+)?:\s*$/;
|
|
43
|
+
const UNDEFINED_SYMBOL = /^\s+"?([^",]+)"?,\s+referenced from:\s*$/;
|
|
44
|
+
function undefinedSymbolMessage(symbol) {
|
|
45
|
+
return `Undefined symbol: ${symbol}`;
|
|
46
|
+
}
|
|
47
|
+
const PODS_OUT_OF_SYNC = /The sandbox is not in sync with the Podfile\.lock/;
|
|
48
|
+
const SIGNING = [
|
|
49
|
+
/No profiles for '.*' were found/,
|
|
50
|
+
/requires a development team/i,
|
|
51
|
+
/[Cc]ode ?[Ss]igning [Ee]rror/,
|
|
52
|
+
/No signing certificate/i,
|
|
53
|
+
/code signing is required/i,
|
|
54
|
+
/errSecInternalComponent/,
|
|
55
|
+
/Provisioning profile .* doesn't (?:include|match)/i
|
|
56
|
+
];
|
|
57
|
+
const NO_SUCH_SCHEME = /does not contain a scheme named/;
|
|
58
|
+
const CAS_CORRUPT = /not a IncludeTreeRoot node kind/;
|
|
59
|
+
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.";
|
|
60
|
+
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`.";
|
|
61
|
+
function remedyFor(message, root, sdk) {
|
|
62
|
+
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.";
|
|
63
|
+
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.`;
|
|
64
|
+
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.";
|
|
65
|
+
for (const pattern of SIGNING) if (pattern.test(message)) return sdk.startsWith("iphoneos") ? DEVICE_SIGNING_REMEDY : SIMULATOR_SIGNING_REMEDY;
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
function fileFromPrefix(prefix) {
|
|
69
|
+
const trimmed = String(prefix).trim().replace(/:$/, "");
|
|
70
|
+
if (!trimmed) return null;
|
|
71
|
+
const head = (trimmed.split(": ")[0] ?? "").trim();
|
|
72
|
+
if (!head.includes("/")) return null;
|
|
73
|
+
return head;
|
|
74
|
+
}
|
|
75
|
+
function makeDiagnostic({ file = null, line = null, column = null, message }, root, sdk) {
|
|
76
|
+
const text = String(message).trim();
|
|
77
|
+
const out = { message: text };
|
|
78
|
+
if (file) out.file = file;
|
|
79
|
+
if (line !== null && line !== void 0) out.line = line;
|
|
80
|
+
if (column !== null && column !== void 0) out.column = column;
|
|
81
|
+
out.message = text;
|
|
82
|
+
const remedy = remedyFor(text, root, sdk);
|
|
83
|
+
if (remedy) out.remedy = remedy;
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
function dedupeKey(d) {
|
|
87
|
+
return `${d.file || ""}|${d.line || ""}|${d.column || ""}|${d.message}`;
|
|
88
|
+
}
|
|
89
|
+
function extractXcodeDiagnostics(transcript, root = null, sdk = "iphonesimulator") {
|
|
90
|
+
if (typeof transcript !== "string" || transcript === "") return [];
|
|
91
|
+
const lines = transcript.split("\n");
|
|
92
|
+
const out = [];
|
|
93
|
+
const seen = /* @__PURE__ */ new Set();
|
|
94
|
+
const push = (d) => {
|
|
95
|
+
const key = dedupeKey(d);
|
|
96
|
+
if (seen.has(key)) return;
|
|
97
|
+
seen.add(key);
|
|
98
|
+
out.push(d);
|
|
99
|
+
};
|
|
100
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
101
|
+
const rawLine = lines[i];
|
|
102
|
+
if (rawLine === void 0) continue;
|
|
103
|
+
const raw = rawLine.replace(/\r$/, "");
|
|
104
|
+
if (raw.includes(BUILD_FAILED)) break;
|
|
105
|
+
if (UNDEFINED_HEADER.test(raw)) {
|
|
106
|
+
for (let j = i + 1; j < lines.length; j += 1) {
|
|
107
|
+
const symLine = lines[j];
|
|
108
|
+
if (symLine === void 0) continue;
|
|
109
|
+
const sym = UNDEFINED_SYMBOL.exec(symLine.replace(/\r$/, ""));
|
|
110
|
+
if (sym) {
|
|
111
|
+
const symbol = sym[1];
|
|
112
|
+
if (symbol !== void 0) push(makeDiagnostic({ message: undefinedSymbolMessage(symbol) }, root, sdk));
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (/^\s+\S/.test(symLine) && !/^\S/.test(symLine)) continue;
|
|
116
|
+
i = j - 1;
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const ld = LD_ERROR.exec(raw);
|
|
122
|
+
if (ld) {
|
|
123
|
+
push(makeDiagnostic({ message: `ld: ${ld[1]}` }, root, sdk));
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const positioned = POSITIONED.exec(raw);
|
|
127
|
+
if (positioned) {
|
|
128
|
+
const posMsg = positioned[4];
|
|
129
|
+
if (posMsg === void 0) continue;
|
|
130
|
+
push(makeDiagnostic({
|
|
131
|
+
file: positioned[1],
|
|
132
|
+
line: Number(positioned[2]),
|
|
133
|
+
column: positioned[3] === void 0 ? null : Number(positioned[3]),
|
|
134
|
+
message: posMsg
|
|
135
|
+
}, root, sdk));
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const plain = UNPOSITIONED.exec(raw);
|
|
139
|
+
if (plain) {
|
|
140
|
+
const plainMsg = plain[2];
|
|
141
|
+
if (plainMsg === void 0) continue;
|
|
142
|
+
push(makeDiagnostic({
|
|
143
|
+
file: fileFromPrefix(plain[1] || ""),
|
|
144
|
+
message: plainMsg
|
|
145
|
+
}, root, sdk));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
150
|
+
function capDiagnostics(diagnostics, max = 10) {
|
|
151
|
+
const list = Array.isArray(diagnostics) ? diagnostics : [];
|
|
152
|
+
if (list.length <= max) return {
|
|
153
|
+
diagnostics: list.slice(),
|
|
154
|
+
truncated: 0
|
|
155
|
+
};
|
|
156
|
+
return {
|
|
157
|
+
diagnostics: list.slice(0, max),
|
|
158
|
+
truncated: list.length - max
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function describeDiagnostic(diagnostic) {
|
|
162
|
+
if (!diagnostic || typeof diagnostic !== "object") return "";
|
|
163
|
+
const { file, line, column, message } = diagnostic;
|
|
164
|
+
if (!file) return String(message ?? "");
|
|
165
|
+
return `${file}${line ? column ? `:${line}:${column}` : `:${line}` : ""}: ${message}`;
|
|
166
|
+
}
|
|
167
|
+
//#endregion
|
|
168
|
+
//#region src/engine/xcode.ts
|
|
169
|
+
const IOS_DIR = "ios";
|
|
170
|
+
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.";
|
|
171
|
+
function buildFailure(message, remedy) {
|
|
172
|
+
return { error: {
|
|
173
|
+
code: "STIM_BUILD_FAILED",
|
|
174
|
+
message,
|
|
175
|
+
remedy
|
|
176
|
+
} };
|
|
177
|
+
}
|
|
178
|
+
function pickXcodeProject(entries) {
|
|
179
|
+
const names = (Array.isArray(entries) ? entries : []).filter((e) => typeof e === "string");
|
|
180
|
+
const workspaces = names.filter((e) => e.endsWith(".xcworkspace")).toSorted();
|
|
181
|
+
const projects = names.filter((e) => e.endsWith(".xcodeproj")).toSorted();
|
|
182
|
+
if (workspaces.length > 0) {
|
|
183
|
+
const projectNames = new Set(projects.map((p) => basename(p, ".xcodeproj")));
|
|
184
|
+
const file = workspaces.find((w) => projectNames.has(basename(w, ".xcworkspace"))) || workspaces[0];
|
|
185
|
+
if (file === void 0) return null;
|
|
186
|
+
return {
|
|
187
|
+
kind: "workspace",
|
|
188
|
+
flag: "-workspace",
|
|
189
|
+
file,
|
|
190
|
+
name: basename(file, ".xcworkspace")
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
if (projects.length > 0) {
|
|
194
|
+
const file = projects[0];
|
|
195
|
+
if (file === void 0) return null;
|
|
196
|
+
return {
|
|
197
|
+
kind: "project",
|
|
198
|
+
flag: "-project",
|
|
199
|
+
file,
|
|
200
|
+
name: basename(file, ".xcodeproj")
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
function discoverXcodeProject(root) {
|
|
206
|
+
const dir = join(root, IOS_DIR);
|
|
207
|
+
let entries;
|
|
208
|
+
try {
|
|
209
|
+
entries = readdirSync(dir);
|
|
210
|
+
} catch {
|
|
211
|
+
return buildFailure(`No ${IOS_DIR}/ directory in ${root}.`, PREBUILD_REMEDY);
|
|
212
|
+
}
|
|
213
|
+
const picked = pickXcodeProject(entries);
|
|
214
|
+
if (!picked) return buildFailure(`${dir} contains no .xcworkspace and no .xcodeproj.`, PREBUILD_REMEDY);
|
|
215
|
+
return {
|
|
216
|
+
...picked,
|
|
217
|
+
dir,
|
|
218
|
+
path: join(dir, picked.file)
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function parseSchemeList(text) {
|
|
222
|
+
const empty = {
|
|
223
|
+
name: null,
|
|
224
|
+
schemes: []
|
|
225
|
+
};
|
|
226
|
+
if (typeof text !== "string") return empty;
|
|
227
|
+
const start = text.indexOf("{");
|
|
228
|
+
const end = text.lastIndexOf("}");
|
|
229
|
+
if (start < 0 || end <= start) return empty;
|
|
230
|
+
let data;
|
|
231
|
+
try {
|
|
232
|
+
data = JSON.parse(text.slice(start, end + 1));
|
|
233
|
+
} catch {
|
|
234
|
+
return empty;
|
|
235
|
+
}
|
|
236
|
+
if (!data || typeof data !== "object") return empty;
|
|
237
|
+
const record = data;
|
|
238
|
+
const container = record.workspace || record.project;
|
|
239
|
+
if (!container || typeof container !== "object") return empty;
|
|
240
|
+
const info = container;
|
|
241
|
+
const schemes = Array.isArray(info.schemes) ? info.schemes.filter((s) => typeof s === "string" && s.trim() !== "") : [];
|
|
242
|
+
return {
|
|
243
|
+
name: typeof info.name === "string" ? info.name : null,
|
|
244
|
+
schemes
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
const TEST_SCHEME = /(?:UI)?Tests$/;
|
|
248
|
+
function pickScheme(schemes, containerName) {
|
|
249
|
+
const list = (Array.isArray(schemes) ? schemes : []).filter((s) => typeof s === "string" && s.trim() !== "");
|
|
250
|
+
if (list.length === 0) return null;
|
|
251
|
+
const name = typeof containerName === "string" ? containerName.trim() : "";
|
|
252
|
+
if (name) {
|
|
253
|
+
const exact = list.find((s) => s === name);
|
|
254
|
+
if (exact) return exact;
|
|
255
|
+
const insensitive = list.find((s) => s.toLowerCase() === name.toLowerCase());
|
|
256
|
+
if (insensitive) return insensitive;
|
|
257
|
+
}
|
|
258
|
+
const app = list.filter((s) => !TEST_SCHEME.test(s));
|
|
259
|
+
return app.length === 1 ? app[0] : null;
|
|
260
|
+
}
|
|
261
|
+
const LIST_TIMEOUT_MS = 18e4;
|
|
262
|
+
function listSchemes(project, { exec = null } = {}) {
|
|
263
|
+
const executor = exec || getExecutor();
|
|
264
|
+
try {
|
|
265
|
+
return parseSchemeList(executor.runFile("xcodebuild", [
|
|
266
|
+
project.flag,
|
|
267
|
+
project.path,
|
|
268
|
+
"-list",
|
|
269
|
+
"-json"
|
|
270
|
+
], { timeoutMs: LIST_TIMEOUT_MS }));
|
|
271
|
+
} catch {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function resolveScheme(project, { exec = null, scheme: requested = null } = {}) {
|
|
276
|
+
const listing = listSchemes(project, { exec });
|
|
277
|
+
if (listing === null) return { error: {
|
|
278
|
+
code: "STIM_NO_SCHEME",
|
|
279
|
+
message: `Could not list schemes for ${project.path}.`,
|
|
280
|
+
remedy: `Run \`xcodebuild ${project.flag} ${project.path} -list\` to see what it reports.`
|
|
281
|
+
} };
|
|
282
|
+
if (requested !== null) {
|
|
283
|
+
if (listing.schemes.includes(requested)) return {
|
|
284
|
+
scheme: requested,
|
|
285
|
+
schemes: listing.schemes
|
|
286
|
+
};
|
|
287
|
+
return { error: {
|
|
288
|
+
code: "STIM_NO_SCHEME",
|
|
289
|
+
message: `No shared Xcode scheme named ${JSON.stringify(requested)} in ${project.path}. Available schemes: ${listing.schemes.join(", ") || "none"}.`,
|
|
290
|
+
remedy: "Pass an exact available name with --scheme, or share the intended app scheme in Xcode."
|
|
291
|
+
} };
|
|
292
|
+
}
|
|
293
|
+
let scheme = pickScheme(listing.schemes, listing.name || project.name);
|
|
294
|
+
if (!scheme && project.dir) try {
|
|
295
|
+
const app = JSON.parse(readFileSync(join(project.dir, "..", "app.json"), "utf8"));
|
|
296
|
+
scheme = pickScheme(listing.schemes, app?.name);
|
|
297
|
+
} catch {}
|
|
298
|
+
if (!scheme) {
|
|
299
|
+
const found = listing.schemes.length ? listing.schemes.join(", ") : "none";
|
|
300
|
+
return { error: {
|
|
301
|
+
code: "STIM_NO_SCHEME",
|
|
302
|
+
message: `Could not select an app scheme in ${project.path} (schemes: ${found}).`,
|
|
303
|
+
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."
|
|
304
|
+
} };
|
|
305
|
+
}
|
|
306
|
+
return {
|
|
307
|
+
scheme,
|
|
308
|
+
schemes: listing.schemes
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
const PREFIX_MAP_TARGET = "/^src";
|
|
312
|
+
function prefixMapping(workspaceRoot) {
|
|
313
|
+
return `${String(workspaceRoot).replace(/\/+$/, "")}=${PREFIX_MAP_TARGET}`;
|
|
314
|
+
}
|
|
315
|
+
function compilationPrefixMappings(workspaceRoot, derivedDataPath) {
|
|
316
|
+
const source = resolve(workspaceRoot);
|
|
317
|
+
const derived = resolve(derivedDataPath);
|
|
318
|
+
const mappings = [prefixMapping(source)];
|
|
319
|
+
const rel = relative(source, derived);
|
|
320
|
+
if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) mappings.push(`${derived}=/^derived-data`);
|
|
321
|
+
return mappings.join(" ");
|
|
322
|
+
}
|
|
323
|
+
function ccacheEnabled(podfileProperties) {
|
|
324
|
+
if (!podfileProperties || typeof podfileProperties !== "object") return false;
|
|
325
|
+
return podfileProperties["apple.ccacheEnabled"] === "true";
|
|
326
|
+
}
|
|
327
|
+
function readPodfileProperties(root) {
|
|
328
|
+
try {
|
|
329
|
+
const parsed = JSON.parse(readFileSync(join(root, IOS_DIR, "Podfile.properties.json"), "utf-8"));
|
|
330
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
331
|
+
} catch {
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
function compilationCacheSettings({ workspaceRoot, derivedDataPath, casPath, xcodeMajor, ccache = false, optimizations = resolveOptimizations({}, {}).ios }) {
|
|
336
|
+
if (xcodeMajor === null || xcodeMajor === void 0) return [];
|
|
337
|
+
if (xcodeMajor < 26) return [];
|
|
338
|
+
if (ccache) return [];
|
|
339
|
+
return [
|
|
340
|
+
`COMPILATION_CACHE_ENABLE_CACHING=${optimizations.compilationCache ? "YES" : "NO"}`,
|
|
341
|
+
`COMPILATION_CACHE_CAS_PATH=${casPath}`,
|
|
342
|
+
`SWIFT_ENABLE_COMPILE_CACHE=${optimizations.compilationCache && optimizations.swiftCompilationCache ? "YES" : "NO"}`,
|
|
343
|
+
`CLANG_ENABLE_PREFIX_MAPPING=${optimizations.prefixMapping ? "YES" : "NO"}`,
|
|
344
|
+
`CLANG_OTHER_PREFIX_MAPPINGS=${optimizations.prefixMapping ? compilationPrefixMappings(workspaceRoot, derivedDataPath) : ""}`
|
|
345
|
+
];
|
|
346
|
+
}
|
|
347
|
+
function parseXcodeMajor(output) {
|
|
348
|
+
const m = /^Xcode\s+(\d+)/m.exec(String(output || ""));
|
|
349
|
+
if (!m) return null;
|
|
350
|
+
const digits = m[1];
|
|
351
|
+
if (digits === void 0) return null;
|
|
352
|
+
const major = parseInt(digits, 10);
|
|
353
|
+
return Number.isFinite(major) ? major : null;
|
|
354
|
+
}
|
|
355
|
+
function detectXcodeMajor(exec = null) {
|
|
356
|
+
return parseXcodeMajor((exec || getExecutor()).runQuiet("xcodebuild -version", { timeoutMs: 1e4 }));
|
|
357
|
+
}
|
|
358
|
+
function resolveCompilationCacheSettings({ root, derivedDataPath, exec = null, casPath = sharedCompilationCache(), optimizations = resolveOptimizations({}, {}).ios, onNote = (line) => console.error(line) }) {
|
|
359
|
+
const settings = compilationCacheSettings({
|
|
360
|
+
workspaceRoot: root,
|
|
361
|
+
derivedDataPath,
|
|
362
|
+
casPath,
|
|
363
|
+
xcodeMajor: detectXcodeMajor(exec),
|
|
364
|
+
ccache: ccacheEnabled(readPodfileProperties(root)),
|
|
365
|
+
optimizations
|
|
366
|
+
});
|
|
367
|
+
if (settings.length > 0 && optimizations.compilationCache) {
|
|
368
|
+
register({
|
|
369
|
+
dir: casPath,
|
|
370
|
+
name: "Xcode compilation cache",
|
|
371
|
+
prune: "atomic",
|
|
372
|
+
note: "shared Xcode compilation cache"
|
|
373
|
+
});
|
|
374
|
+
onNote(chalk.dim(phaseLine("cache", `compilation cache on (CAS at ${casPath})`)));
|
|
375
|
+
}
|
|
376
|
+
return settings;
|
|
377
|
+
}
|
|
378
|
+
function xcodebuildArgs({ project, scheme, udid = null, destination = null, configuration = "Debug", sdk = "iphonesimulator", derivedDataPath, extraArgs = [], buildSettings = [] }) {
|
|
379
|
+
return [
|
|
380
|
+
project.flag,
|
|
381
|
+
project.path,
|
|
382
|
+
"-scheme",
|
|
383
|
+
scheme,
|
|
384
|
+
"-configuration",
|
|
385
|
+
configuration,
|
|
386
|
+
"-sdk",
|
|
387
|
+
sdk,
|
|
388
|
+
"-destination",
|
|
389
|
+
destination || `id=${udid}`,
|
|
390
|
+
"-derivedDataPath",
|
|
391
|
+
derivedDataPath,
|
|
392
|
+
...extraArgs,
|
|
393
|
+
"build",
|
|
394
|
+
...buildSettings
|
|
395
|
+
];
|
|
396
|
+
}
|
|
397
|
+
function productsDir(derivedDataPath, { configuration = "Debug", sdk = "iphonesimulator" } = {}) {
|
|
398
|
+
return join(derivedDataPath, "Build", "Products", `${configuration}-${sdk}`);
|
|
399
|
+
}
|
|
400
|
+
function pickAppBundle(entries, preferredName = null) {
|
|
401
|
+
const apps = (Array.isArray(entries) ? entries : []).filter((e) => typeof e === "string" && e.endsWith(".app")).toSorted();
|
|
402
|
+
if (apps.length === 0) return null;
|
|
403
|
+
if (preferredName) {
|
|
404
|
+
const wanted = `${preferredName}.app`;
|
|
405
|
+
const exact = apps.find((a) => a === wanted);
|
|
406
|
+
if (exact) return exact;
|
|
407
|
+
}
|
|
408
|
+
return apps[0];
|
|
409
|
+
}
|
|
410
|
+
function findAppBundle(dir, preferredName = null) {
|
|
411
|
+
let entries;
|
|
412
|
+
try {
|
|
413
|
+
entries = readdirSync(dir);
|
|
414
|
+
} catch {
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
const app = pickAppBundle(entries, preferredName);
|
|
418
|
+
return app ? join(dir, app) : null;
|
|
419
|
+
}
|
|
420
|
+
function parseBundleId(plistJson) {
|
|
421
|
+
if (typeof plistJson !== "string") return null;
|
|
422
|
+
let data;
|
|
423
|
+
try {
|
|
424
|
+
data = JSON.parse(plistJson);
|
|
425
|
+
} catch {
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
if (!data || typeof data !== "object") return null;
|
|
429
|
+
const id = data.CFBundleIdentifier;
|
|
430
|
+
return typeof id === "string" && id.trim() !== "" ? id.trim() : null;
|
|
431
|
+
}
|
|
432
|
+
function readBundleId(appPath, { exec = null } = {}) {
|
|
433
|
+
const executor = exec || getExecutor();
|
|
434
|
+
try {
|
|
435
|
+
const id = parseBundleId(executor.runFile("plutil", [
|
|
436
|
+
"-convert",
|
|
437
|
+
"json",
|
|
438
|
+
"-o",
|
|
439
|
+
"-",
|
|
440
|
+
join(appPath, "Info.plist")
|
|
441
|
+
]));
|
|
442
|
+
if (id) return id;
|
|
443
|
+
} catch {}
|
|
444
|
+
try {
|
|
445
|
+
const value = executor.runFile("defaults", [
|
|
446
|
+
"read",
|
|
447
|
+
join(appPath, "Info"),
|
|
448
|
+
"CFBundleIdentifier"
|
|
449
|
+
]);
|
|
450
|
+
const trimmed = String(value).trim();
|
|
451
|
+
return trimmed === "" ? null : trimmed;
|
|
452
|
+
} catch {
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
function parseBundleExecutable(plistJson) {
|
|
457
|
+
if (typeof plistJson !== "string") return null;
|
|
458
|
+
let data;
|
|
459
|
+
try {
|
|
460
|
+
data = JSON.parse(plistJson);
|
|
461
|
+
} catch {
|
|
462
|
+
return null;
|
|
463
|
+
}
|
|
464
|
+
if (!data || typeof data !== "object") return null;
|
|
465
|
+
const name = data.CFBundleExecutable;
|
|
466
|
+
return typeof name === "string" && name.trim() !== "" ? name.trim() : null;
|
|
467
|
+
}
|
|
468
|
+
function readBundleExecutable(appPath, { exec = null } = {}) {
|
|
469
|
+
const executor = exec || getExecutor();
|
|
470
|
+
try {
|
|
471
|
+
const name = parseBundleExecutable(executor.runFile("plutil", [
|
|
472
|
+
"-convert",
|
|
473
|
+
"json",
|
|
474
|
+
"-o",
|
|
475
|
+
"-",
|
|
476
|
+
join(appPath, "Info.plist")
|
|
477
|
+
]));
|
|
478
|
+
if (name) return name;
|
|
479
|
+
} catch {}
|
|
480
|
+
try {
|
|
481
|
+
const value = executor.runFile("defaults", [
|
|
482
|
+
"read",
|
|
483
|
+
join(appPath, "Info"),
|
|
484
|
+
"CFBundleExecutable"
|
|
485
|
+
]);
|
|
486
|
+
const trimmed = String(value).trim();
|
|
487
|
+
return trimmed === "" ? null : trimmed;
|
|
488
|
+
} catch {
|
|
489
|
+
return null;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
function tailLines(lines, count = 5) {
|
|
493
|
+
return (Array.isArray(lines) ? lines : []).filter((l) => typeof l === "string" && l.trim() !== "").slice(-count);
|
|
494
|
+
}
|
|
495
|
+
const HEARTBEAT_INTERVAL_MS = 3e4;
|
|
496
|
+
const HEARTBEAT_ACTIVITY = {
|
|
497
|
+
build: "compiling",
|
|
498
|
+
deps: "installing",
|
|
499
|
+
pods: "installing"
|
|
500
|
+
};
|
|
501
|
+
function heartbeatLine(elapsedMs, label = "build", estimateMs = null) {
|
|
502
|
+
const activity = HEARTBEAT_ACTIVITY[label] ?? "running";
|
|
503
|
+
const elapsed = formatElapsed(elapsedMs);
|
|
504
|
+
const inside = estimateMs && estimateMs > 0 ? elapsedMs > estimateMs ? `${elapsed}, usually ~${formatElapsed(estimateMs)}` : `${elapsed} of ~${formatElapsed(estimateMs)}` : elapsed;
|
|
505
|
+
return phaseLine(label, `still ${activity} (${inside})`);
|
|
506
|
+
}
|
|
507
|
+
const defaultSchedule = (run, delayMs) => {
|
|
508
|
+
const timer = setTimeout(run, delayMs);
|
|
509
|
+
timer.unref?.();
|
|
510
|
+
return () => clearTimeout(timer);
|
|
511
|
+
};
|
|
512
|
+
function startBuildHeartbeat({ intervalMs, elapsed, emit, label = "build", estimateMs = null, schedule = defaultSchedule }) {
|
|
513
|
+
if (!(intervalMs > 0)) return () => {};
|
|
514
|
+
let stopped = false;
|
|
515
|
+
let lastBeat = 0;
|
|
516
|
+
let cancel = null;
|
|
517
|
+
function tick() {
|
|
518
|
+
if (stopped) return;
|
|
519
|
+
const ms = elapsed();
|
|
520
|
+
const beat = Math.floor(ms / intervalMs) * intervalMs;
|
|
521
|
+
if (beat > lastBeat) {
|
|
522
|
+
lastBeat = beat;
|
|
523
|
+
emit(heartbeatLine(beat, label, estimateMs));
|
|
524
|
+
}
|
|
525
|
+
cancel = schedule(tick, intervalMs - ms % intervalMs);
|
|
526
|
+
}
|
|
527
|
+
cancel = schedule(tick, intervalMs);
|
|
528
|
+
return () => {
|
|
529
|
+
stopped = true;
|
|
530
|
+
cancel?.();
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
function failedResult({ code, diagnostics, durationMs, exitCode = null, transcriptLines = 0, tail = [], compilationCache = COMPILATION_CACHE_UNAVAILABLE }) {
|
|
534
|
+
const capped = capDiagnostics(diagnostics);
|
|
535
|
+
return {
|
|
536
|
+
ok: false,
|
|
537
|
+
code,
|
|
538
|
+
diagnostics: capped.diagnostics,
|
|
539
|
+
truncated: capped.truncated,
|
|
540
|
+
durationMs,
|
|
541
|
+
exitCode,
|
|
542
|
+
transcriptLines,
|
|
543
|
+
tail,
|
|
544
|
+
compilationCache
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
const COMPILATION_CACHE_UNAVAILABLE = {
|
|
548
|
+
status: "unavailable",
|
|
549
|
+
hits: null,
|
|
550
|
+
cacheableTasks: null,
|
|
551
|
+
hitRatePercent: null
|
|
552
|
+
};
|
|
553
|
+
const COMPILATION_CACHE_NOT_RUN = {
|
|
554
|
+
status: "not-run",
|
|
555
|
+
hits: null,
|
|
556
|
+
cacheableTasks: null,
|
|
557
|
+
hitRatePercent: null
|
|
558
|
+
};
|
|
559
|
+
function parseCompilationCacheActivity(line) {
|
|
560
|
+
if (typeof line !== "string") return null;
|
|
561
|
+
const match = line.match(/(?:note:\s*)?(\d+) hits \/ (\d+) cacheable tasks \((\d+(?:\.\d+)?)%\)/);
|
|
562
|
+
if (!match) return null;
|
|
563
|
+
return {
|
|
564
|
+
status: "reported",
|
|
565
|
+
hits: Number(match[1]),
|
|
566
|
+
cacheableTasks: Number(match[2]),
|
|
567
|
+
hitRatePercent: Number(match[3])
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
function compilationCacheActivityLine(activity) {
|
|
571
|
+
if (activity.status === "reported") return `${activity.hits}/${activity.cacheableTasks} hits (${activity.hitRatePercent}%)`;
|
|
572
|
+
if (activity.status === "not-run") return "not run; artifact cache supplied the app";
|
|
573
|
+
return "unavailable; Xcode did not report reliable statistics";
|
|
574
|
+
}
|
|
575
|
+
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) }) {
|
|
576
|
+
if (!root || typeof root !== "string") throw new TypeError("buildIos requires {root}");
|
|
577
|
+
if (!logWriter || typeof logWriter.write !== "function") throw new TypeError("buildIos requires {logWriter} with a write() method");
|
|
578
|
+
if (!udid && !destination) throw new TypeError("buildIos requires {udid} (or an explicit {destination})");
|
|
579
|
+
const executor = exec || getExecutor();
|
|
580
|
+
const dd = derivedDataPath || (scheme ? join(workspaceDerivedData(root), `scheme-${createHash("sha256").update(scheme).digest("hex")}`) : workspaceDerivedData(root));
|
|
581
|
+
const startedAt = now();
|
|
582
|
+
const elapsed = () => now() - startedAt;
|
|
583
|
+
const reportError = (message, remedy) => {
|
|
584
|
+
logWriter.write({
|
|
585
|
+
src: "build",
|
|
586
|
+
level: "error",
|
|
587
|
+
msg: message,
|
|
588
|
+
event: "build_diagnostic",
|
|
589
|
+
...remedy ? { remedy } : {}
|
|
590
|
+
});
|
|
591
|
+
};
|
|
592
|
+
let target = project;
|
|
593
|
+
if (!target) {
|
|
594
|
+
const discovered = discoverXcodeProject(root);
|
|
595
|
+
if (discovered.error) {
|
|
596
|
+
reportError(discovered.error.message, discovered.error.remedy);
|
|
597
|
+
return failedResult({
|
|
598
|
+
code: discovered.error.code,
|
|
599
|
+
diagnostics: [{
|
|
600
|
+
message: discovered.error.message,
|
|
601
|
+
remedy: discovered.error.remedy ?? void 0
|
|
602
|
+
}],
|
|
603
|
+
durationMs: elapsed()
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
target = discovered;
|
|
607
|
+
}
|
|
608
|
+
const resolvedTarget = target;
|
|
609
|
+
const selected = resolveScheme(resolvedTarget, {
|
|
610
|
+
exec: executor,
|
|
611
|
+
scheme
|
|
612
|
+
});
|
|
613
|
+
if (selected.error) {
|
|
614
|
+
reportError(selected.error.message, selected.error.remedy);
|
|
615
|
+
return failedResult({
|
|
616
|
+
code: selected.error.code,
|
|
617
|
+
diagnostics: [{
|
|
618
|
+
message: selected.error.message,
|
|
619
|
+
remedy: selected.error.remedy
|
|
620
|
+
}],
|
|
621
|
+
durationMs: elapsed()
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
const buildScheme = selected.scheme;
|
|
625
|
+
const buildSettings = compilationCache === void 0 ? resolveCompilationCacheSettings({
|
|
626
|
+
root,
|
|
627
|
+
derivedDataPath: dd,
|
|
628
|
+
exec: executor,
|
|
629
|
+
onNote,
|
|
630
|
+
optimizations
|
|
631
|
+
}) : compilationCache || [];
|
|
632
|
+
const args = xcodebuildArgs({
|
|
633
|
+
project: resolvedTarget,
|
|
634
|
+
scheme: buildScheme,
|
|
635
|
+
udid,
|
|
636
|
+
destination,
|
|
637
|
+
configuration,
|
|
638
|
+
sdk,
|
|
639
|
+
derivedDataPath: dd,
|
|
640
|
+
extraArgs,
|
|
641
|
+
buildSettings
|
|
642
|
+
});
|
|
643
|
+
logWriter.write({
|
|
644
|
+
src: "build",
|
|
645
|
+
level: "info",
|
|
646
|
+
msg: `xcodebuild ${args.join(" ")}`,
|
|
647
|
+
event: "build_start"
|
|
648
|
+
});
|
|
649
|
+
const transcript = [];
|
|
650
|
+
let compilationCacheActivity = COMPILATION_CACHE_UNAVAILABLE;
|
|
651
|
+
const onLine = (line) => {
|
|
652
|
+
const msg = cleanLine(line);
|
|
653
|
+
transcript.push(msg);
|
|
654
|
+
if (msg.trim() === "") return;
|
|
655
|
+
logWriter.write({
|
|
656
|
+
src: "build",
|
|
657
|
+
level: "debug",
|
|
658
|
+
msg
|
|
659
|
+
});
|
|
660
|
+
const activity = parseCompilationCacheActivity(msg);
|
|
661
|
+
if (activity) {
|
|
662
|
+
compilationCacheActivity = activity;
|
|
663
|
+
logWriter.write({
|
|
664
|
+
src: "build",
|
|
665
|
+
level: "info",
|
|
666
|
+
msg: compilationCacheActivityLine(activity),
|
|
667
|
+
event: "compilation_cache",
|
|
668
|
+
hits: activity.hits,
|
|
669
|
+
cacheableTasks: activity.cacheableTasks,
|
|
670
|
+
hitRatePercent: activity.hitRatePercent
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
let child;
|
|
675
|
+
try {
|
|
676
|
+
child = executor.spawn("xcodebuild", args, {
|
|
677
|
+
cwd: resolvedTarget.dir || root,
|
|
678
|
+
stdio: [
|
|
679
|
+
"ignore",
|
|
680
|
+
"pipe",
|
|
681
|
+
"pipe"
|
|
682
|
+
],
|
|
683
|
+
detached: false,
|
|
684
|
+
env: {
|
|
685
|
+
...process.env,
|
|
686
|
+
NSUnbufferedIO: "YES",
|
|
687
|
+
FORCE_COLOR: "0"
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
} catch (err) {
|
|
691
|
+
const message = `Could not run xcodebuild: ${err.message}`;
|
|
692
|
+
const remedy = "Install Xcode and select it with `sudo xcode-select -s /Applications/Xcode.app`.";
|
|
693
|
+
reportError(message, remedy);
|
|
694
|
+
return failedResult({
|
|
695
|
+
code: "STIM_BUILD_FAILED",
|
|
696
|
+
diagnostics: [{
|
|
697
|
+
message,
|
|
698
|
+
remedy
|
|
699
|
+
}],
|
|
700
|
+
durationMs: elapsed()
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
const outReader = createLineReader(onLine);
|
|
704
|
+
const errReader = createLineReader(onLine);
|
|
705
|
+
child.stdout?.setEncoding?.("utf-8");
|
|
706
|
+
child.stderr?.setEncoding?.("utf-8");
|
|
707
|
+
child.stdout?.on("data", (chunk) => outReader.push(chunk));
|
|
708
|
+
child.stderr?.on("data", (chunk) => errReader.push(chunk));
|
|
709
|
+
const stopHeartbeat = startBuildHeartbeat({
|
|
710
|
+
intervalMs: heartbeatMs,
|
|
711
|
+
elapsed,
|
|
712
|
+
emit: onHeartbeat,
|
|
713
|
+
estimateMs
|
|
714
|
+
});
|
|
715
|
+
const outcome = await new Promise((settlePromise) => {
|
|
716
|
+
let settled = false;
|
|
717
|
+
const finish = (value) => {
|
|
718
|
+
if (settled) return;
|
|
719
|
+
settled = true;
|
|
720
|
+
outReader.flush();
|
|
721
|
+
errReader.flush();
|
|
722
|
+
settlePromise(value);
|
|
723
|
+
};
|
|
724
|
+
child.on("close", (code, signal) => finish({
|
|
725
|
+
code,
|
|
726
|
+
signal
|
|
727
|
+
}));
|
|
728
|
+
child.on("error", (error) => finish({
|
|
729
|
+
code: null,
|
|
730
|
+
signal: null,
|
|
731
|
+
error
|
|
732
|
+
}));
|
|
733
|
+
});
|
|
734
|
+
stopHeartbeat();
|
|
735
|
+
const durationMs = elapsed();
|
|
736
|
+
const text = transcript.join("\n");
|
|
737
|
+
if (outcome.error) {
|
|
738
|
+
const message = `Could not run xcodebuild: ${outcome.error.message}`;
|
|
739
|
+
const remedy = "Install Xcode and select it with `sudo xcode-select -s /Applications/Xcode.app`.";
|
|
740
|
+
reportError(message, remedy);
|
|
741
|
+
return failedResult({
|
|
742
|
+
code: "STIM_BUILD_FAILED",
|
|
743
|
+
diagnostics: [{
|
|
744
|
+
message,
|
|
745
|
+
remedy
|
|
746
|
+
}],
|
|
747
|
+
durationMs,
|
|
748
|
+
transcriptLines: transcript.length,
|
|
749
|
+
tail: tailLines(transcript),
|
|
750
|
+
compilationCache: compilationCacheActivity
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
if (outcome.code !== 0) {
|
|
754
|
+
const diagnostics = extractXcodeDiagnostics(text, root, sdk);
|
|
755
|
+
const capped = capDiagnostics(diagnostics);
|
|
756
|
+
for (const d of capped.diagnostics) reportError(describeDiagnostic(d), d.remedy);
|
|
757
|
+
if (capped.diagnostics.length === 0) reportError(`xcodebuild exited ${outcome.code ?? `on ${outcome.signal}`} with no recognizable diagnostic; see the transcript above.`, null);
|
|
758
|
+
return failedResult({
|
|
759
|
+
code: "STIM_BUILD_FAILED",
|
|
760
|
+
diagnostics,
|
|
761
|
+
durationMs,
|
|
762
|
+
exitCode: outcome.code,
|
|
763
|
+
transcriptLines: transcript.length,
|
|
764
|
+
tail: tailLines(transcript),
|
|
765
|
+
compilationCache: compilationCacheActivity
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
const products = productsDir(dd, {
|
|
769
|
+
configuration,
|
|
770
|
+
sdk
|
|
771
|
+
});
|
|
772
|
+
let appPath = null;
|
|
773
|
+
if (scheme) try {
|
|
774
|
+
const targets = JSON.parse(executor.runFile("xcodebuild", [
|
|
775
|
+
resolvedTarget.flag,
|
|
776
|
+
resolvedTarget.path,
|
|
777
|
+
"-scheme",
|
|
778
|
+
buildScheme,
|
|
779
|
+
"-configuration",
|
|
780
|
+
configuration,
|
|
781
|
+
"-sdk",
|
|
782
|
+
sdk,
|
|
783
|
+
"-destination",
|
|
784
|
+
destination || `id=${udid}`,
|
|
785
|
+
"-derivedDataPath",
|
|
786
|
+
dd,
|
|
787
|
+
...extraArgs,
|
|
788
|
+
...buildSettings,
|
|
789
|
+
"-showBuildSettings",
|
|
790
|
+
"-json"
|
|
791
|
+
], { timeoutMs: 3e4 }));
|
|
792
|
+
const paths = /* @__PURE__ */ new Set();
|
|
793
|
+
for (const item of Array.isArray(targets) ? targets : []) {
|
|
794
|
+
const settings = item?.buildSettings;
|
|
795
|
+
if (settings?.PRODUCT_TYPE !== "com.apple.product-type.application" || settings.PLATFORM_NAME !== sdk) continue;
|
|
796
|
+
const dir = settings.TARGET_BUILD_DIR;
|
|
797
|
+
const name = settings.FULL_PRODUCT_NAME;
|
|
798
|
+
if (typeof dir !== "string" || !isAbsolute(dir) || typeof name !== "string" || basename(name) !== name || !name.endsWith(".app")) continue;
|
|
799
|
+
const path = join(dir, name);
|
|
800
|
+
if (statSync(path).isDirectory()) paths.add(path);
|
|
801
|
+
}
|
|
802
|
+
if (paths.size === 1) appPath = [...paths][0];
|
|
803
|
+
} catch {}
|
|
804
|
+
else appPath = findAppBundle(products, buildScheme);
|
|
805
|
+
if (!appPath) {
|
|
806
|
+
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}.`;
|
|
807
|
+
const remedy = "Check that the scheme builds one application target for this platform, not a library, test bundle, or several apps.";
|
|
808
|
+
reportError(message, remedy);
|
|
809
|
+
return failedResult({
|
|
810
|
+
code: "STIM_BUILD_FAILED",
|
|
811
|
+
diagnostics: [{
|
|
812
|
+
message,
|
|
813
|
+
remedy
|
|
814
|
+
}],
|
|
815
|
+
durationMs,
|
|
816
|
+
exitCode: 0,
|
|
817
|
+
transcriptLines: transcript.length,
|
|
818
|
+
tail: tailLines(transcript),
|
|
819
|
+
compilationCache: compilationCacheActivity
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
const bundleId = readBundleId(appPath, { exec: executor });
|
|
823
|
+
if (!bundleId) {
|
|
824
|
+
const message = `No readable CFBundleIdentifier in ${join(appPath, "Info.plist")}.`;
|
|
825
|
+
const remedy = "Check PRODUCT_BUNDLE_IDENTIFIER in the target's Debug configuration.";
|
|
826
|
+
reportError(message, remedy);
|
|
827
|
+
return failedResult({
|
|
828
|
+
code: "STIM_BUILD_FAILED",
|
|
829
|
+
diagnostics: [{
|
|
830
|
+
message,
|
|
831
|
+
remedy
|
|
832
|
+
}],
|
|
833
|
+
durationMs,
|
|
834
|
+
exitCode: 0,
|
|
835
|
+
transcriptLines: transcript.length,
|
|
836
|
+
tail: tailLines(transcript),
|
|
837
|
+
compilationCache: compilationCacheActivity
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
logWriter.write({
|
|
841
|
+
src: "build",
|
|
842
|
+
level: "info",
|
|
843
|
+
msg: `BUILD SUCCEEDED ${appPath} (${bundleId}) in ${durationMs}ms`,
|
|
844
|
+
event: "build_done"
|
|
845
|
+
});
|
|
846
|
+
return {
|
|
847
|
+
ok: true,
|
|
848
|
+
appPath,
|
|
849
|
+
bundleId,
|
|
850
|
+
durationMs,
|
|
851
|
+
scheme: buildScheme,
|
|
852
|
+
project: resolvedTarget,
|
|
853
|
+
derivedDataPath: dd,
|
|
854
|
+
productsDir: products,
|
|
855
|
+
transcriptLines: transcript.length,
|
|
856
|
+
compilationCache: compilationCacheActivity
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
//#endregion
|
|
860
|
+
export { compilationCacheActivityLine as a, readBundleExecutable as c, startBuildHeartbeat as d, tailLines as f, podInstallCommand as h, ccacheEnabled as i, readBundleId as l, bundlerPin as m, HEARTBEAT_INTERVAL_MS as n, detectXcodeMajor as o, describeDiagnostic as p, buildIos as r, discoverXcodeProject as s, COMPILATION_CACHE_NOT_RUN as t, resolveScheme as u };
|