@invarn/cibuild 1.8.0 → 1.9.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.
@@ -8,7 +8,7 @@ export declare class StepExecutionError extends Error {
8
8
  export declare class PipelineRunner {
9
9
  private config;
10
10
  constructor(config: CIConfig);
11
- runStep(step: StepDef): Promise<void>;
11
+ runStep(step: StepDef, stepNumber?: number): Promise<void>;
12
12
  private formatDuration;
13
13
  validatePipeline(pipeline: PipelineDef): void;
14
14
  displayWarnings(warnings: string[], skippedSteps: number): void;
@@ -1 +1 @@
1
- {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/runner.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAKjE,qBAAa,kBAAmB,SAAQ,KAAK;IAElC,MAAM,EAAE,MAAM;IACd,QAAQ,EAAE,MAAM;IAChB,QAAQ,EAAE,MAAM;gBAFhB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM;CAK1B;AAED,qBAAa,cAAc;IACb,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,QAAQ;IAE9B,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAsI3C,OAAO,CAAC,cAAc;IAYtB,gBAAgB,CAAC,QAAQ,EAAE,WAAW,GAAG,IAAI;IAwB7C,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI;IAczD,WAAW,CAAC,QAAQ,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CA0CpG"}
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/runner.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAMjE,qBAAa,kBAAmB,SAAQ,KAAK;IAElC,MAAM,EAAE,MAAM;IACd,QAAQ,EAAE,MAAM;IAChB,QAAQ,EAAE,MAAM;gBAFhB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM;CAK1B;AAED,qBAAa,cAAc;IACb,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,QAAQ;IAE9B,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoJhE,OAAO,CAAC,cAAc;IAYtB,gBAAgB,CAAC,QAAQ,EAAE,WAAW,GAAG,IAAI;IAwB7C,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI;IAczD,WAAW,CAAC,QAAQ,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CA2CpG"}
@@ -2,6 +2,7 @@ import { spawn } from "node:child_process";
2
2
  import { dirname, join, resolve } from "node:path";
3
3
  import { unlinkSync, existsSync, writeFileSync, chmodSync, mkdtempSync, rmSync, mkdirSync } from "node:fs";
4
4
  import { tmpdir } from "node:os";
5
+ import { formatStepMarker, shouldEmitStepMarker } from "./step-marker.js";
5
6
  // Use absolute path for envstore so it works regardless of cd in scripts
6
7
  const ENVMAN_ENVSTORE_PATH = resolve(process.cwd(), ".ci", ".envstore.json");
7
8
  export class StepExecutionError extends Error {
@@ -21,7 +22,21 @@ export class PipelineRunner {
21
22
  constructor(config) {
22
23
  this.config = config;
23
24
  }
24
- async runStep(step) {
25
+ async runStep(step, stepNumber) {
26
+ // Emit the INVARN_STEP marker before any other output so the
27
+ // dashboard's step-progress parser sees a step boundary even when
28
+ // the step's own logs are silent. Pairs with the parser at
29
+ // `lib/shared/step-progress.ts` in the Invarn repo. `stepNumber`
30
+ // is optional to keep `runStep` backward-compatible for direct
31
+ // callers (lib consumers) that don't run inside a pipeline loop.
32
+ //
33
+ // Gated on INVARN_RUNNER=1: cibuild is a public npm package
34
+ // (@invarn/cibuild) and must not leak Invarn's log-parsing protocol
35
+ // into external users' stdout. The Invarn runner opts in by exporting
36
+ // the var into the sandbox; see cibuild-runner/src/runner.ts.
37
+ if (stepNumber != null && shouldEmitStepMarker()) {
38
+ console.log(formatStepMarker(stepNumber, step.name));
39
+ }
25
40
  console.log(`\n[${step.id}] Running: ${step.name}`);
26
41
  console.log("─".repeat(60));
27
42
  const startTime = Date.now();
@@ -209,13 +224,14 @@ export -f envman
209
224
  }
210
225
  this.validatePipeline(pipeline);
211
226
  const stepsMap = new Map(pipeline.steps.map((s) => [s.id, s]));
212
- for (const stepId of pipeline.stepsOrder) {
227
+ for (let i = 0; i < pipeline.stepsOrder.length; i++) {
228
+ const stepId = pipeline.stepsOrder[i];
213
229
  const step = stepsMap.get(stepId);
214
230
  if (!step) {
215
231
  throw new Error(`Step ${stepId} not found`);
216
232
  }
217
233
  try {
218
- await this.runStep(step);
234
+ await this.runStep(step, i + 1);
219
235
  }
220
236
  catch (err) {
221
237
  if (step.isSkippable && err instanceof StepExecutionError) {
@@ -0,0 +1,12 @@
1
+ export declare function formatStepMarker(stepNumber: number, stepName: string): string;
2
+ /**
3
+ * cibuild ships publicly on npm as `@invarn/cibuild`. Emitting the marker
4
+ * unconditionally would leak Invarn's log-parsing protocol into external
5
+ * users' stdout. The Invarn runner (cibuild-runner) opts in by exporting
6
+ * INVARN_RUNNER=1 into the sandbox; everywhere else this returns false
7
+ * and the runner stays quiet.
8
+ *
9
+ * Strict equality to "1" — no truthy parsing — keeps the contract crisp.
10
+ */
11
+ export declare function shouldEmitStepMarker(env?: NodeJS.ProcessEnv): boolean;
12
+ //# sourceMappingURL=step-marker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"step-marker.d.ts","sourceRoot":"","sources":["../../src/step-marker.ts"],"names":[],"mappings":"AAcA,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAElF"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Producer side of the INVARN_STEP marker contract.
3
+ *
4
+ * The dashboard's `lib/shared/step-progress.ts` parses runtime logs for
5
+ * lines matching `/▸ INVARN_STEP:(\d+):(.+)/` to populate
6
+ * `BuildEnrichment.parsedSteps`. The runner emits one marker per step
7
+ * via `console.log(formatStepMarker(...))` before executing it, so every
8
+ * build (agent or not) gets symmetric step telemetry.
9
+ *
10
+ * Step name is embedded as-is. The dashboard parser uses `(.+)` (greedy),
11
+ * so step names containing colons (`build:android:debug`) round-trip.
12
+ */
13
+ const STEP_MARKER_PREFIX = '▸ INVARN_STEP';
14
+ export function formatStepMarker(stepNumber, stepName) {
15
+ return `${STEP_MARKER_PREFIX}:${stepNumber}:${stepName}`;
16
+ }
17
+ /**
18
+ * cibuild ships publicly on npm as `@invarn/cibuild`. Emitting the marker
19
+ * unconditionally would leak Invarn's log-parsing protocol into external
20
+ * users' stdout. The Invarn runner (cibuild-runner) opts in by exporting
21
+ * INVARN_RUNNER=1 into the sandbox; everywhere else this returns false
22
+ * and the runner stays quiet.
23
+ *
24
+ * Strict equality to "1" — no truthy parsing — keeps the contract crisp.
25
+ */
26
+ export function shouldEmitStepMarker(env = process.env) {
27
+ return env.INVARN_RUNNER === '1';
28
+ }
29
+ //# sourceMappingURL=step-marker.js.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Unit tests for the INVARN_STEP marker formatter.
3
+ *
4
+ * The dashboard's `lib/shared/step-progress.ts` parses runtime logs for
5
+ * lines matching `/▸ INVARN_STEP:(\d+):(.+)/` to populate
6
+ * `BuildEnrichment.parsedSteps`. This formatter is the producer side of
7
+ * that contract — the runner emits one marker per step before executing
8
+ * it so non-agent builds get symmetric step telemetry.
9
+ */
10
+ export {};
11
+ //# sourceMappingURL=step-marker.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"step-marker.test.d.ts","sourceRoot":"","sources":["../../src/step-marker.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Unit tests for the INVARN_STEP marker formatter.
3
+ *
4
+ * The dashboard's `lib/shared/step-progress.ts` parses runtime logs for
5
+ * lines matching `/▸ INVARN_STEP:(\d+):(.+)/` to populate
6
+ * `BuildEnrichment.parsedSteps`. This formatter is the producer side of
7
+ * that contract — the runner emits one marker per step before executing
8
+ * it so non-agent builds get symmetric step telemetry.
9
+ */
10
+ import { describe, test, expect } from '@jest/globals';
11
+ import { formatStepMarker, shouldEmitStepMarker } from './step-marker.js';
12
+ describe('formatStepMarker', () => {
13
+ test('formats a step number and name as the wire-format marker', () => {
14
+ expect(formatStepMarker(1, 'install-deps')).toBe('▸ INVARN_STEP:1:install-deps');
15
+ });
16
+ test('preserves colons in the step name (the dashboard parser is greedy on the trailing capture)', () => {
17
+ expect(formatStepMarker(2, 'build:android:debug')).toBe('▸ INVARN_STEP:2:build:android:debug');
18
+ });
19
+ test('uses 1-indexed step numbers (matches the agent generator and dashboard parser)', () => {
20
+ expect(formatStepMarker(3, 'lint')).toBe('▸ INVARN_STEP:3:lint');
21
+ });
22
+ });
23
+ /**
24
+ * Gate that keeps the marker off when cibuild is used outside Invarn
25
+ * infrastructure. cibuild ships publicly on npm as @invarn/cibuild and
26
+ * must not leak Invarn's log-parsing protocol into external users' stdout.
27
+ * The Invarn runner (cibuild-runner) opts in by exporting INVARN_RUNNER=1
28
+ * into the sandbox alongside CIBUILD_NON_INTERACTIVE / CI.
29
+ */
30
+ describe('shouldEmitStepMarker', () => {
31
+ test('returns true only when INVARN_RUNNER === "1"', () => {
32
+ expect(shouldEmitStepMarker({ INVARN_RUNNER: '1' })).toBe(true);
33
+ });
34
+ test('returns false when INVARN_RUNNER is absent (public/external cibuild use)', () => {
35
+ expect(shouldEmitStepMarker({})).toBe(false);
36
+ });
37
+ test('returns false for INVARN_RUNNER values that are not literally "1"', () => {
38
+ // Strict equality keeps the wire-level handshake unambiguous — no truthy parsing.
39
+ expect(shouldEmitStepMarker({ INVARN_RUNNER: '0' })).toBe(false);
40
+ expect(shouldEmitStepMarker({ INVARN_RUNNER: 'true' })).toBe(false);
41
+ expect(shouldEmitStepMarker({ INVARN_RUNNER: '' })).toBe(false);
42
+ expect(shouldEmitStepMarker({ INVARN_RUNNER: ' 1 ' })).toBe(false);
43
+ });
44
+ });
45
+ //# sourceMappingURL=step-marker.test.js.map
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Resolves `destination: "auto"` for xcode-* step executors.
3
+ *
4
+ * `auto` queries `xcrun simctl list devices available --json` and picks the
5
+ * newest iPhone in the highest installed iOS runtime, returning a canonical
6
+ * destination string for xcodebuild: `platform=iOS Simulator,OS=<v>,name=<n>`.
7
+ *
8
+ * The host shell-out lives in {@link resolveAutoDestination} so the parser
9
+ * ({@link resolveAutoDestinationFromSimctlJson}) stays pure and unit-testable
10
+ * against fixture JSON.
11
+ */
12
+ /** Failure mode for "auto" resolution. Surfaces as the step's failure reason. */
13
+ export declare class AutoDestinationError extends Error {
14
+ constructor(message: string);
15
+ }
16
+ /**
17
+ * Pure helper — parses `xcrun simctl list devices available --json` output
18
+ * and returns the canonical destination string.
19
+ *
20
+ * Selection rules (per spike spec):
21
+ * - Highest available iOS runtime: highest semver among keys matching
22
+ * `com.apple.CoreSimulator.SimRuntime.iOS-*`.
23
+ * - Newest iPhone: device whose name starts with "iPhone" and has the highest
24
+ * model number suffix (e.g. "iPhone 16 Pro Max" > "iPhone 15" >
25
+ * "iPhone SE (3rd generation)"). Ties at the highest model number prefer
26
+ * "Pro Max", then "Pro", then plain.
27
+ */
28
+ export declare function resolveAutoDestinationFromSimctlJson(rawJson: string): string;
29
+ /**
30
+ * Pass-through resolver used by xcode-* executors.
31
+ *
32
+ * Returns the literal value unchanged when it isn't the magic string `"auto"`.
33
+ * When it is, delegates to a resolver (default: {@link resolveAutoDestination},
34
+ * which shells out to `xcrun simctl`). The resolver injection point exists for
35
+ * tests, so executor-level tests can verify the "auto" branch without
36
+ * touching the host's xcrun.
37
+ */
38
+ export declare function resolveDestinationInput(value: string, autoResolver?: () => string): string;
39
+ /**
40
+ * Shell-out wrapper: runs `xcrun simctl list devices available --json` and
41
+ * delegates the parse to {@link resolveAutoDestinationFromSimctlJson}.
42
+ *
43
+ * Surfaces the documented failure modes (xcrun missing, simctl failure,
44
+ * no iPhone runtime) as {@link AutoDestinationError}.
45
+ */
46
+ export declare function resolveAutoDestination(): string;
47
+ //# sourceMappingURL=xcode-destination.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xcode-destination.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/xcode-destination.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAgBH,iFAAiF;AACjF,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,oCAAoC,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA4B5E;AAiID;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,MAAM,EACb,YAAY,GAAE,MAAM,MAA+B,GAClD,MAAM,CAGR;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CA4B/C"}
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Resolves `destination: "auto"` for xcode-* step executors.
3
+ *
4
+ * `auto` queries `xcrun simctl list devices available --json` and picks the
5
+ * newest iPhone in the highest installed iOS runtime, returning a canonical
6
+ * destination string for xcodebuild: `platform=iOS Simulator,OS=<v>,name=<n>`.
7
+ *
8
+ * The host shell-out lives in {@link resolveAutoDestination} so the parser
9
+ * ({@link resolveAutoDestinationFromSimctlJson}) stays pure and unit-testable
10
+ * against fixture JSON.
11
+ */
12
+ import { execFileSync } from 'node:child_process';
13
+ /** Failure mode for "auto" resolution. Surfaces as the step's failure reason. */
14
+ export class AutoDestinationError extends Error {
15
+ constructor(message) {
16
+ super(message);
17
+ this.name = 'AutoDestinationError';
18
+ }
19
+ }
20
+ /**
21
+ * Pure helper — parses `xcrun simctl list devices available --json` output
22
+ * and returns the canonical destination string.
23
+ *
24
+ * Selection rules (per spike spec):
25
+ * - Highest available iOS runtime: highest semver among keys matching
26
+ * `com.apple.CoreSimulator.SimRuntime.iOS-*`.
27
+ * - Newest iPhone: device whose name starts with "iPhone" and has the highest
28
+ * model number suffix (e.g. "iPhone 16 Pro Max" > "iPhone 15" >
29
+ * "iPhone SE (3rd generation)"). Ties at the highest model number prefer
30
+ * "Pro Max", then "Pro", then plain.
31
+ */
32
+ export function resolveAutoDestinationFromSimctlJson(rawJson) {
33
+ let parsed;
34
+ try {
35
+ parsed = JSON.parse(rawJson);
36
+ }
37
+ catch {
38
+ throw new AutoDestinationError('Failed to resolve destination: auto — xcrun simctl returned no usable output. ' +
39
+ 'Pin a specific destination string instead.');
40
+ }
41
+ const iosRuntime = pickHighestIosRuntime(parsed.devices ?? {});
42
+ if (!iosRuntime) {
43
+ throw new AutoDestinationError('Failed to resolve destination: auto — no iPhone simulator runtimes are installed. ' +
44
+ 'Install one via Xcode → Settings → Platforms.');
45
+ }
46
+ const iphone = pickNewestIphone(iosRuntime.devices);
47
+ if (!iphone) {
48
+ throw new AutoDestinationError('Failed to resolve destination: auto — no iPhone simulator runtimes are installed. ' +
49
+ 'Install one via Xcode → Settings → Platforms.');
50
+ }
51
+ return `platform=iOS Simulator,OS=${iosRuntime.version},name=${iphone.name}`;
52
+ }
53
+ const IOS_RUNTIME_PREFIX = 'com.apple.CoreSimulator.SimRuntime.iOS-';
54
+ /**
55
+ * Picks the runtime with the highest semver among
56
+ * `com.apple.CoreSimulator.SimRuntime.iOS-*` keys whose device list contains
57
+ * at least one available iPhone.
58
+ */
59
+ function pickHighestIosRuntime(devicesByRuntime) {
60
+ const candidates = [];
61
+ for (const [key, devices] of Object.entries(devicesByRuntime)) {
62
+ if (!key.startsWith(IOS_RUNTIME_PREFIX))
63
+ continue;
64
+ const versionSuffix = key.slice(IOS_RUNTIME_PREFIX.length); // e.g. "18-0"
65
+ const tuple = parseVersionTuple(versionSuffix.replace(/-/g, '.'));
66
+ if (tuple.length === 0)
67
+ continue;
68
+ const availableIphones = devices.filter((d) => isAvailableIphone(d));
69
+ if (availableIphones.length === 0)
70
+ continue;
71
+ candidates.push({
72
+ versionTuple: tuple,
73
+ version: tuple.join('.'),
74
+ devices: availableIphones,
75
+ });
76
+ }
77
+ if (candidates.length === 0)
78
+ return null;
79
+ candidates.sort((a, b) => compareTuples(b.versionTuple, a.versionTuple));
80
+ const winner = candidates[0];
81
+ return { version: winner.version, devices: winner.devices };
82
+ }
83
+ function isAvailableIphone(device) {
84
+ if (device.isAvailable === false)
85
+ return false;
86
+ return typeof device.name === 'string' && device.name.startsWith('iPhone');
87
+ }
88
+ /** Parses "18.0" or "18.0.1" into [18, 0] / [18, 0, 1]. */
89
+ function parseVersionTuple(raw) {
90
+ const parts = raw.split('.');
91
+ const out = [];
92
+ for (const p of parts) {
93
+ const n = Number.parseInt(p, 10);
94
+ if (!Number.isFinite(n))
95
+ return [];
96
+ out.push(n);
97
+ }
98
+ return out;
99
+ }
100
+ function compareTuples(a, b) {
101
+ const len = Math.max(a.length, b.length);
102
+ for (let i = 0; i < len; i++) {
103
+ const av = a[i] ?? 0;
104
+ const bv = b[i] ?? 0;
105
+ if (av !== bv)
106
+ return av - bv;
107
+ }
108
+ return 0;
109
+ }
110
+ const VARIANT_RANK = [
111
+ { pattern: /\bpro\s+max\b/i, rank: 3 },
112
+ { pattern: /\bpro\b/i, rank: 2 },
113
+ { pattern: /\bplus\b/i, rank: 1 },
114
+ ];
115
+ /**
116
+ * Picks the newest iPhone by model-number suffix, with Pro Max > Pro > plain
117
+ * tie-breaking. iPhones with no numeric model (e.g. "iPhone SE (3rd generation)")
118
+ * are ranked below numeric models.
119
+ */
120
+ function pickNewestIphone(devices) {
121
+ const scored = [];
122
+ for (const device of devices) {
123
+ if (!isAvailableIphone(device))
124
+ continue;
125
+ scored.push({
126
+ device,
127
+ model: extractIphoneModelNumber(device.name),
128
+ variantRank: extractVariantRank(device.name),
129
+ });
130
+ }
131
+ if (scored.length === 0)
132
+ return null;
133
+ scored.sort((a, b) => {
134
+ if (a.model !== b.model)
135
+ return b.model - a.model;
136
+ if (a.variantRank !== b.variantRank)
137
+ return b.variantRank - a.variantRank;
138
+ return a.device.name.localeCompare(b.device.name);
139
+ });
140
+ return scored[0].device;
141
+ }
142
+ /**
143
+ * "iPhone 16 Pro Max" → 16. "iPhone 15" → 15. "iPhone SE (3rd generation)" → -1.
144
+ * We look at the token immediately after "iPhone".
145
+ */
146
+ function extractIphoneModelNumber(name) {
147
+ const match = name.match(/^iPhone\s+(\d+)/i);
148
+ if (!match)
149
+ return -1;
150
+ return Number.parseInt(match[1], 10);
151
+ }
152
+ function extractVariantRank(name) {
153
+ for (const { pattern, rank } of VARIANT_RANK) {
154
+ if (pattern.test(name))
155
+ return rank;
156
+ }
157
+ return 0;
158
+ }
159
+ /**
160
+ * Pass-through resolver used by xcode-* executors.
161
+ *
162
+ * Returns the literal value unchanged when it isn't the magic string `"auto"`.
163
+ * When it is, delegates to a resolver (default: {@link resolveAutoDestination},
164
+ * which shells out to `xcrun simctl`). The resolver injection point exists for
165
+ * tests, so executor-level tests can verify the "auto" branch without
166
+ * touching the host's xcrun.
167
+ */
168
+ export function resolveDestinationInput(value, autoResolver = resolveAutoDestination) {
169
+ if (value !== 'auto')
170
+ return value;
171
+ return autoResolver();
172
+ }
173
+ /**
174
+ * Shell-out wrapper: runs `xcrun simctl list devices available --json` and
175
+ * delegates the parse to {@link resolveAutoDestinationFromSimctlJson}.
176
+ *
177
+ * Surfaces the documented failure modes (xcrun missing, simctl failure,
178
+ * no iPhone runtime) as {@link AutoDestinationError}.
179
+ */
180
+ export function resolveAutoDestination() {
181
+ let raw;
182
+ try {
183
+ raw = execFileSync('xcrun', ['simctl', 'list', 'devices', 'available', '--json'], {
184
+ encoding: 'utf-8',
185
+ stdio: ['ignore', 'pipe', 'pipe'],
186
+ });
187
+ }
188
+ catch (err) {
189
+ if (err && (err.code === 'ENOENT' || /not found/i.test(err.message ?? ''))) {
190
+ throw new AutoDestinationError('xcrun not available on this runner; cannot resolve destination: auto. ' +
191
+ 'Pin a specific destination string instead.');
192
+ }
193
+ throw new AutoDestinationError('Failed to resolve destination: auto — xcrun simctl returned no usable output. ' +
194
+ 'Pin a specific destination string instead.');
195
+ }
196
+ if (!raw || raw.trim() === '') {
197
+ throw new AutoDestinationError('Failed to resolve destination: auto — xcrun simctl returned no usable output. ' +
198
+ 'Pin a specific destination string instead.');
199
+ }
200
+ return resolveAutoDestinationFromSimctlJson(raw);
201
+ }
202
+ //# sourceMappingURL=xcode-destination.js.map
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Tests for the shared "auto destination" resolver.
3
+ *
4
+ * The resolver consumes the JSON `xcrun simctl list devices available --json`
5
+ * would produce on a macOS runner, and returns the canonical
6
+ * `platform=iOS Simulator,OS=<v>,name=<name>` string to feed to xcodebuild.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=xcode-destination.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xcode-destination.test.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/xcode-destination.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}