xcodebuild-axi 0.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.
- package/CHANGELOG.md +35 -0
- package/LICENSE +21 -0
- package/README.md +304 -0
- package/dist/bin/xcodebuild-axi.d.ts +2 -0
- package/dist/bin/xcodebuild-axi.js +8 -0
- package/dist/bin/xcodebuild-axi.js.map +1 -0
- package/dist/src/action.d.ts +68 -0
- package/dist/src/action.js +373 -0
- package/dist/src/action.js.map +1 -0
- package/dist/src/archive.d.ts +33 -0
- package/dist/src/archive.js +77 -0
- package/dist/src/archive.js.map +1 -0
- package/dist/src/args.d.ts +23 -0
- package/dist/src/args.js +123 -0
- package/dist/src/args.js.map +1 -0
- package/dist/src/auth.d.ts +11 -0
- package/dist/src/auth.js +51 -0
- package/dist/src/auth.js.map +1 -0
- package/dist/src/cli.d.ts +20 -0
- package/dist/src/cli.js +132 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/commands/analyze.d.ts +2 -0
- package/dist/src/commands/analyze.js +32 -0
- package/dist/src/commands/analyze.js.map +1 -0
- package/dist/src/commands/archive.d.ts +2 -0
- package/dist/src/commands/archive.js +63 -0
- package/dist/src/commands/archive.js.map +1 -0
- package/dist/src/commands/build.d.ts +2 -0
- package/dist/src/commands/build.js +60 -0
- package/dist/src/commands/build.js.map +1 -0
- package/dist/src/commands/clean.d.ts +2 -0
- package/dist/src/commands/clean.js +39 -0
- package/dist/src/commands/clean.js.map +1 -0
- package/dist/src/commands/coverage.d.ts +2 -0
- package/dist/src/commands/coverage.js +111 -0
- package/dist/src/commands/coverage.js.map +1 -0
- package/dist/src/commands/destinations.d.ts +2 -0
- package/dist/src/commands/destinations.js +56 -0
- package/dist/src/commands/destinations.js.map +1 -0
- package/dist/src/commands/export.d.ts +2 -0
- package/dist/src/commands/export.js +170 -0
- package/dist/src/commands/export.js.map +1 -0
- package/dist/src/commands/find.d.ts +2 -0
- package/dist/src/commands/find.js +70 -0
- package/dist/src/commands/find.js.map +1 -0
- package/dist/src/commands/home.d.ts +9 -0
- package/dist/src/commands/home.js +97 -0
- package/dist/src/commands/home.js.map +1 -0
- package/dist/src/commands/info.d.ts +2 -0
- package/dist/src/commands/info.js +95 -0
- package/dist/src/commands/info.js.map +1 -0
- package/dist/src/commands/localize.d.ts +2 -0
- package/dist/src/commands/localize.js +121 -0
- package/dist/src/commands/localize.js.map +1 -0
- package/dist/src/commands/migrate.d.ts +8 -0
- package/dist/src/commands/migrate.js +177 -0
- package/dist/src/commands/migrate.js.map +1 -0
- package/dist/src/commands/packages.d.ts +2 -0
- package/dist/src/commands/packages.js +108 -0
- package/dist/src/commands/packages.js.map +1 -0
- package/dist/src/commands/platforms.d.ts +2 -0
- package/dist/src/commands/platforms.js +379 -0
- package/dist/src/commands/platforms.js.map +1 -0
- package/dist/src/commands/result.d.ts +2 -0
- package/dist/src/commands/result.js +100 -0
- package/dist/src/commands/result.js.map +1 -0
- package/dist/src/commands/schemes.d.ts +2 -0
- package/dist/src/commands/schemes.js +39 -0
- package/dist/src/commands/schemes.js.map +1 -0
- package/dist/src/commands/settings.d.ts +2 -0
- package/dist/src/commands/settings.js +191 -0
- package/dist/src/commands/settings.js.map +1 -0
- package/dist/src/commands/setup.d.ts +2 -0
- package/dist/src/commands/setup.js +84 -0
- package/dist/src/commands/setup.js.map +1 -0
- package/dist/src/commands/sim.d.ts +2 -0
- package/dist/src/commands/sim.js +187 -0
- package/dist/src/commands/sim.js.map +1 -0
- package/dist/src/commands/test.d.ts +2 -0
- package/dist/src/commands/test.js +317 -0
- package/dist/src/commands/test.js.map +1 -0
- package/dist/src/commands/testplans.d.ts +8 -0
- package/dist/src/commands/testplans.js +75 -0
- package/dist/src/commands/testplans.js.map +1 -0
- package/dist/src/commands/tests.d.ts +13 -0
- package/dist/src/commands/tests.js +154 -0
- package/dist/src/commands/tests.js.map +1 -0
- package/dist/src/commands/xcframework.d.ts +2 -0
- package/dist/src/commands/xcframework.js +77 -0
- package/dist/src/commands/xcframework.js.map +1 -0
- package/dist/src/context.d.ts +23 -0
- package/dist/src/context.js +72 -0
- package/dist/src/context.js.map +1 -0
- package/dist/src/destination.d.ts +50 -0
- package/dist/src/destination.js +174 -0
- package/dist/src/destination.js.map +1 -0
- package/dist/src/errors.d.ts +6 -0
- package/dist/src/errors.js +55 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/packages.d.ts +31 -0
- package/dist/src/packages.js +117 -0
- package/dist/src/packages.js.map +1 -0
- package/dist/src/report.d.ts +20 -0
- package/dist/src/report.js +39 -0
- package/dist/src/report.js.map +1 -0
- package/dist/src/scheme.d.ts +22 -0
- package/dist/src/scheme.js +66 -0
- package/dist/src/scheme.js.map +1 -0
- package/dist/src/simctl.d.ts +33 -0
- package/dist/src/simctl.js +85 -0
- package/dist/src/simctl.js.map +1 -0
- package/dist/src/surface.d.ts +55 -0
- package/dist/src/surface.js +350 -0
- package/dist/src/surface.js.map +1 -0
- package/dist/src/toon.d.ts +34 -0
- package/dist/src/toon.js +79 -0
- package/dist/src/toon.js.map +1 -0
- package/dist/src/version.d.ts +1 -0
- package/dist/src/version.js +24 -0
- package/dist/src/version.js.map +1 -0
- package/dist/src/xccov.d.ts +25 -0
- package/dist/src/xccov.js +78 -0
- package/dist/src/xccov.js.map +1 -0
- package/dist/src/xcodebuild.d.ts +65 -0
- package/dist/src/xcodebuild.js +163 -0
- package/dist/src/xcodebuild.js.map +1 -0
- package/dist/src/xcresult.d.ts +96 -0
- package/dist/src/xcresult.js +156 -0
- package/dist/src/xcresult.js.map +1 -0
- package/package.json +75 -0
- package/skills/xcodebuild-axi/SKILL.md +64 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Diagnostic } from "./xcresult.js";
|
|
2
|
+
/**
|
|
3
|
+
* Render a capped list of diagnostics.
|
|
4
|
+
*
|
|
5
|
+
* Errors cascade: one bad protocol conformance can produce sixty lines that
|
|
6
|
+
* all name the same root cause, and printing all sixty is exactly the flood
|
|
7
|
+
* this tool exists to stop. The cap keeps the first N — which is where the
|
|
8
|
+
* cause almost always is — and says how many it withheld (AXI principles 3
|
|
9
|
+
* and 4), so the count is never silently wrong.
|
|
10
|
+
*/
|
|
11
|
+
export declare function diagnosticsBlock(label: string, diagnostics: Diagnostic[], limit: number, messageLimit?: number): {
|
|
12
|
+
block: string;
|
|
13
|
+
hidden: number;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* The last lines of a transcript, for a failure the result bundle cannot
|
|
17
|
+
* explain — a wedged simulator, a signal, a timeout. Showing nothing here
|
|
18
|
+
* would be the quiet wrapper hiding the very failure it exists to surface.
|
|
19
|
+
*/
|
|
20
|
+
export declare function transcriptTail(tail: string, lines?: number): string;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { renderList, truncate } from "./toon.js";
|
|
2
|
+
/**
|
|
3
|
+
* Render a capped list of diagnostics.
|
|
4
|
+
*
|
|
5
|
+
* Errors cascade: one bad protocol conformance can produce sixty lines that
|
|
6
|
+
* all name the same root cause, and printing all sixty is exactly the flood
|
|
7
|
+
* this tool exists to stop. The cap keeps the first N — which is where the
|
|
8
|
+
* cause almost always is — and says how many it withheld (AXI principles 3
|
|
9
|
+
* and 4), so the count is never silently wrong.
|
|
10
|
+
*/
|
|
11
|
+
export function diagnosticsBlock(label, diagnostics, limit, messageLimit = 300) {
|
|
12
|
+
if (diagnostics.length === 0)
|
|
13
|
+
return { block: "", hidden: 0 };
|
|
14
|
+
const shown = diagnostics.slice(0, limit).map((diagnostic) => ({
|
|
15
|
+
file: diagnostic.file,
|
|
16
|
+
line: diagnostic.line,
|
|
17
|
+
col: diagnostic.col,
|
|
18
|
+
type: diagnostic.type,
|
|
19
|
+
message: truncate(diagnostic.message, messageLimit).text,
|
|
20
|
+
}));
|
|
21
|
+
const hidden = diagnostics.length - shown.length;
|
|
22
|
+
const heading = hidden > 0 ? `${label} (${shown.length} of ${diagnostics.length})` : label;
|
|
23
|
+
return { block: renderList(heading, shown), hidden };
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The last lines of a transcript, for a failure the result bundle cannot
|
|
27
|
+
* explain — a wedged simulator, a signal, a timeout. Showing nothing here
|
|
28
|
+
* would be the quiet wrapper hiding the very failure it exists to surface.
|
|
29
|
+
*/
|
|
30
|
+
export function transcriptTail(tail, lines = 15) {
|
|
31
|
+
const kept = tail
|
|
32
|
+
.split("\n")
|
|
33
|
+
.filter((line) => line.trim().length > 0)
|
|
34
|
+
.slice(-lines);
|
|
35
|
+
if (kept.length === 0)
|
|
36
|
+
return "";
|
|
37
|
+
return `tail[${kept.length}]:\n${kept.map((line) => ` ${line}`).join("\n")}`;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/report.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAEjD;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAC9B,KAAa,EACb,WAAyB,EACzB,KAAa,EACb,YAAY,GAAG,GAAG;IAElB,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IAE9D,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;QAC7D,IAAI,EAAE,UAAU,CAAC,IAAI;QACrB,IAAI,EAAE,UAAU,CAAC,IAAI;QACrB,GAAG,EAAE,UAAU,CAAC,GAAG;QACnB,IAAI,EAAE,UAAU,CAAC,IAAI;QACrB,OAAO,EAAE,QAAQ,CAAC,UAAU,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,IAAI;KACzD,CAAC,CAAC,CAAC;IAEJ,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IACjD,MAAM,OAAO,GACX,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,KAAK,KAAK,CAAC,MAAM,OAAO,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC;IAE7E,OAAO,EAAE,KAAK,EAAE,UAAU,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;AACvD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,KAAK,GAAG,EAAE;IACrD,MAAM,IAAI,GAAG,IAAI;SACd,KAAK,CAAC,IAAI,CAAC;SACX,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;SACxC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC;IACjB,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACjC,OAAO,QAAQ,IAAI,CAAC,MAAM,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AAChF,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ProjectContext } from "./context.js";
|
|
2
|
+
export interface SchemeInfo {
|
|
3
|
+
name: string;
|
|
4
|
+
schemes: string[];
|
|
5
|
+
targets: string[];
|
|
6
|
+
configurations: string[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* `-list -json`, which unlike the plain form emits no package-graph preamble
|
|
10
|
+
* and no prose. Still worth going through `runMetadata` so a missing Xcode is
|
|
11
|
+
* reported as a structured error rather than an ENOENT stack.
|
|
12
|
+
*/
|
|
13
|
+
export declare function listSchemes(project: ProjectContext): Promise<SchemeInfo>;
|
|
14
|
+
/**
|
|
15
|
+
* Resolve the scheme to act on.
|
|
16
|
+
*
|
|
17
|
+
* With one scheme there is nothing to ask about, so we use it — a required
|
|
18
|
+
* flag whose value can only be one thing is a wasted round trip. With several,
|
|
19
|
+
* refusing is right, but the refusal carries the list so the next call is
|
|
20
|
+
* correct rather than another lookup (AXI principle 6).
|
|
21
|
+
*/
|
|
22
|
+
export declare function requireScheme(project: ProjectContext, requested: string | undefined, command: string): Promise<string>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { AxiError } from "./errors.js";
|
|
2
|
+
import { runMetadata } from "./xcodebuild.js";
|
|
3
|
+
/**
|
|
4
|
+
* `-list -json`, which unlike the plain form emits no package-graph preamble
|
|
5
|
+
* and no prose. Still worth going through `runMetadata` so a missing Xcode is
|
|
6
|
+
* reported as a structured error rather than an ENOENT stack.
|
|
7
|
+
*/
|
|
8
|
+
export async function listSchemes(project) {
|
|
9
|
+
const { stdout, stderr, exitCode } = await runMetadata([
|
|
10
|
+
...project.flags,
|
|
11
|
+
"-list",
|
|
12
|
+
"-json",
|
|
13
|
+
]);
|
|
14
|
+
const start = stdout.indexOf("{");
|
|
15
|
+
if (start === -1) {
|
|
16
|
+
throw (mapListFailure(`${stdout}\n${stderr}`) ??
|
|
17
|
+
new AxiError(`xcodebuild could not read ${project.name} (exit ${exitCode})`, "NO_PROJECT"));
|
|
18
|
+
}
|
|
19
|
+
const parsed = JSON.parse(stdout.slice(start));
|
|
20
|
+
const container = parsed.workspace ?? parsed.project ?? {};
|
|
21
|
+
return {
|
|
22
|
+
name: container.name ?? project.name,
|
|
23
|
+
schemes: container.schemes ?? [],
|
|
24
|
+
targets: parsed.project?.targets ?? [],
|
|
25
|
+
configurations: parsed.project?.configurations ?? [],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function mapListFailure(output) {
|
|
29
|
+
if (/does not contain a scheme/.test(output))
|
|
30
|
+
return undefined;
|
|
31
|
+
const match = output.match(/xcodebuild: error: (.+)/);
|
|
32
|
+
return match?.[1] ? new AxiError(match[1].trim(), "NO_PROJECT") : undefined;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Resolve the scheme to act on.
|
|
36
|
+
*
|
|
37
|
+
* With one scheme there is nothing to ask about, so we use it — a required
|
|
38
|
+
* flag whose value can only be one thing is a wasted round trip. With several,
|
|
39
|
+
* refusing is right, but the refusal carries the list so the next call is
|
|
40
|
+
* correct rather than another lookup (AXI principle 6).
|
|
41
|
+
*/
|
|
42
|
+
export async function requireScheme(project, requested, command) {
|
|
43
|
+
const { schemes } = await listSchemes(project);
|
|
44
|
+
if (requested) {
|
|
45
|
+
if (schemes.length > 0 && !schemes.includes(requested)) {
|
|
46
|
+
const near = schemes.filter((s) => s.toLowerCase().includes(requested.toLowerCase()));
|
|
47
|
+
throw new AxiError(`No scheme named '${requested}' in ${project.name}`, "SCHEME_NOT_FOUND", near.length > 0
|
|
48
|
+
? [`did you mean: ${near.join(", ")}`]
|
|
49
|
+
: [`schemes in ${project.name}: ${schemes.join(", ")}`]);
|
|
50
|
+
}
|
|
51
|
+
return requested;
|
|
52
|
+
}
|
|
53
|
+
const only = schemes[0];
|
|
54
|
+
if (schemes.length === 1 && only !== undefined)
|
|
55
|
+
return only;
|
|
56
|
+
if (schemes.length === 0) {
|
|
57
|
+
throw new AxiError(`${project.name} declares no shared schemes`, "SCHEME_NOT_FOUND", [
|
|
58
|
+
"Open the project in Xcode and mark a scheme as Shared, or pass `--scheme <name>`",
|
|
59
|
+
]);
|
|
60
|
+
}
|
|
61
|
+
throw new AxiError(`${project.name} has ${schemes.length} schemes — pass --scheme`, "VALIDATION_ERROR", [
|
|
62
|
+
`xcodebuild-axi ${command} --scheme <name>`,
|
|
63
|
+
`schemes: ${schemes.join(", ")}`,
|
|
64
|
+
]);
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=scheme.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheme.js","sourceRoot":"","sources":["../../src/scheme.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface Simulator {
|
|
2
|
+
udid: string;
|
|
3
|
+
name: string;
|
|
4
|
+
state: string;
|
|
5
|
+
runtime: string;
|
|
6
|
+
isAvailable: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function simctl(args: string[]): Promise<{
|
|
9
|
+
stdout: string;
|
|
10
|
+
exitCode: number;
|
|
11
|
+
stderr: string;
|
|
12
|
+
}>;
|
|
13
|
+
/**
|
|
14
|
+
* Every simulator, flattened out of simctl's runtime-keyed map.
|
|
15
|
+
*
|
|
16
|
+
* The raw JSON nests devices under runtime identifiers like
|
|
17
|
+
* `com.apple.CoreSimulator.SimRuntime.iOS-26-5` and carries six fields per
|
|
18
|
+
* device that nobody asked for — data paths, log paths, sizes. Four fields
|
|
19
|
+
* and a readable runtime name is the whole useful shape.
|
|
20
|
+
*/
|
|
21
|
+
export declare function listSimulators(options?: {
|
|
22
|
+
available?: boolean;
|
|
23
|
+
}): Promise<Simulator[]>;
|
|
24
|
+
/** `com.apple.CoreSimulator.SimRuntime.iOS-26-5` -> `iOS 26.5`. */
|
|
25
|
+
export declare function prettyRuntime(runtimeId: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* Find one simulator by udid or name.
|
|
28
|
+
*
|
|
29
|
+
* Name matching prefers a booted device, because when a name resolves to
|
|
30
|
+
* several runtimes the booted one is almost always the one meant — and acting
|
|
31
|
+
* on the wrong copy of "iPhone 17 Pro" is a silent wrong answer.
|
|
32
|
+
*/
|
|
33
|
+
export declare function findSimulator(simulators: Simulator[], query: string): Simulator | undefined;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { AxiError } from "./errors.js";
|
|
3
|
+
/** `simctl` reads and mutations, wrapped so failures arrive structured. */
|
|
4
|
+
const MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
5
|
+
export function simctl(args) {
|
|
6
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
7
|
+
execFile("xcrun", ["simctl", ...args], { maxBuffer: MAX_BUFFER_BYTES, encoding: "utf-8" }, (error, stdout, stderr) => {
|
|
8
|
+
if (error && error.code === "ENOENT") {
|
|
9
|
+
rejectPromise(new AxiError("xcrun is not available — Xcode is not installed or selected", "XCODE_NOT_INSTALLED"));
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
resolvePromise({
|
|
13
|
+
stdout,
|
|
14
|
+
stderr,
|
|
15
|
+
exitCode: typeof error?.code === "number" ? error.code : error ? 1 : 0,
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Every simulator, flattened out of simctl's runtime-keyed map.
|
|
22
|
+
*
|
|
23
|
+
* The raw JSON nests devices under runtime identifiers like
|
|
24
|
+
* `com.apple.CoreSimulator.SimRuntime.iOS-26-5` and carries six fields per
|
|
25
|
+
* device that nobody asked for — data paths, log paths, sizes. Four fields
|
|
26
|
+
* and a readable runtime name is the whole useful shape.
|
|
27
|
+
*/
|
|
28
|
+
export async function listSimulators(options = {}) {
|
|
29
|
+
const args = ["list", "-j", "devices"];
|
|
30
|
+
if (options.available !== false)
|
|
31
|
+
args.push("available");
|
|
32
|
+
const { stdout } = await simctl(args);
|
|
33
|
+
let parsed;
|
|
34
|
+
try {
|
|
35
|
+
parsed = JSON.parse(stdout);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
throw new AxiError("simctl returned output that could not be read", "UNKNOWN");
|
|
39
|
+
}
|
|
40
|
+
const out = [];
|
|
41
|
+
for (const [runtimeId, devices] of Object.entries(parsed.devices ?? {})) {
|
|
42
|
+
const runtime = prettyRuntime(runtimeId);
|
|
43
|
+
for (const device of devices) {
|
|
44
|
+
if (device.udid === undefined || device.name === undefined)
|
|
45
|
+
continue;
|
|
46
|
+
out.push({
|
|
47
|
+
udid: device.udid,
|
|
48
|
+
name: device.name,
|
|
49
|
+
state: (device.state ?? "unknown").toLowerCase(),
|
|
50
|
+
runtime,
|
|
51
|
+
isAvailable: device.isAvailable !== false,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
/** `com.apple.CoreSimulator.SimRuntime.iOS-26-5` -> `iOS 26.5`. */
|
|
58
|
+
export function prettyRuntime(runtimeId) {
|
|
59
|
+
const tail = runtimeId.split(".").pop() ?? runtimeId;
|
|
60
|
+
// The version part must actually look like one, so an identifier in some
|
|
61
|
+
// other shape comes back untouched rather than silently mangled.
|
|
62
|
+
const match = tail.match(/^([A-Za-z]+)-(\d+(?:-\d+)*)$/);
|
|
63
|
+
if (!match || match[1] === undefined || match[2] === undefined)
|
|
64
|
+
return tail;
|
|
65
|
+
return `${match[1]} ${match[2].replace(/-/g, ".")}`;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Find one simulator by udid or name.
|
|
69
|
+
*
|
|
70
|
+
* Name matching prefers a booted device, because when a name resolves to
|
|
71
|
+
* several runtimes the booted one is almost always the one meant — and acting
|
|
72
|
+
* on the wrong copy of "iPhone 17 Pro" is a silent wrong answer.
|
|
73
|
+
*/
|
|
74
|
+
export function findSimulator(simulators, query) {
|
|
75
|
+
const wanted = query.toLowerCase();
|
|
76
|
+
const byUdid = simulators.find((simulator) => simulator.udid.toLowerCase() === wanted);
|
|
77
|
+
if (byUdid)
|
|
78
|
+
return byUdid;
|
|
79
|
+
const named = simulators.filter((simulator) => simulator.name.toLowerCase() === wanted);
|
|
80
|
+
if (named.length > 0) {
|
|
81
|
+
return named.find((simulator) => simulator.state === "booted") ?? named[0];
|
|
82
|
+
}
|
|
83
|
+
return simulators.find((simulator) => simulator.name.toLowerCase().includes(wanted));
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=simctl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"simctl.js","sourceRoot":"","sources":["../../src/simctl.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How much of `xcodebuild` this tool actually covers.
|
|
3
|
+
*
|
|
4
|
+
* Wrapping a CLI silently narrows it, and the narrowing is invisible until an
|
|
5
|
+
* agent needs the one flag that got left out. So the mapping is declared here
|
|
6
|
+
* rather than inferred: every option `xcodebuild -help` prints is classified,
|
|
7
|
+
* and `npm run coverage -- --check` fails if a new Xcode adds one this file
|
|
8
|
+
* has never heard of.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* The Xcode whose `xcodebuild -help` this map was written against.
|
|
12
|
+
*
|
|
13
|
+
* The option list is not stable across Xcode releases — 26 listed `-dry-run`
|
|
14
|
+
* and `-downloadAllPreviouslySelectedPlatforms`, 27 dropped both and added the
|
|
15
|
+
* `platforms` and codesize families. So a machine running a different Xcode
|
|
16
|
+
* will legitimately disagree with this file, and only a machine running this
|
|
17
|
+
* one can say the map is actually stale. `scripts/coverage.ts` fails on a
|
|
18
|
+
* mismatch here and merely reports one elsewhere.
|
|
19
|
+
*/
|
|
20
|
+
export declare const AUTHORED_AGAINST = "27.0";
|
|
21
|
+
export type OptionCoverage =
|
|
22
|
+
/** Reachable through an xcodebuild-axi flag or command. */
|
|
23
|
+
{
|
|
24
|
+
status: "exposed";
|
|
25
|
+
via: string;
|
|
26
|
+
}
|
|
27
|
+
/** The tool always sets it, so there is nothing for a caller to pass. */
|
|
28
|
+
| {
|
|
29
|
+
status: "always";
|
|
30
|
+
why: string;
|
|
31
|
+
}
|
|
32
|
+
/** Answered better by something xcodebuild-axi already does. */
|
|
33
|
+
| {
|
|
34
|
+
status: "superseded";
|
|
35
|
+
why: string;
|
|
36
|
+
}
|
|
37
|
+
/** Deliberately not wrapped, for the stated reason. */
|
|
38
|
+
| {
|
|
39
|
+
status: "n/a";
|
|
40
|
+
why: string;
|
|
41
|
+
};
|
|
42
|
+
export declare const OPTION_COVERAGE: Record<string, OptionCoverage>;
|
|
43
|
+
/** The build actions, classified the same way. */
|
|
44
|
+
export declare const ACTION_COVERAGE: Record<string, OptionCoverage>;
|
|
45
|
+
export interface CoverageTally {
|
|
46
|
+
total: number;
|
|
47
|
+
exposed: number;
|
|
48
|
+
always: number;
|
|
49
|
+
superseded: number;
|
|
50
|
+
na: number;
|
|
51
|
+
/** Reachable, handled for you, or answered better — what the README quotes. */
|
|
52
|
+
covered: number;
|
|
53
|
+
percent: number;
|
|
54
|
+
}
|
|
55
|
+
export declare function tally(map: Record<string, OptionCoverage>): CoverageTally;
|