stim 1.0.0 → 1.1.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.
Files changed (63) hide show
  1. package/README.md +22 -10
  2. package/dist/android-CC5bUer7.mjs +204 -0
  3. package/dist/android-DnZlT3aU.mjs +2279 -0
  4. package/dist/android-cas-DGlTStWu.mjs +1078 -0
  5. package/dist/android-cas-compiler.mjs +1 -1
  6. package/dist/android-xmyGBNy5.mjs +665 -0
  7. package/dist/app-install-oZIQRbdD.mjs +1141 -0
  8. package/dist/build-lock-vE45ZzJZ.mjs +645 -0
  9. package/dist/build-slots-CgacA9zQ.mjs +152 -0
  10. package/dist/{cache-manifest-4tH64LQ9.mjs → cache-manifest-oeH18ERr.mjs} +2 -2
  11. package/dist/cache-manifest.mjs +1 -1
  12. package/dist/cli.mjs +24 -27166
  13. package/dist/collector-run.mjs +6 -4
  14. package/dist/command-output-8wHMEde0.mjs +289 -0
  15. package/dist/{config-7kmtuhO2.mjs → config-D7uu8Gbq.mjs} +43 -12
  16. package/dist/deps-64wVnpZc.mjs +435 -0
  17. package/dist/dev-client-Bvky1hya.mjs +557 -0
  18. package/dist/device-DJKv5Qej.mjs +250 -0
  19. package/dist/device-lease-D-G3SgVe.mjs +335 -0
  20. package/dist/device-pool-BlHXnYYs.mjs +408 -0
  21. package/dist/device-remote-CCgkG82J.mjs +1209 -0
  22. package/dist/doctor-BRF9aAJ3.mjs +530 -0
  23. package/dist/doctor-DlVxTtMq.mjs +1137 -0
  24. package/dist/error-diagnostics-B5IJwv3-.mjs +636 -0
  25. package/dist/gc-DhXdgSdt.mjs +1651 -0
  26. package/dist/guide-DrhrDBo5.mjs +4319 -0
  27. package/dist/ios-BZxKPuqP.mjs +382 -0
  28. package/dist/ios-CK1Vrk6Q.mjs +2721 -0
  29. package/dist/ios-device-CYbuRNXR.mjs +81 -0
  30. package/dist/ios-device-CdSm0GDw.mjs +413 -0
  31. package/dist/logs-Dxoy16Qv.mjs +176 -0
  32. package/dist/logs-query-oqn47Ffx.mjs +273 -0
  33. package/dist/metro-B01H3EuD.mjs +184 -0
  34. package/dist/metro-store-CDDZHtPl.mjs +92 -0
  35. package/dist/ownership-B9QIdQOw.mjs +475 -0
  36. package/dist/ownership-D8HmSv2E.mjs +60 -0
  37. package/dist/ownership-claim-CeJLEpjD.mjs +569 -0
  38. package/dist/{project-Dit1CckW.mjs → project-COj6nzzh.mjs} +12 -3
  39. package/dist/reclaim-BPvN2HlP.mjs +347 -0
  40. package/dist/reload-DYZEBbZy.mjs +332 -0
  41. package/dist/remote-cache-DOi5-zLj.mjs +934 -0
  42. package/dist/{server-bare-BmTVVmBB.mjs → server-bare-BYHqEeyw.mjs} +3 -2
  43. package/dist/{server-expo-CHJe1r6j.mjs → server-expo-BEMr_m6p.mjs} +5 -4
  44. package/dist/server-expo-rb0k-0w7.mjs +2 -0
  45. package/dist/settings-BSwrY4FZ.mjs +687 -0
  46. package/dist/spawn-entry-nCE6m770.mjs +14 -0
  47. package/dist/start-ByCylYtB.mjs +822 -0
  48. package/dist/{state-2nush3MK.mjs → state-IYEY-zi8.mjs} +21 -22
  49. package/dist/stats-DZ4Q5J-7.mjs +62 -0
  50. package/dist/stats-wqxPm7Tv.mjs +307 -0
  51. package/dist/status-Dia0XTqG.mjs +363 -0
  52. package/dist/stop-BApyaF21.mjs +591 -0
  53. package/dist/stop-D6o5zAdm.mjs +3 -0
  54. package/dist/supervisor-run.d.mts +0 -1
  55. package/dist/supervisor-run.mjs +4 -4
  56. package/dist/workspace-process-lock-CxOO9-pf.mjs +47 -0
  57. package/dist/worktree-Dx3ZC5lS.mjs +539 -0
  58. package/dist/worktree-DxEmUI_n.mjs +1322 -0
  59. package/dist/xcode-CDB6fytw.mjs +1705 -0
  60. package/package.json +5 -5
  61. package/dist/android-jc8MnUyL.mjs +0 -514
  62. package/dist/metro-store-COLl1pOk.mjs +0 -1249
  63. package/dist/server-expo-BZJm--wA.mjs +0 -2
@@ -0,0 +1,687 @@
1
+ import { d as getConfigPath, g as loadConfig, m as getRepoSettings, p as getProjectSettings } from "./config-D7uu8Gbq.mjs";
2
+ import { g as repoRoot, s as gitCommonDir } from "./worktree-Dx3ZC5lS.mjs";
3
+ import { createHash } from "node:crypto";
4
+ import { isAbsolute } from "node:path";
5
+ import { existsSync, readFileSync, realpathSync, statSync } from "fs";
6
+ import { isAbsolute as isAbsolute$1, join as join$1, relative as relative$1, resolve as resolve$1, sep as sep$1 } from "path";
7
+ //#region src/optimizations.ts
8
+ const OPTIMIZATION_SHAPES = {
9
+ optimizations: "object",
10
+ "optimizations.buildCache": "boolean",
11
+ "optimizations.remoteBuildCache": "boolean",
12
+ "optimizations.releaseBundleSwap": "boolean",
13
+ "optimizations.metroSharedCache": "boolean",
14
+ "optimizations.ios": "object",
15
+ "optimizations.ios.compilationCache": "boolean",
16
+ "optimizations.ios.swiftCompilationCache": "boolean",
17
+ "optimizations.ios.prefixMapping": "boolean",
18
+ "optimizations.android": "object",
19
+ "optimizations.android.compilerCache": "string",
20
+ "optimizations.android.casToolchain": "path",
21
+ "optimizations.android.pch": "string",
22
+ "optimizations.android.gradleBuildCache": "boolean",
23
+ "optimizations.android.targetAbiOnly": "boolean"
24
+ };
25
+ function compilerCacheFallbackMessage({ fallback, compilerCache, file = null }) {
26
+ const origin = fallback.fromEnvironment ? "the environment" : file;
27
+ const backend = compilerCache === "none" ? "Android builds use no compiler cache, because optimizations.android.compilerCache is \"none\"." : "Android builds fall back to ccache when it is available.";
28
+ return `${fallback.key}${origin ? ` in ${origin}` : ""} ${fallback.reason}. ${backend}`;
29
+ }
30
+ function optimizationBuildProfile(platform, options) {
31
+ const selected = platform === "ios" ? options.ios : { pch: options.android.pch };
32
+ const defaults = platform === "ios" ? {
33
+ compilationCache: true,
34
+ swiftCompilationCache: false,
35
+ prefixMapping: true
36
+ } : { pch: "auto" };
37
+ if (JSON.stringify(selected) === JSON.stringify(defaults)) return void 0;
38
+ return `opt-${createHash("sha256").update(JSON.stringify(selected)).digest("hex").slice(0, 16)}`;
39
+ }
40
+ function resolveOptimizations(settings = {}, env = process.env) {
41
+ function choice(path, choices, fallback) {
42
+ const raw = optimizationValue(settings, path);
43
+ if (raw === void 0) return fallback;
44
+ if (typeof raw !== "string" || !choices.includes(raw)) throw new Error(`Invalid optimizations.${path}. Expected ${choices.join(", ")}.`);
45
+ return raw;
46
+ }
47
+ const compiler = choice("android.compilerCache", [
48
+ "auto",
49
+ "ccache",
50
+ "cas",
51
+ "none"
52
+ ], "auto");
53
+ const fromEnvironment = Boolean(env.STIM_ANDROID_CAS_TOOLCHAIN);
54
+ const manifest = env.STIM_ANDROID_CAS_TOOLCHAIN || optimizationValue(settings, "android.casToolchain");
55
+ let fallback = null;
56
+ let toolchain = null;
57
+ if (manifest !== void 0) {
58
+ if (typeof manifest === "string" && isAbsolute(manifest) && !/[\r\n\0]/.test(manifest)) toolchain = manifest;
59
+ else fallback = {
60
+ key: fromEnvironment ? "STIM_ANDROID_CAS_TOOLCHAIN" : "optimizations.android.casToolchain",
61
+ reason: `is ${JSON.stringify(manifest)}, which is not an absolute path to a toolchain JSON manifest`,
62
+ fromEnvironment,
63
+ manifest: null
64
+ };
65
+ }
66
+ let compilerCache = compiler === "auto" ? toolchain ? "cas" : "ccache" : compiler;
67
+ if (compilerCache === "cas" && !toolchain) {
68
+ fallback ??= {
69
+ key: "optimizations.android.compilerCache",
70
+ reason: "is \"cas\", but no optimizations.android.casToolchain or STIM_ANDROID_CAS_TOOLCHAIN names the toolchain manifest",
71
+ fromEnvironment: false,
72
+ manifest: null
73
+ };
74
+ compilerCache = "ccache";
75
+ }
76
+ return {
77
+ buildCache: optimizationBoolean(settings, "buildCache"),
78
+ remoteBuildCache: optimizationBoolean(settings, "remoteBuildCache"),
79
+ releaseBundleSwap: optimizationBoolean(settings, "releaseBundleSwap"),
80
+ metroSharedCache: optimizationBoolean(settings, "metroSharedCache"),
81
+ ios: {
82
+ compilationCache: optimizationBoolean(settings, "ios.compilationCache"),
83
+ swiftCompilationCache: optimizationBoolean(settings, "ios.swiftCompilationCache", false),
84
+ prefixMapping: optimizationBoolean(settings, "ios.prefixMapping")
85
+ },
86
+ android: {
87
+ compilerCache,
88
+ casToolchain: toolchain,
89
+ compilerCacheFallback: fallback,
90
+ pch: choice("android.pch", [
91
+ "auto",
92
+ "on",
93
+ "off"
94
+ ], "auto"),
95
+ gradleBuildCache: optimizationBoolean(settings, "android.gradleBuildCache"),
96
+ targetAbiOnly: optimizationBoolean(settings, "android.targetAbiOnly")
97
+ }
98
+ };
99
+ }
100
+ function artifactCachePolicy(options, reuse, release) {
101
+ return {
102
+ read: reuse && options.buildCache && (!release || options.releaseBundleSwap),
103
+ write: options.buildCache,
104
+ remote: options.buildCache && options.remoteBuildCache
105
+ };
106
+ }
107
+ function optimizationValue(settings, path) {
108
+ let node = settings;
109
+ for (const key of ["optimizations", ...path.split(".")]) {
110
+ if (node === void 0) return void 0;
111
+ if (!node || typeof node !== "object" || Array.isArray(node)) throw new Error("Invalid optimizations setting. Expected nested objects.");
112
+ node = node[key];
113
+ }
114
+ return node;
115
+ }
116
+ function optimizationBoolean(settings, path, fallback = true) {
117
+ const raw = optimizationValue(settings, path);
118
+ if (raw === void 0) return fallback;
119
+ if (typeof raw !== "boolean") throw new Error(`Invalid optimizations.${path}. Expected true or false.`);
120
+ return raw;
121
+ }
122
+ function resolveMetroSharedCache(settings) {
123
+ return optimizationBoolean(settings, "metroSharedCache");
124
+ }
125
+ //#endregion
126
+ //#region src/engine/metro-reach.ts
127
+ const TUNNEL_MODES = [
128
+ "auto",
129
+ "off",
130
+ "expo",
131
+ "cloudflared",
132
+ "ngrok"
133
+ ];
134
+ const PUBLIC_METRO_ENV = "STIM_METRO_PUBLIC_URL";
135
+ const MANAGED_PROVIDERS = ["ngrok", "cloudflared"];
136
+ const NAMED = {
137
+ cloudflared: "`cloudflared` (brew install cloudflared)",
138
+ ngrok: "`ngrok` (brew install ngrok, then `ngrok config add-authtoken <token>`)"
139
+ };
140
+ function planMetroReach({ mode, metroPort, publicUrl = null, isExpo, available = [] }) {
141
+ const named = publicUrl?.trim().replace(/\/+$/, "") || null;
142
+ if (mode === "off") return {
143
+ origin: `http://localhost:${metroPort}`,
144
+ gate: false
145
+ };
146
+ if (named) return {
147
+ origin: named,
148
+ gate: true
149
+ };
150
+ if (mode === "expo") {
151
+ if (!isExpo) return {
152
+ failed: "metro.tunnel is \"expo\", but this workspace does not run an Expo dev server.",
153
+ remedy: "Use \"auto\" to let stim start a tunnel, or \"off\" if the device shares this machine."
154
+ };
155
+ return { expoTunnel: true };
156
+ }
157
+ if (mode === "cloudflared" || mode === "ngrok") {
158
+ if (!available.includes(mode)) return {
159
+ failed: `metro.tunnel is "${mode}", but ${mode} is not on PATH.`,
160
+ remedy: `Install ${NAMED[mode] ?? mode}, or set metro.tunnel to "auto".`
161
+ };
162
+ return { start: mode };
163
+ }
164
+ const provider = available[0];
165
+ if (provider) return { start: provider };
166
+ return {
167
+ failed: `A remote device cannot reach this workspace's Metro on port ${metroPort}, and no tunnel is available to give it one.`,
168
+ remedy: `Install ${NAMED.ngrok} or ${NAMED.cloudflared} and Stim will manage the tunnel for you. If you already have one, set metro.publicUrl to its URL. If the device shares this machine (a local \`agent-device proxy\`), set metro.tunnel to "off".`
169
+ };
170
+ }
171
+ function detectProviders(onPath) {
172
+ return MANAGED_PROVIDERS.filter((p) => onPath(p));
173
+ }
174
+ //#endregion
175
+ //#region src/settings.ts
176
+ function isPlainObject(v) {
177
+ return v !== null && typeof v === "object" && !Array.isArray(v);
178
+ }
179
+ function mergeSettingsLayers(layers) {
180
+ const out = {};
181
+ for (const layer of layers) {
182
+ if (!isPlainObject(layer)) continue;
183
+ for (const [key, value] of Object.entries(layer)) if (isPlainObject(value) && isPlainObject(out[key])) out[key] = mergeSettingsLayers([out[key], value]);
184
+ else if (!(key in out)) out[key] = value;
185
+ }
186
+ return out;
187
+ }
188
+ const SETTING_SHAPE_RULES = {
189
+ boolean: {
190
+ expected: "true or false",
191
+ accepts: (value) => typeof value === "boolean"
192
+ },
193
+ string: {
194
+ expected: "a string",
195
+ accepts: (value) => typeof value === "string"
196
+ },
197
+ path: {
198
+ expected: "a string path",
199
+ accepts: (value) => typeof value === "string"
200
+ },
201
+ strings: {
202
+ expected: "an array of strings",
203
+ accepts: (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string")
204
+ },
205
+ number: {
206
+ expected: "a number",
207
+ accepts: (value) => typeof value === "number"
208
+ },
209
+ object: {
210
+ expected: "an object",
211
+ accepts: isPlainObject
212
+ }
213
+ };
214
+ const SETTING_SHAPES = {
215
+ ...OPTIMIZATION_SHAPES,
216
+ "ios.deviceType": "string",
217
+ "ios.runtime": "string",
218
+ "ios.configuration": "string",
219
+ "ios.remote": "string",
220
+ "ios.simslimProfile": "path",
221
+ "ios.signingIdentity": "string",
222
+ "ios.signingIdentitySha1": "string",
223
+ "ios.lanHost": "string",
224
+ "android.systemImage": "string",
225
+ "android.dataPartitionSizeGb": "number",
226
+ "android.avdConfigFile": "path",
227
+ "android.avdConfig": "object",
228
+ "android.variant": "string",
229
+ "android.keystore": "path",
230
+ "android.keystorePassword": "string",
231
+ "android.remote": "string",
232
+ "metro.tunnel": "string",
233
+ "metro.ngrokUrl": "string",
234
+ "metro.publicUrl": "string",
235
+ "worktree.exclude": "strings",
236
+ "worktree.defaultBranch": "string",
237
+ "cache.provider": "string",
238
+ "cache.options": "object",
239
+ caches: "strings"
240
+ };
241
+ const KNOWN_SETTINGS = new Set(Object.keys(SETTING_SHAPES));
242
+ const SETTINGS_WITH_RESOLVE_TIME_FALLBACK = /* @__PURE__ */ new Set(["optimizations.android.casToolchain"]);
243
+ const PATH_SETTINGS = Object.freeze(Object.entries(SETTING_SHAPES).filter(([, shape]) => shape === "path").map(([path]) => path));
244
+ function settingValueAt(settings, path) {
245
+ let node = settings;
246
+ for (const segment of path.split(".")) {
247
+ if (!isPlainObject(node)) return void 0;
248
+ node = node[segment];
249
+ }
250
+ return node;
251
+ }
252
+ function settingShapeErrors(settings) {
253
+ const errors = [];
254
+ for (const [path, shape] of Object.entries(SETTING_SHAPES)) {
255
+ if (SETTINGS_WITH_RESOLVE_TIME_FALLBACK.has(path)) continue;
256
+ const value = settingValueAt(settings, path);
257
+ if (value === void 0) continue;
258
+ const rule = SETTING_SHAPE_RULES[shape];
259
+ if (rule.accepts(value)) continue;
260
+ errors.push(`Invalid ${path} setting ${JSON.stringify(value)}. Expected ${rule.expected}.`);
261
+ }
262
+ return errors;
263
+ }
264
+ const SETTING_SHAPE_REMEDY = "Run `stim guide settings` for the shape each setting takes.";
265
+ const MAX_ANDROID_DATA_PARTITION_SIZE_GB = 16384;
266
+ function validateAndroidDataPartitionSizeGb(raw) {
267
+ if (typeof raw !== "number" || !Number.isSafeInteger(raw) || raw < 6 || raw > 16384) throw new Error(`Invalid android.dataPartitionSizeGb setting ${JSON.stringify(raw)}. Expected an integer from 6 through ${MAX_ANDROID_DATA_PARTITION_SIZE_GB} GiB.`);
268
+ return raw;
269
+ }
270
+ function androidDataPartitionSizeBytes(sizeGb) {
271
+ return validateAndroidDataPartitionSizeGb(sizeGb) * 1024 ** 3;
272
+ }
273
+ function androidDataPartitionSizeGbRaw(settings) {
274
+ if (!isPlainObject(settings) || !isPlainObject(settings.android)) return void 0;
275
+ return settings.android.dataPartitionSizeGb;
276
+ }
277
+ function androidDataPartitionSizeGbSetting(settings) {
278
+ const raw = androidDataPartitionSizeGbRaw(settings);
279
+ if (raw === void 0) return 8;
280
+ return validateAndroidDataPartitionSizeGb(raw);
281
+ }
282
+ function androidDataPartitionSizeGbSettingError(settings) {
283
+ try {
284
+ androidDataPartitionSizeGbSetting(settings);
285
+ return null;
286
+ } catch (error) {
287
+ return String(error?.message || error);
288
+ }
289
+ }
290
+ function yesNoAvdValue(value) {
291
+ if (value === true || value === "true" || value === "yes") return "yes";
292
+ if (value === false || value === "false" || value === "no") return "no";
293
+ throw new Error("expected true, false, \"yes\", or \"no\"");
294
+ }
295
+ function boundedIntegerAvdRule(min, max, unit) {
296
+ return {
297
+ help: `integer ${min}-${max}${unit ? ` ${unit}` : ""}`,
298
+ parse: (value) => {
299
+ const parsed = typeof value === "number" ? value : typeof value === "string" && /^(0|[1-9]\d*)$/.test(value) ? Number(value) : NaN;
300
+ if (!Number.isSafeInteger(parsed) || parsed < min || parsed > max) throw new Error(`expected an integer from ${min} through ${max}${unit ? ` ${unit}` : ""}`);
301
+ return String(parsed);
302
+ }
303
+ };
304
+ }
305
+ function enumAvdRule(values) {
306
+ return {
307
+ help: values.join("|"),
308
+ parse: (value) => {
309
+ if (typeof value !== "string" || !values.includes(value)) throw new Error(`expected one of: ${values.join(", ")}`);
310
+ return value;
311
+ }
312
+ };
313
+ }
314
+ const ANDROID_AVD_CONFIG_RULES = {
315
+ "hw.cpu.ncore": boundedIntegerAvdRule(1, 64),
316
+ "hw.gpu.mode": enumAvdRule([
317
+ "auto",
318
+ "host",
319
+ "software",
320
+ "lavapipe",
321
+ "swiftshader",
322
+ "swangle"
323
+ ]),
324
+ "hw.initialOrientation": enumAvdRule(["portrait", "landscape"]),
325
+ "hw.lcd.density": boundedIntegerAvdRule(72, 1e3, "dpi"),
326
+ "hw.lcd.vsync": boundedIntegerAvdRule(1, 1e3, "Hz"),
327
+ "hw.ramSize": boundedIntegerAvdRule(1536, 8192, "MB"),
328
+ "hw.screen": enumAvdRule([
329
+ "no-touch",
330
+ "touch",
331
+ "multi-touch"
332
+ ]),
333
+ "runtime.network.latency": enumAvdRule([
334
+ "none",
335
+ "gsm",
336
+ "hscsd",
337
+ "gprs",
338
+ "edge",
339
+ "umts",
340
+ "hsdpa",
341
+ "lte",
342
+ "evdo",
343
+ "5g"
344
+ ]),
345
+ "runtime.network.speed": enumAvdRule([
346
+ "gsm",
347
+ "hscsd",
348
+ "gprs",
349
+ "edge",
350
+ "umts",
351
+ "hsdpa",
352
+ "lte",
353
+ "evdo",
354
+ "5g",
355
+ "full"
356
+ ]),
357
+ "vm.heapSize": boundedIntegerAvdRule(16, 4096, "MB")
358
+ };
359
+ const YES_NO_AVD_RULE = {
360
+ help: "true|false|yes|no",
361
+ parse: yesNoAvdValue
362
+ };
363
+ for (const key of [
364
+ "hw.accelerometer",
365
+ "hw.accelerometer_uncalibrated",
366
+ "hw.audioInput",
367
+ "hw.audioOutput",
368
+ "hw.battery",
369
+ "hw.dPad",
370
+ "hw.gps",
371
+ "hw.gpu.enabled",
372
+ "hw.gyroscope",
373
+ "hw.keyboard",
374
+ "hw.mainKeys",
375
+ "hw.rotaryInput",
376
+ "hw.trackBall",
377
+ "showDeviceFrame"
378
+ ]) ANDROID_AVD_CONFIG_RULES[key] = YES_NO_AVD_RULE;
379
+ const ANDROID_AVD_CONFIG_KEYS = Object.freeze(Object.keys(ANDROID_AVD_CONFIG_RULES).toSorted());
380
+ const ANDROID_AVD_CONFIG_HELP = Object.freeze(ANDROID_AVD_CONFIG_KEYS.map((key) => `${key}: ${ANDROID_AVD_CONFIG_RULES[key].help}`));
381
+ function isSafeAndroidAvdConfigKey(key) {
382
+ return Object.hasOwn(ANDROID_AVD_CONFIG_RULES, key);
383
+ }
384
+ function normalizedAndroidAvdConfigEntry(key, value) {
385
+ if (typeof key !== "string" || key !== key.trim() || !isSafeAndroidAvdConfigKey(key)) throw new Error(`Unsupported android.avdConfig key ${JSON.stringify(key)}. Use a documented safe hardware override; identity, path, storage, image, snapshot, and boot-lifecycle keys are protected.`);
386
+ if (typeof value === "string" && /[\r\n\0]/.test(value)) throw new Error(`Invalid android.avdConfig value for ${key}: expected one line.`);
387
+ try {
388
+ return [key, ANDROID_AVD_CONFIG_RULES[key].parse(value)];
389
+ } catch (error) {
390
+ throw new Error(`Invalid android.avdConfig value for ${key}: ${String(error?.message || error)}.`, { cause: error });
391
+ }
392
+ }
393
+ function parseAndroidAvdConfigIni(contents) {
394
+ const entries = {};
395
+ const lines = contents.replace(/^\uFEFF/, "").split(/\r?\n/);
396
+ for (let index = 0; index < lines.length; index++) {
397
+ const line = String(lines[index] ?? "").trim();
398
+ if (!line || line.startsWith("#") || line.startsWith(";")) continue;
399
+ const separator = line.indexOf("=");
400
+ if (separator <= 0) throw new Error(`Invalid android.avdConfigFile line ${index + 1}: expected key=value.`);
401
+ const [key, value] = normalizedAndroidAvdConfigEntry(line.slice(0, separator), line.slice(separator + 1).trim());
402
+ if (Object.hasOwn(entries, key)) throw new Error(`Invalid android.avdConfigFile line ${index + 1}: duplicate key ${key}.`);
403
+ entries[key] = value;
404
+ }
405
+ return entries;
406
+ }
407
+ function pathEscapesRoot(root, path) {
408
+ const child = relative$1(root, path);
409
+ return child === ".." || child.startsWith(`..${sep$1}`) || isAbsolute$1(child);
410
+ }
411
+ function androidAvdConfigSetting(settings, settingsRoot) {
412
+ if (!isPlainObject(settings) || !isPlainObject(settings.android)) return {};
413
+ const android = settings.android;
414
+ let entries = {};
415
+ if ("avdConfigFile" in android) {
416
+ if (typeof android.avdConfigFile !== "string" || !android.avdConfigFile.trim() || android.avdConfigFile !== android.avdConfigFile.trim() || /[\r\n\0]/.test(android.avdConfigFile) || isAbsolute$1(android.avdConfigFile)) throw new Error("Invalid android.avdConfigFile setting. Expected a relative file path inside the app directory.");
417
+ try {
418
+ const root = realpathSync(settingsRoot);
419
+ const candidate = resolve$1(root, android.avdConfigFile);
420
+ if (pathEscapesRoot(root, candidate)) throw new Error("path escapes the settings root");
421
+ const path = realpathSync(candidate);
422
+ if (pathEscapesRoot(root, path)) throw new Error("symlink target escapes the settings root");
423
+ const stat = statSync(path);
424
+ if (!stat.isFile()) throw new Error("path is not a regular file");
425
+ if (stat.size > 65536) throw new Error("file exceeds 64 KiB");
426
+ const contents = readFileSync(path, "utf8");
427
+ if (Buffer.byteLength(contents) > 65536) throw new Error("file exceeds 64 KiB");
428
+ entries = parseAndroidAvdConfigIni(contents);
429
+ } catch (error) {
430
+ throw new Error(`Could not read valid android.avdConfigFile ${android.avdConfigFile}: ${String(error?.message || error)}`, { cause: error });
431
+ }
432
+ }
433
+ if ("avdConfig" in android) {
434
+ if (!isPlainObject(android.avdConfig)) throw new Error("Invalid android.avdConfig setting. Expected an object of native AVD key/value pairs.");
435
+ for (const [key, value] of Object.entries(android.avdConfig)) {
436
+ const [normalizedKey, normalizedValue] = normalizedAndroidAvdConfigEntry(key, value);
437
+ entries[normalizedKey] = normalizedValue;
438
+ }
439
+ }
440
+ return entries;
441
+ }
442
+ function androidAvdConfigSettingError(settings, projectPath) {
443
+ try {
444
+ androidAvdConfigSetting(settings, projectPath);
445
+ return null;
446
+ } catch (error) {
447
+ return String(error?.message || error);
448
+ }
449
+ }
450
+ function iosSimSlimProfileSetting(settings, settingsRoot) {
451
+ if (!isPlainObject(settings) || !isPlainObject(settings.ios) || !("simslimProfile" in settings.ios)) return null;
452
+ const value = settings.ios.simslimProfile;
453
+ if (typeof value !== "string" || !value.trim() || value !== value.trim() || /[\r\n\0]/.test(value) || isAbsolute$1(value)) throw new Error("Invalid ios.simslimProfile setting. Expected a relative JSON file path inside the app directory.");
454
+ try {
455
+ const root = realpathSync(settingsRoot);
456
+ const candidate = resolve$1(root, value);
457
+ if (pathEscapesRoot(root, candidate)) throw new Error("path escapes the settings root");
458
+ const path = realpathSync(candidate);
459
+ if (pathEscapesRoot(root, path)) throw new Error("symlink target escapes the settings root");
460
+ const stat = statSync(path);
461
+ if (!stat.isFile()) throw new Error("path is not a regular file");
462
+ if (stat.size > 65536) throw new Error("file exceeds 64 KiB");
463
+ return path;
464
+ } catch (error) {
465
+ throw new Error(`Could not read ios.simslimProfile ${value}: ${String(error?.message || error)}`, { cause: error });
466
+ }
467
+ }
468
+ function iosString(settings, key) {
469
+ if (!isPlainObject(settings) || !isPlainObject(settings.ios)) return void 0;
470
+ return settings.ios[key];
471
+ }
472
+ function iosSigningIdentitySetting(settings) {
473
+ const raw = iosString(settings, "signingIdentity");
474
+ return typeof raw === "string" && raw.trim() !== "" ? raw.trim() : null;
475
+ }
476
+ function iosSigningIdentitySettingError(settings) {
477
+ const raw = iosString(settings, "signingIdentity");
478
+ if (raw === void 0) return null;
479
+ if (typeof raw !== "string" || raw.trim() === "" || /[\r\n\0]/.test(raw)) return `Invalid ios.signingIdentity setting ${JSON.stringify(raw)}. Expected the identity name \`security find-identity -v -p codesigning\` prints, such as "Apple Development: Jane (TEAMID5678)".`;
480
+ return null;
481
+ }
482
+ const SIGNING_IDENTITY_SHA1 = /^[0-9A-Fa-f]{40}$/;
483
+ function iosSigningIdentitySha1Setting(settings) {
484
+ const raw = iosString(settings, "signingIdentitySha1");
485
+ return typeof raw === "string" && SIGNING_IDENTITY_SHA1.test(raw.trim()) ? raw.trim().toUpperCase() : null;
486
+ }
487
+ function iosSigningIdentitySha1SettingError(settings) {
488
+ const raw = iosString(settings, "signingIdentitySha1");
489
+ if (raw === void 0) return null;
490
+ if (typeof raw !== "string" || !SIGNING_IDENTITY_SHA1.test(raw.trim())) return `Invalid ios.signingIdentitySha1 setting ${JSON.stringify(raw)}. Expected the 40-character hex SHA-1 hash \`security find-identity -v -p codesigning\` prints beside the identity name.`;
491
+ return null;
492
+ }
493
+ const LAN_HOST = /^(?!-)[A-Za-z0-9-]+(?:\.(?!-)[A-Za-z0-9-]+)*$/;
494
+ function iosLanHostSetting(settings) {
495
+ const raw = iosString(settings, "lanHost");
496
+ return typeof raw === "string" && LAN_HOST.test(raw.trim()) ? raw.trim() : null;
497
+ }
498
+ function iosLanHostSettingError(settings) {
499
+ const raw = iosString(settings, "lanHost");
500
+ if (raw === void 0) return null;
501
+ if (typeof raw !== "string" || !LAN_HOST.test(raw.trim())) return `Invalid ios.lanHost setting ${JSON.stringify(raw)}. Expected a bare address or hostname the phone can reach, such as "192.168.1.42" -- never a URL, a scheme, or a port.`;
502
+ return null;
503
+ }
504
+ function unknownSettingKeys(settings, prefix = "") {
505
+ if (!isPlainObject(settings)) return [];
506
+ const unknown = [];
507
+ for (const [key, value] of Object.entries(settings)) {
508
+ const path = prefix ? `${prefix}.${key}` : key;
509
+ if (KNOWN_SETTINGS.has(path) && ![...KNOWN_SETTINGS].some((k) => k.startsWith(`${path}.`))) continue;
510
+ if (isPlainObject(value) && [...KNOWN_SETTINGS].some((k) => k.startsWith(`${path}.`))) {
511
+ unknown.push(...unknownSettingKeys(value, path));
512
+ continue;
513
+ }
514
+ unknown.push(path);
515
+ }
516
+ return unknown;
517
+ }
518
+ function readCommittedSettings(directory) {
519
+ if (!directory) return {};
520
+ const p = join$1(directory, ".stim.json");
521
+ if (!existsSync(p)) return {};
522
+ try {
523
+ const parsed = JSON.parse(readFileSync(p, "utf-8"));
524
+ return isPlainObject(parsed) ? parsed : {};
525
+ } catch {
526
+ return {};
527
+ }
528
+ }
529
+ function settingsLayers({ projectPath, gitCommonDir, repoRoot }) {
530
+ const machine = loadConfig();
531
+ const machineFile = getConfigPath();
532
+ const committedDir = projectPath ?? repoRoot;
533
+ return [
534
+ projectPath ? {
535
+ file: `${machineFile} (projects["${projectPath}"].settings)`,
536
+ settings: getProjectSettings(projectPath)
537
+ } : null,
538
+ gitCommonDir ? {
539
+ file: `${machineFile} (repos["${gitCommonDir}"].settings)`,
540
+ settings: getRepoSettings(gitCommonDir)
541
+ } : null,
542
+ committedDir ? {
543
+ file: join$1(committedDir, ".stim.json"),
544
+ settings: readCommittedSettings(committedDir)
545
+ } : null,
546
+ machine?.optimizations === void 0 ? null : {
547
+ file: machineFile,
548
+ settings: { optimizations: machine.optimizations }
549
+ }
550
+ ].filter((layer) => layer !== null);
551
+ }
552
+ function settingOrigin(layers, dottedKey) {
553
+ for (const layer of layers) {
554
+ const value = settingValueAt(layer.settings, dottedKey);
555
+ if (value !== void 0) return {
556
+ file: layer.file,
557
+ value
558
+ };
559
+ }
560
+ return null;
561
+ }
562
+ function settingFile(context, dottedKey) {
563
+ return settingOrigin(settingsLayers(context), dottedKey)?.file ?? null;
564
+ }
565
+ function resolveSettings(context) {
566
+ return mergeSettingsLayers(settingsLayers(context).map((layer) => layer.settings));
567
+ }
568
+ function settingsForProject(root) {
569
+ return resolveSettings({
570
+ projectPath: root,
571
+ gitCommonDir: gitCommonDir(root),
572
+ repoRoot: repoRoot(root) ?? root
573
+ });
574
+ }
575
+ function projectOptimizations(root) {
576
+ return resolveOptimizations(settingsForProject(root));
577
+ }
578
+ function projectMetroSharedCache(root) {
579
+ return resolveMetroSharedCache(settingsForProject(root));
580
+ }
581
+ function cacheBlock(layer) {
582
+ if (!isPlainObject(layer) || !isPlainObject(layer.cache)) return null;
583
+ return layer.cache;
584
+ }
585
+ function resolveCacheProviderConfig({ projectPath, gitCommonDir, repoRoot }) {
586
+ const layers = [
587
+ {
588
+ settings: projectPath ? getProjectSettings(projectPath) : {},
589
+ baseDir: projectPath ?? null
590
+ },
591
+ {
592
+ settings: gitCommonDir ? getRepoSettings(gitCommonDir) : {},
593
+ baseDir: repoRoot ?? projectPath ?? null
594
+ },
595
+ {
596
+ settings: readCommittedSettings(projectPath),
597
+ baseDir: projectPath ?? null
598
+ }
599
+ ];
600
+ let provider = null;
601
+ let baseDir = null;
602
+ for (const layer of layers) {
603
+ const reference = cacheBlock(layer.settings)?.provider;
604
+ if (typeof reference !== "string" || reference.trim() === "" || layer.baseDir === null) continue;
605
+ provider = reference.trim();
606
+ baseDir = layer.baseDir;
607
+ break;
608
+ }
609
+ if (provider === null || baseDir === null) return null;
610
+ const options = mergeSettingsLayers(layers.map((layer) => {
611
+ const block = cacheBlock(layer.settings)?.options;
612
+ return isPlainObject(block) ? block : null;
613
+ }));
614
+ return {
615
+ provider,
616
+ options,
617
+ baseDir
618
+ };
619
+ }
620
+ function cacheProviderSettingError(settings) {
621
+ const block = settings.cache;
622
+ if (!isPlainObject(block)) return block === void 0 ? null : `Invalid cache setting ${JSON.stringify(block)}. Expected an object with provider and options.`;
623
+ if ("provider" in block && (typeof block.provider !== "string" || block.provider.trim() === "")) return `Invalid cache.provider setting ${JSON.stringify(block.provider)}. Expected a module path or package name.`;
624
+ if ("options" in block && !isPlainObject(block.options)) return `Invalid cache.options setting ${JSON.stringify(block.options)}. Expected an object.`;
625
+ return null;
626
+ }
627
+ const REMOTE_DEVICE_BACKENDS = ["proxy", "eas"];
628
+ function remoteIosSetting(settings) {
629
+ return remoteSetting(settings, "ios");
630
+ }
631
+ function remoteAndroidSetting(settings) {
632
+ return remoteSetting(settings, "android");
633
+ }
634
+ function remoteSetting(settings, platform) {
635
+ const block = settings[platform];
636
+ if (!isPlainObject(block)) return null;
637
+ const remote = block.remote;
638
+ return typeof remote === "string" && REMOTE_DEVICE_BACKENDS.includes(remote) ? remote : null;
639
+ }
640
+ function remoteDeviceSettingError(settings) {
641
+ for (const platform of ["ios", "android"]) {
642
+ const block = settings[platform];
643
+ if (!isPlainObject(block) || !("remote" in block)) continue;
644
+ if (remoteSetting(settings, platform) === null) return `Invalid ${platform}.remote setting ${JSON.stringify(block.remote)}. Expected one of: ${REMOTE_DEVICE_BACKENDS.join(", ")}.`;
645
+ }
646
+ return null;
647
+ }
648
+ function tunnelModeSetting(settings) {
649
+ const block = settings.metro;
650
+ if (typeof block !== "object" || block === null) return null;
651
+ const mode = block.tunnel;
652
+ return typeof mode === "string" && TUNNEL_MODES.includes(mode) ? mode : null;
653
+ }
654
+ function metroTunnelSettingError(settings) {
655
+ const block = settings.metro;
656
+ if (!isPlainObject(block)) return null;
657
+ if ("tunnel" in block) {
658
+ const mode = block.tunnel;
659
+ if (typeof mode !== "string" || !TUNNEL_MODES.includes(mode)) return `Invalid metro.tunnel setting ${JSON.stringify(mode)}. Expected one of: ${TUNNEL_MODES.join(", ")}.`;
660
+ }
661
+ if (!("ngrokUrl" in block)) return null;
662
+ if (block.tunnel !== "ngrok") return "metro.ngrokUrl requires metro.tunnel to be \"ngrok\".";
663
+ if (normalizedHttpsUrl(block.ngrokUrl) === null) return "metro.ngrokUrl must be a valid HTTPS URL.";
664
+ return null;
665
+ }
666
+ function publicUrlSetting(settings) {
667
+ const block = settings.metro;
668
+ if (typeof block !== "object" || block === null) return null;
669
+ const url = block.publicUrl;
670
+ return typeof url === "string" && url.trim() ? url : null;
671
+ }
672
+ function ngrokUrlSetting(settings) {
673
+ const block = settings.metro;
674
+ if (typeof block !== "object" || block === null) return null;
675
+ if (block.tunnel !== "ngrok") return null;
676
+ return normalizedHttpsUrl(block.ngrokUrl);
677
+ }
678
+ function normalizedHttpsUrl(raw) {
679
+ if (typeof raw !== "string" || !raw.trim()) return null;
680
+ try {
681
+ return new URL(raw.trim()).protocol === "https:" ? raw.trim().replace(/\/+$/, "") : null;
682
+ } catch {
683
+ return null;
684
+ }
685
+ }
686
+ //#endregion
687
+ export { settingFile as A, compilerCacheFallbackMessage as B, projectOptimizations as C, remoteIosSetting as D, remoteDeviceSettingError as E, unknownSettingKeys as F, resolveOptimizations as H, PUBLIC_METRO_ENV as I, detectProviders as L, settingShapeErrors as M, settingsLayers as N, resolveCacheProviderConfig as O, tunnelModeSetting as P, planMetroReach as R, projectMetroSharedCache as S, remoteAndroidSetting as T, optimizationBuildProfile as V, iosSigningIdentitySha1SettingError as _, SETTING_SHAPE_REMEDY as a, metroTunnelSettingError as b, androidDataPartitionSizeBytes as c, cacheProviderSettingError as d, iosLanHostSetting as f, iosSigningIdentitySha1Setting as g, iosSigningIdentitySettingError as h, SETTINGS_WITH_RESOLVE_TIME_FALLBACK as i, settingOrigin as j, resolveSettings as k, androidDataPartitionSizeGbSetting as l, iosSigningIdentitySetting as m, PATH_SETTINGS as n, androidAvdConfigSetting as o, iosLanHostSettingError as p, REMOTE_DEVICE_BACKENDS as r, androidAvdConfigSettingError as s, ANDROID_AVD_CONFIG_HELP as t, androidDataPartitionSizeGbSettingError as u, iosSimSlimProfileSetting as v, publicUrlSetting as w, ngrokUrlSetting as x, mergeSettingsLayers as y, artifactCachePolicy as z };