@ai-development-environment/control-agent 0.0.30 → 0.0.32

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 (2) hide show
  1. package/dist/control-agent.js +703 -222
  2. package/package.json +1 -1
@@ -2288,6 +2288,7 @@ var IOS_DEPLOY_JOB_KIND = "ios.build.deploy";
2288
2288
  var IOS_EXPORT_JOB_KIND = "ios.archive.export";
2289
2289
  var IOS_TEST_RESULTS_JOB_KIND = "ios.test-results.parse";
2290
2290
  var IOS_COVERAGE_REPORT_JOB_KIND = "ios.coverage.generate";
2291
+ var IOS_SIGNING_INSPECT_JOB_KIND = "ios.signing.inspect";
2291
2292
  var IOS_BUILD_JOB_KINDS = [
2292
2293
  IOS_SOURCE_DISCOVER_JOB_KIND,
2293
2294
  IOS_SOURCE_PARSE_JOB_KIND,
@@ -2299,7 +2300,8 @@ var IOS_BUILD_JOB_KINDS = [
2299
2300
  IOS_DEPLOY_JOB_KIND,
2300
2301
  IOS_EXPORT_JOB_KIND,
2301
2302
  IOS_TEST_RESULTS_JOB_KIND,
2302
- IOS_COVERAGE_REPORT_JOB_KIND
2303
+ IOS_COVERAGE_REPORT_JOB_KIND,
2304
+ IOS_SIGNING_INSPECT_JOB_KIND
2303
2305
  ];
2304
2306
  var BUILD_ACTIONS = [
2305
2307
  "BUILD",
@@ -2840,6 +2842,29 @@ function parseBuildExportPayload(value) {
2840
2842
  settings: parseBuildExportSettings(input.settings)
2841
2843
  };
2842
2844
  }
2845
+ function provisioningProfileType(profile) {
2846
+ if (profile.getTaskAllow) return "DEVELOPMENT";
2847
+ if (profile.provisionsAllDevices) return "ENTERPRISE";
2848
+ return profile.hasProvisionedDevices ? "AD_HOC" : "APP_STORE";
2849
+ }
2850
+ function parseBuildSigningInspectPayload(value) {
2851
+ const input = objectValue6(value, "build signing payload");
2852
+ return {
2853
+ buildId: stringValue6(input.buildId, "build signing payload.buildId"),
2854
+ codebaseId: stringValue6(
2855
+ input.codebaseId,
2856
+ "build signing payload.codebaseId"
2857
+ ),
2858
+ artifactDirectory: stringValue6(
2859
+ input.artifactDirectory,
2860
+ "build signing payload.artifactDirectory"
2861
+ ),
2862
+ archiveRelativePath: safeRelativePath2(
2863
+ input.archiveRelativePath,
2864
+ "build signing payload.archiveRelativePath"
2865
+ )
2866
+ };
2867
+ }
2843
2868
 
2844
2869
  // src/inventory.ts
2845
2870
  var AGENT_VERSION = "0.1.0";
@@ -2980,64 +3005,159 @@ function runCloudflared(payload, timeoutMs, signal, onLog) {
2980
3005
  });
2981
3006
  }
2982
3007
 
2983
- // src/handlers/ccusage.ts
2984
- import { Buffer as Buffer2 } from "node:buffer";
2985
- var MAX_CCUSAGE_STDOUT_BYTES = 16 * 1024 * 1024;
2986
- function validateCcusagePayload(payload) {
2987
- if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
2988
- throw new Error("ccusage.report payload must be an object");
3008
+ // src/handlers/signing.ts
3009
+ import { createHash as createHash2 } from "node:crypto";
3010
+ import { readdir, stat as stat2 } from "node:fs/promises";
3011
+ import { homedir as homedir2 } from "node:os";
3012
+ import { basename, join as join2, relative, sep } from "node:path";
3013
+
3014
+ // ../agent-contract/src/plist.ts
3015
+ function xmlEscape(value) {
3016
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
3017
+ }
3018
+ function plistValue(value) {
3019
+ if (typeof value === "boolean") return value ? "<true/>" : "<false/>";
3020
+ if (typeof value === "string") return `<string>${xmlEscape(value)}</string>`;
3021
+ if (typeof value === "number" && Number.isInteger(value)) {
3022
+ return `<integer>${value}</integer>`;
2989
3023
  }
2990
- const keys = Object.keys(payload);
2991
- if (keys.length > 0) {
2992
- throw new Error(`Unexpected ccusage.report payload field: ${keys[0]}`);
3024
+ if (Array.isArray(value)) {
3025
+ return `<array>${value.map((entry) => plistValue(entry)).join("")}</array>`;
2993
3026
  }
2994
- return {};
3027
+ if (value && typeof value === "object") {
3028
+ return `<dict>${Object.entries(value).map(([key, entry]) => `<key>${xmlEscape(key)}</key>${plistValue(entry)}`).join("")}</dict>`;
3029
+ }
3030
+ throw new Error("Unsupported plist value");
2995
3031
  }
2996
- async function runCcusage(payload, timeoutMs, signal, onLog) {
2997
- validateCcusagePayload(payload);
2998
- const stdout = [];
2999
- let stdoutBytes = 0;
3000
- let exceededLimit = false;
3001
- const result = await runProcess({
3002
- command: "ccusage",
3003
- args: ["--json"],
3004
- timeoutMs,
3005
- signal,
3006
- onLog: async (log) => {
3007
- if (log.stream !== "STDOUT") {
3008
- await onLog(log);
3009
- return;
3032
+ function plistDocument(value) {
3033
+ return `<?xml version="1.0" encoding="UTF-8"?>
3034
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3035
+ <plist version="1.0">${plistValue(value)}</plist>
3036
+ `;
3037
+ }
3038
+ var ENTITIES = {
3039
+ amp: "&",
3040
+ lt: "<",
3041
+ gt: ">",
3042
+ quot: '"',
3043
+ apos: "'"
3044
+ };
3045
+ function decodeText(value) {
3046
+ return value.replace(
3047
+ /&(#x?[0-9a-fA-F]+|[a-z]+);/g,
3048
+ (match, entity) => {
3049
+ if (entity.startsWith("#x") || entity.startsWith("#X")) {
3050
+ return String.fromCodePoint(Number.parseInt(entity.slice(2), 16));
3010
3051
  }
3011
- const lineBytes = Buffer2.byteLength(log.message, "utf8") + 1;
3012
- if (stdoutBytes + lineBytes > MAX_CCUSAGE_STDOUT_BYTES) {
3013
- exceededLimit = true;
3014
- return;
3052
+ if (entity.startsWith("#")) {
3053
+ return String.fromCodePoint(Number.parseInt(entity.slice(1), 10));
3015
3054
  }
3016
- stdoutBytes += lineBytes;
3017
- stdout.push(log.message);
3055
+ return ENTITIES[entity] ?? match;
3056
+ }
3057
+ );
3058
+ }
3059
+ function nextTag(xml, from) {
3060
+ let index = from;
3061
+ for (; ; ) {
3062
+ const start = xml.indexOf("<", index);
3063
+ if (start < 0) return null;
3064
+ if (xml.startsWith("<!--", start)) {
3065
+ const close2 = xml.indexOf("-->", start);
3066
+ if (close2 < 0) return null;
3067
+ index = close2 + 3;
3068
+ continue;
3069
+ }
3070
+ if (xml.startsWith("<?", start) || xml.startsWith("<!", start)) {
3071
+ const close2 = xml.indexOf(">", start);
3072
+ if (close2 < 0) return null;
3073
+ index = close2 + 1;
3074
+ continue;
3075
+ }
3076
+ const close = xml.indexOf(">", start);
3077
+ if (close < 0) return null;
3078
+ const raw = xml.slice(start + 1, close);
3079
+ const closing = raw.startsWith("/");
3080
+ const selfClosing = raw.endsWith("/");
3081
+ const name = raw.slice(closing ? 1 : 0, selfClosing ? raw.length - 1 : raw.length).trim().split(/\s/)[0];
3082
+ return { name, closing, selfClosing, end: close + 1 };
3083
+ }
3084
+ }
3085
+ function textUntilClose(xml, from, name) {
3086
+ const close = xml.indexOf(`</${name}`, from);
3087
+ if (close < 0) throw new Error(`Unterminated <${name}> in plist`);
3088
+ const end = xml.indexOf(">", close);
3089
+ return [decodeText(xml.slice(from, close)), end + 1];
3090
+ }
3091
+ var MAX_DEPTH = 64;
3092
+ function parseValue(xml, from, depth) {
3093
+ if (depth > MAX_DEPTH) throw new Error("plist nesting is too deep");
3094
+ const tag = nextTag(xml, from);
3095
+ if (!tag || tag.closing) throw new Error("Expected a plist value");
3096
+ if (tag.selfClosing) {
3097
+ switch (tag.name) {
3098
+ case "true":
3099
+ return [true, tag.end];
3100
+ case "false":
3101
+ return [false, tag.end];
3102
+ case "dict":
3103
+ return [{}, tag.end];
3104
+ case "array":
3105
+ return [[], tag.end];
3106
+ default:
3107
+ return ["", tag.end];
3108
+ }
3109
+ }
3110
+ switch (tag.name) {
3111
+ case "dict": {
3112
+ const result = {};
3113
+ let cursor = tag.end;
3114
+ for (; ; ) {
3115
+ const next = nextTag(xml, cursor);
3116
+ if (!next) throw new Error("Unterminated <dict> in plist");
3117
+ if (next.closing && next.name === "dict") return [result, next.end];
3118
+ if (next.name !== "key") throw new Error("Expected <key> in <dict>");
3119
+ const [key, afterKey] = textUntilClose(xml, next.end, "key");
3120
+ const [value, afterValue] = parseValue(xml, afterKey, depth + 1);
3121
+ result[key] = value;
3122
+ cursor = afterValue;
3123
+ }
3124
+ }
3125
+ case "array": {
3126
+ const result = [];
3127
+ let cursor = tag.end;
3128
+ for (; ; ) {
3129
+ const next = nextTag(xml, cursor);
3130
+ if (!next) throw new Error("Unterminated <array> in plist");
3131
+ if (next.closing && next.name === "array") return [result, next.end];
3132
+ const [value, afterValue] = parseValue(xml, cursor, depth + 1);
3133
+ result.push(value);
3134
+ cursor = afterValue;
3135
+ }
3136
+ }
3137
+ case "integer":
3138
+ case "real": {
3139
+ const [text2, end] = textUntilClose(xml, tag.end, tag.name);
3140
+ return [Number(text2.trim()), end];
3141
+ }
3142
+ // Dates and data are returned verbatim: callers need the timestamp text and
3143
+ // never the certificate bytes, so decoding them would only cost memory.
3144
+ case "string":
3145
+ case "date":
3146
+ case "data": {
3147
+ const [text2, end] = textUntilClose(xml, tag.end, tag.name);
3148
+ return [tag.name === "string" ? text2 : text2.trim(), end];
3149
+ }
3150
+ default: {
3151
+ const [, end] = textUntilClose(xml, tag.end, tag.name);
3152
+ return [null, end];
3018
3153
  }
3019
- });
3020
- if (result.exitCode !== 0 || result.cancelled || result.timedOut || result.signal !== null) {
3021
- return result;
3022
- }
3023
- if (exceededLimit) {
3024
- throw new Error(
3025
- `ccusage JSON exceeded the ${MAX_CCUSAGE_STDOUT_BYTES} byte limit`
3026
- );
3027
- }
3028
- let parsed;
3029
- try {
3030
- parsed = JSON.parse(stdout.join("\n"));
3031
- } catch {
3032
- throw new Error("ccusage returned malformed JSON");
3033
3154
  }
3034
- return { ...result, report: parseCcusageReport(parsed) };
3035
3155
  }
3036
-
3037
- // src/handlers/build-data.ts
3038
- import { lstat, opendir, readdir, realpath, rm } from "node:fs/promises";
3039
- import { homedir as homedir2 } from "node:os";
3040
- import { basename, dirname as dirname2, isAbsolute, join as join2, resolve } from "node:path";
3156
+ function parsePlist(xml) {
3157
+ const start = xml.indexOf("<plist");
3158
+ const from = start < 0 ? 0 : xml.indexOf(">", start) + 1;
3159
+ return parseValue(xml, from, 0)[0];
3160
+ }
3041
3161
 
3042
3162
  // src/capture-command.ts
3043
3163
  import { spawn as spawn2 } from "node:child_process";
@@ -3107,7 +3227,333 @@ function captureCommand(options) {
3107
3227
  });
3108
3228
  }
3109
3229
 
3230
+ // src/handlers/signing.ts
3231
+ var PROFILE_DIRECTORIES = [
3232
+ join2(
3233
+ homedir2(),
3234
+ "Library",
3235
+ "Developer",
3236
+ "Xcode",
3237
+ "UserData",
3238
+ "Provisioning Profiles"
3239
+ ),
3240
+ join2(homedir2(), "Library", "MobileDevice", "Provisioning Profiles")
3241
+ ];
3242
+ var PROFILE_EXTENSIONS = [".mobileprovision", ".provisionprofile"];
3243
+ function text(value) {
3244
+ return typeof value === "string" && value.trim() ? value : null;
3245
+ }
3246
+ function stringList(value) {
3247
+ return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
3248
+ }
3249
+ async function decodeProfile(path, timeoutMs, signal) {
3250
+ try {
3251
+ const result = await captureCommand({
3252
+ command: "/usr/bin/security",
3253
+ args: ["cms", "-D", "-i", path],
3254
+ timeoutMs: Math.min(timeoutMs, 1e4),
3255
+ signal
3256
+ });
3257
+ if (result.exitCode !== 0 || !result.stdout.trim()) return null;
3258
+ const parsed = parsePlist(result.stdout);
3259
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
3260
+ return null;
3261
+ }
3262
+ return parsed;
3263
+ } catch {
3264
+ return null;
3265
+ }
3266
+ }
3267
+ function toProfile(uuid, raw, now) {
3268
+ const name = text(raw.Name);
3269
+ if (!name) return null;
3270
+ const entitlements = raw.Entitlements && typeof raw.Entitlements === "object" ? raw.Entitlements : {};
3271
+ const applicationIdentifier = text(entitlements["application-identifier"]);
3272
+ const teamId = stringList(raw.TeamIdentifier)[0] ?? text(entitlements["com.apple.developer.team-identifier"]);
3273
+ const bundleId = applicationIdentifier ? applicationIdentifier.replace(/^[A-Z0-9]+\./, "") : "*";
3274
+ const expiresAt = text(raw.ExpirationDate);
3275
+ const expiry = expiresAt ? Date.parse(expiresAt) : Number.NaN;
3276
+ return {
3277
+ uuid,
3278
+ name,
3279
+ teamId: teamId ?? null,
3280
+ teamName: text(raw.TeamName),
3281
+ bundleId,
3282
+ type: provisioningProfileType({
3283
+ getTaskAllow: entitlements["get-task-allow"] === true,
3284
+ hasProvisionedDevices: stringList(raw.ProvisionedDevices).length > 0,
3285
+ provisionsAllDevices: raw.ProvisionsAllDevices === true
3286
+ }),
3287
+ platforms: stringList(raw.Platform),
3288
+ expiresAt,
3289
+ expired: Number.isFinite(expiry) ? expiry < now : false,
3290
+ xcodeManaged: raw.IsXcodeManaged === true,
3291
+ certificateSha1s: certificateFingerprints(raw.DeveloperCertificates)
3292
+ };
3293
+ }
3294
+ function certificateFingerprints(value) {
3295
+ return stringList(value).flatMap((encoded) => {
3296
+ try {
3297
+ const der = Buffer.from(encoded.replace(/\s+/g, ""), "base64");
3298
+ if (!der.length) return [];
3299
+ return [createHash2("sha1").update(der).digest("hex").toUpperCase()];
3300
+ } catch {
3301
+ return [];
3302
+ }
3303
+ });
3304
+ }
3305
+ async function readProfiles(timeoutMs, signal) {
3306
+ const profiles = /* @__PURE__ */ new Map();
3307
+ const now = Date.now();
3308
+ for (const directory of PROFILE_DIRECTORIES) {
3309
+ let entries;
3310
+ try {
3311
+ entries = await readdir(directory);
3312
+ } catch {
3313
+ continue;
3314
+ }
3315
+ for (const entry of entries) {
3316
+ if (!PROFILE_EXTENSIONS.some((suffix) => entry.endsWith(suffix)))
3317
+ continue;
3318
+ signal.throwIfAborted();
3319
+ const raw = await decodeProfile(
3320
+ join2(directory, entry),
3321
+ timeoutMs,
3322
+ signal
3323
+ );
3324
+ if (!raw) continue;
3325
+ const uuid = text(raw.UUID) ?? basename(entry).replace(/\.[^.]+$/, "");
3326
+ const profile = toProfile(uuid, raw, now);
3327
+ if (profile && !profiles.has(profile.uuid)) {
3328
+ profiles.set(profile.uuid, profile);
3329
+ }
3330
+ }
3331
+ }
3332
+ return dedupeProfiles([...profiles.values()]).sort(
3333
+ (left, right) => left.name.localeCompare(right.name)
3334
+ );
3335
+ }
3336
+ function dedupeProfiles(profiles) {
3337
+ const best = /* @__PURE__ */ new Map();
3338
+ for (const profile of profiles) {
3339
+ const key = [
3340
+ profile.name,
3341
+ profile.type,
3342
+ profile.bundleId,
3343
+ profile.teamId ?? "",
3344
+ [...profile.platforms].sort().join(","),
3345
+ [...profile.certificateSha1s].sort().join(",")
3346
+ ].join("\0");
3347
+ const existing = best.get(key);
3348
+ if (!existing || (profile.expiresAt ?? "") > (existing.expiresAt ?? "") || // Stable tie-break so repeated inspections return the same profile.
3349
+ profile.expiresAt === existing.expiresAt && profile.uuid < existing.uuid) {
3350
+ best.set(key, profile);
3351
+ }
3352
+ }
3353
+ return [...best.values()];
3354
+ }
3355
+ var IDENTITY_PATTERN = /^\s*\d+\)\s+([0-9A-F]{40})\s+"(.+)"\s*$/i;
3356
+ async function readIdentities(timeoutMs, signal) {
3357
+ try {
3358
+ const result = await captureCommand({
3359
+ command: "/usr/bin/security",
3360
+ args: ["find-identity", "-v", "-p", "codesigning"],
3361
+ timeoutMs: Math.min(timeoutMs, 1e4),
3362
+ signal
3363
+ });
3364
+ if (result.exitCode !== 0) return [];
3365
+ const identities = /* @__PURE__ */ new Map();
3366
+ for (const line of result.stdout.split("\n")) {
3367
+ const match = IDENTITY_PATTERN.exec(line);
3368
+ if (!match) continue;
3369
+ const [, sha1, name] = match;
3370
+ const team = /\(([A-Z0-9]{10})\)\s*$/.exec(name);
3371
+ identities.set(sha1, { sha1, name, teamId: team?.[1] ?? null });
3372
+ }
3373
+ return [...identities.values()];
3374
+ } catch {
3375
+ return [];
3376
+ }
3377
+ }
3378
+ async function readArchiveBundles(archivePath, timeoutMs, signal) {
3379
+ const products = join2(archivePath, "Products");
3380
+ const bundles = [];
3381
+ const queue = [products];
3382
+ while (queue.length) {
3383
+ signal.throwIfAborted();
3384
+ const current = queue.shift();
3385
+ let entries;
3386
+ try {
3387
+ entries = await readdir(current, { withFileTypes: true });
3388
+ } catch {
3389
+ continue;
3390
+ }
3391
+ for (const entry of entries) {
3392
+ if (entry.isSymbolicLink() || !entry.isDirectory()) continue;
3393
+ const path = join2(current, entry.name);
3394
+ const signable = /\.(app|appex)$/.test(entry.name);
3395
+ if (!signable) {
3396
+ if (!/\.[A-Za-z0-9]+$/.test(entry.name)) queue.push(path);
3397
+ continue;
3398
+ }
3399
+ const bundleId = await bundleIdentifier(path, timeoutMs, signal);
3400
+ if (bundleId) {
3401
+ bundles.push({
3402
+ bundleId,
3403
+ name: basename(entry.name).replace(/\.(app|appex)$/, ""),
3404
+ relativePath: relative(archivePath, path).split(sep).join("/"),
3405
+ ...await embeddedProfile(path, timeoutMs, signal)
3406
+ });
3407
+ }
3408
+ queue.push(path);
3409
+ }
3410
+ }
3411
+ return bundles.sort(
3412
+ (left, right) => left.relativePath.localeCompare(right.relativePath)
3413
+ );
3414
+ }
3415
+ async function bundleIdentifier(bundlePath, timeoutMs, signal) {
3416
+ try {
3417
+ const result = await captureCommand({
3418
+ command: "/usr/bin/plutil",
3419
+ args: [
3420
+ "-extract",
3421
+ "CFBundleIdentifier",
3422
+ "raw",
3423
+ "-o",
3424
+ "-",
3425
+ join2(bundlePath, "Info.plist")
3426
+ ],
3427
+ timeoutMs: Math.min(timeoutMs, 5e3),
3428
+ signal
3429
+ });
3430
+ const value = result.stdout.trim();
3431
+ return result.exitCode === 0 && value ? value : null;
3432
+ } catch {
3433
+ return null;
3434
+ }
3435
+ }
3436
+ async function embeddedProfile(bundlePath, timeoutMs, signal) {
3437
+ for (const name of [
3438
+ "embedded.mobileprovision",
3439
+ "embedded.provisionprofile"
3440
+ ]) {
3441
+ const path = join2(bundlePath, name);
3442
+ try {
3443
+ await stat2(path);
3444
+ } catch {
3445
+ continue;
3446
+ }
3447
+ const raw = await decodeProfile(path, timeoutMs, signal);
3448
+ if (raw) {
3449
+ return {
3450
+ embeddedProfileUuid: text(raw.UUID),
3451
+ embeddedProfileName: text(raw.Name)
3452
+ };
3453
+ }
3454
+ }
3455
+ return { embeddedProfileUuid: null, embeddedProfileName: null };
3456
+ }
3457
+ function teamsFrom(profiles, identities) {
3458
+ const teams = /* @__PURE__ */ new Map();
3459
+ for (const profile of profiles) {
3460
+ if (!profile.teamId) continue;
3461
+ const existing = teams.get(profile.teamId);
3462
+ if (!existing && profile.teamName)
3463
+ teams.set(profile.teamId, profile.teamName);
3464
+ else if (!existing) teams.set(profile.teamId, profile.teamId);
3465
+ }
3466
+ for (const identity of identities) {
3467
+ if (!identity.teamId || teams.has(identity.teamId)) continue;
3468
+ const name = /^[^:]+:\s*(.+?)\s*\([A-Z0-9]{10}\)\s*$/.exec(identity.name);
3469
+ teams.set(identity.teamId, name?.[1] ?? identity.teamId);
3470
+ }
3471
+ return [...teams.entries()].map(([id, name]) => ({ id, name })).sort((left, right) => left.name.localeCompare(right.name));
3472
+ }
3473
+ async function inspectSigningAssets(archivePath, timeoutMs, signal) {
3474
+ const [profiles, identities, bundles] = await Promise.all([
3475
+ readProfiles(timeoutMs, signal),
3476
+ readIdentities(timeoutMs, signal),
3477
+ readArchiveBundles(archivePath, timeoutMs, signal)
3478
+ ]);
3479
+ return {
3480
+ teams: teamsFrom(profiles, identities),
3481
+ identities,
3482
+ profiles,
3483
+ bundles
3484
+ };
3485
+ }
3486
+ var inspectIosSigning = async (payload, timeoutMs, signal) => {
3487
+ const input = parseBuildSigningInspectPayload(payload);
3488
+ const archivePath = join2(input.artifactDirectory, input.archiveRelativePath);
3489
+ const inspection = await inspectSigningAssets(archivePath, timeoutMs, signal);
3490
+ return {
3491
+ exitCode: 0,
3492
+ signal: null,
3493
+ timedOut: false,
3494
+ cancelled: false,
3495
+ ...inspection
3496
+ };
3497
+ };
3498
+
3499
+ // src/handlers/ccusage.ts
3500
+ import { Buffer as Buffer2 } from "node:buffer";
3501
+ var MAX_CCUSAGE_STDOUT_BYTES = 16 * 1024 * 1024;
3502
+ function validateCcusagePayload(payload) {
3503
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
3504
+ throw new Error("ccusage.report payload must be an object");
3505
+ }
3506
+ const keys = Object.keys(payload);
3507
+ if (keys.length > 0) {
3508
+ throw new Error(`Unexpected ccusage.report payload field: ${keys[0]}`);
3509
+ }
3510
+ return {};
3511
+ }
3512
+ async function runCcusage(payload, timeoutMs, signal, onLog) {
3513
+ validateCcusagePayload(payload);
3514
+ const stdout = [];
3515
+ let stdoutBytes = 0;
3516
+ let exceededLimit = false;
3517
+ const result = await runProcess({
3518
+ command: "ccusage",
3519
+ args: ["--json"],
3520
+ timeoutMs,
3521
+ signal,
3522
+ onLog: async (log) => {
3523
+ if (log.stream !== "STDOUT") {
3524
+ await onLog(log);
3525
+ return;
3526
+ }
3527
+ const lineBytes = Buffer2.byteLength(log.message, "utf8") + 1;
3528
+ if (stdoutBytes + lineBytes > MAX_CCUSAGE_STDOUT_BYTES) {
3529
+ exceededLimit = true;
3530
+ return;
3531
+ }
3532
+ stdoutBytes += lineBytes;
3533
+ stdout.push(log.message);
3534
+ }
3535
+ });
3536
+ if (result.exitCode !== 0 || result.cancelled || result.timedOut || result.signal !== null) {
3537
+ return result;
3538
+ }
3539
+ if (exceededLimit) {
3540
+ throw new Error(
3541
+ `ccusage JSON exceeded the ${MAX_CCUSAGE_STDOUT_BYTES} byte limit`
3542
+ );
3543
+ }
3544
+ let parsed;
3545
+ try {
3546
+ parsed = JSON.parse(stdout.join("\n"));
3547
+ } catch {
3548
+ throw new Error("ccusage returned malformed JSON");
3549
+ }
3550
+ return { ...result, report: parseCcusageReport(parsed) };
3551
+ }
3552
+
3110
3553
  // src/handlers/build-data.ts
3554
+ import { lstat, opendir, readdir as readdir2, realpath, rm } from "node:fs/promises";
3555
+ import { homedir as homedir3 } from "node:os";
3556
+ import { basename as basename2, dirname as dirname2, isAbsolute, join as join3, resolve } from "node:path";
3111
3557
  var successfulProcess = {
3112
3558
  exitCode: 0,
3113
3559
  signal: null,
@@ -3124,7 +3570,7 @@ function safeRelativePath3(value) {
3124
3570
  }
3125
3571
  async function configuredRoots(payload) {
3126
3572
  if (payload.mode === "DEFAULT") {
3127
- return [join2(homedir2(), "Library", "Developer", "Xcode", "DerivedData")];
3573
+ return [join3(homedir3(), "Library", "Developer", "Xcode", "DerivedData")];
3128
3574
  }
3129
3575
  if (payload.mode === "ABSOLUTE") {
3130
3576
  if (!payload.path || !isAbsolute(payload.path)) {
@@ -3173,7 +3619,7 @@ async function scanRoot(configuredRoot, timeoutMs, signal) {
3173
3619
  }
3174
3620
  let children;
3175
3621
  try {
3176
- children = await readdir(rootPath2, { withFileTypes: true });
3622
+ children = await readdir2(rootPath2, { withFileTypes: true });
3177
3623
  } catch (error) {
3178
3624
  return {
3179
3625
  entries: [],
@@ -3184,9 +3630,9 @@ async function scanRoot(configuredRoot, timeoutMs, signal) {
3184
3630
  for (const child of children) {
3185
3631
  signal.throwIfAborted();
3186
3632
  if (!child.isDirectory()) continue;
3187
- const path = join2(rootPath2, child.name);
3633
+ const path = join3(rootPath2, child.name);
3188
3634
  const projectPath = await workspacePath(
3189
- join2(path, "info.plist"),
3635
+ join3(path, "info.plist"),
3190
3636
  timeoutMs,
3191
3637
  signal
3192
3638
  );
@@ -3228,7 +3674,7 @@ function assertDirectChild(rootPath2, path) {
3228
3674
  }
3229
3675
  const root = resolve(rootPath2);
3230
3676
  const target2 = resolve(path);
3231
- if (target2 === root || dirname2(target2) !== root || basename(target2) !== basename(path)) {
3677
+ if (target2 === root || dirname2(target2) !== root || basename2(target2) !== basename2(path)) {
3232
3678
  throw new Error("Build Data target must be a direct child of its root");
3233
3679
  }
3234
3680
  }
@@ -3242,7 +3688,7 @@ async function allocatedBytes(path, signal) {
3242
3688
  try {
3243
3689
  for await (const entry of directory) {
3244
3690
  signal.throwIfAborted();
3245
- total += await allocatedBytes(join2(path, entry.name), signal);
3691
+ total += await allocatedBytes(join3(path, entry.name), signal);
3246
3692
  }
3247
3693
  } finally {
3248
3694
  await directory.close().catch(() => void 0);
@@ -3310,9 +3756,9 @@ var deleteBuildData = async (rawPayload, _timeoutMs, signal) => {
3310
3756
  };
3311
3757
 
3312
3758
  // src/handlers/codebases.ts
3313
- import { readdir as readdir2, realpath as realpath2, stat as stat2 } from "node:fs/promises";
3314
- import { homedir as homedir3 } from "node:os";
3315
- import { dirname as dirname3, join as join3, resolve as resolve2 } from "node:path";
3759
+ import { readdir as readdir3, realpath as realpath2, stat as stat3 } from "node:fs/promises";
3760
+ import { homedir as homedir4 } from "node:os";
3761
+ import { dirname as dirname3, join as join4, resolve as resolve2 } from "node:path";
3316
3762
  var successfulProcess2 = {
3317
3763
  exitCode: 0,
3318
3764
  signal: null,
@@ -3389,7 +3835,7 @@ function baseSnapshot(folder) {
3389
3835
  }
3390
3836
  async function fetchedAt(commonDirectory) {
3391
3837
  try {
3392
- return (await stat2(join3(commonDirectory, "FETCH_HEAD"))).mtime.toISOString();
3838
+ return (await stat3(join4(commonDirectory, "FETCH_HEAD"))).mtime.toISOString();
3393
3839
  } catch {
3394
3840
  return null;
3395
3841
  }
@@ -3400,7 +3846,7 @@ async function inspectCodebaseProcess(selectedFolder, timeoutMs, signal, expecte
3400
3846
  let selected;
3401
3847
  try {
3402
3848
  selected = await realpath2(fallbackFolder);
3403
- if (!(await stat2(selected)).isDirectory())
3849
+ if (!(await stat3(selected)).isDirectory())
3404
3850
  throw new Error("Folder is not a directory");
3405
3851
  } catch (error) {
3406
3852
  return {
@@ -3568,23 +4014,23 @@ async function inspectCodebase(selectedFolder, timeoutMs, signal, expectedOrigin
3568
4014
  }
3569
4015
  var browseCodebaseDirectories = async (payload) => {
3570
4016
  const input = codebaseBrowsePayload(payload);
3571
- const homePath = await realpath2(homedir3());
4017
+ const homePath = await realpath2(homedir4());
3572
4018
  const path = await realpath2(input.path ? resolve2(input.path) : homePath);
3573
- if (!(await stat2(path)).isDirectory())
4019
+ if (!(await stat3(path)).isDirectory())
3574
4020
  throw new Error("Path is not a directory");
3575
- const candidates = await readdir2(path, { withFileTypes: true });
4021
+ const candidates = await readdir3(path, { withFileTypes: true });
3576
4022
  const entries = [];
3577
4023
  for (const candidate of candidates) {
3578
4024
  if (candidate.isDirectory()) {
3579
4025
  entries.push({
3580
4026
  name: candidate.name,
3581
- path: join3(path, candidate.name),
4027
+ path: join4(path, candidate.name),
3582
4028
  hidden: candidate.name.startsWith(".")
3583
4029
  });
3584
4030
  } else if (candidate.isSymbolicLink()) {
3585
4031
  try {
3586
- const target2 = join3(path, candidate.name);
3587
- if ((await stat2(target2)).isDirectory()) {
4032
+ const target2 = join4(path, candidate.name);
4033
+ if ((await stat3(target2)).isDirectory()) {
3588
4034
  entries.push({
3589
4035
  name: candidate.name,
3590
4036
  path: target2,
@@ -4154,14 +4600,14 @@ import {
4154
4600
  mkdir as mkdir2,
4155
4601
  readFile as readFile2,
4156
4602
  realpath as realpath3,
4157
- readdir as readdir3,
4603
+ readdir as readdir4,
4158
4604
  rename as rename2,
4159
4605
  rm as rm2,
4160
- stat as stat3,
4606
+ stat as stat4,
4161
4607
  writeFile as writeFile2
4162
4608
  } from "node:fs/promises";
4163
- import { homedir as homedir4 } from "node:os";
4164
- import { dirname as dirname4, isAbsolute as isAbsolute2, join as join4, relative, resolve as resolve3, sep } from "node:path";
4609
+ import { homedir as homedir5 } from "node:os";
4610
+ import { dirname as dirname4, isAbsolute as isAbsolute2, join as join5, relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
4165
4611
  import { promisify } from "node:util";
4166
4612
  var executeFile = promisify(execFile);
4167
4613
  var successfulProcess3 = {
@@ -4216,27 +4662,27 @@ var PROJECT_ROOTS = {
4216
4662
  };
4217
4663
  async function directoryExists(path) {
4218
4664
  try {
4219
- return (await stat3(path)).isDirectory();
4665
+ return (await stat4(path)).isDirectory();
4220
4666
  } catch {
4221
4667
  return false;
4222
4668
  }
4223
4669
  }
4224
4670
  async function configuredTools(tools) {
4225
- const homePath = homedir4();
4671
+ const homePath = homedir5();
4226
4672
  return Promise.all(
4227
4673
  AI_TOOLS.filter((tool) => tools.includes(tool)).map(async (tool) => ({
4228
4674
  tool,
4229
- configured: await directoryExists(join4(homePath, ...CONFIG_PATHS[tool])),
4675
+ configured: await directoryExists(join5(homePath, ...CONFIG_PATHS[tool])),
4230
4676
  homePath
4231
4677
  }))
4232
4678
  );
4233
4679
  }
4234
4680
  function rootPath(location) {
4235
- const base = location.scope === "GLOBAL" ? homedir4() : location.folder;
4681
+ const base = location.scope === "GLOBAL" ? homedir5() : location.folder;
4236
4682
  const parts = location.scope === "GLOBAL" ? GLOBAL_ROOTS[location.rootKind] : PROJECT_ROOTS[location.rootKind];
4237
4683
  const path = resolve3(base, ...parts);
4238
4684
  const normalizedBase = resolve3(base);
4239
- if (path !== normalizedBase && !path.startsWith(`${normalizedBase}${sep}`)) {
4685
+ if (path !== normalizedBase && !path.startsWith(`${normalizedBase}${sep2}`)) {
4240
4686
  throw new Error("Skill root escaped its configured base directory");
4241
4687
  }
4242
4688
  return path;
@@ -4248,12 +4694,12 @@ async function listPackageFiles(skillDirectory) {
4248
4694
  const files = [];
4249
4695
  let totalBytes = 0;
4250
4696
  const visit = async (directory) => {
4251
- for (const entry of await readdir3(directory, { withFileTypes: true })) {
4252
- const path = join4(directory, entry.name);
4697
+ for (const entry of await readdir4(directory, { withFileTypes: true })) {
4698
+ const path = join5(directory, entry.name);
4253
4699
  const metadata = await lstat2(path);
4254
4700
  if (metadata.isSymbolicLink()) {
4255
4701
  throw new Error(
4256
- `Nested symbolic link is not supported: ${relative(skillDirectory, path)}`
4702
+ `Nested symbolic link is not supported: ${relative2(skillDirectory, path)}`
4257
4703
  );
4258
4704
  }
4259
4705
  if (metadata.isDirectory()) {
@@ -4265,14 +4711,14 @@ async function listPackageFiles(skillDirectory) {
4265
4711
  throw new Error("Skill contains too many files");
4266
4712
  if (metadata.size > MAX_SKILL_FILE_BYTES) {
4267
4713
  throw new Error(
4268
- `${relative(skillDirectory, path)} exceeds the per-file limit`
4714
+ `${relative2(skillDirectory, path)} exceeds the per-file limit`
4269
4715
  );
4270
4716
  }
4271
4717
  totalBytes += metadata.size;
4272
4718
  if (totalBytes > MAX_SKILL_PACKAGE_BYTES)
4273
4719
  throw new Error("Skill exceeds the package size limit");
4274
4720
  files.push({
4275
- path: relative(skillDirectory, path).split(sep).join("/"),
4721
+ path: relative2(skillDirectory, path).split(sep2).join("/"),
4276
4722
  contentsBase64: (await readFile2(path)).toString("base64"),
4277
4723
  executable: (metadata.mode & 73) !== 0
4278
4724
  });
@@ -4295,7 +4741,7 @@ async function readPackage(directory) {
4295
4741
  const frontmatter = parseSkillMetadata(
4296
4742
  Buffer.from(definition.contentsBase64, "base64").toString("utf8")
4297
4743
  );
4298
- if (frontmatter.name !== directory.split(sep).at(-1)) {
4744
+ if (frontmatter.name !== directory.split(sep2).at(-1)) {
4299
4745
  throw new Error("Skill name must match its directory name");
4300
4746
  }
4301
4747
  return {
@@ -4336,12 +4782,12 @@ async function scanRoot2(location, configured, targets, warnings) {
4336
4782
  if (!consumers.length) return [];
4337
4783
  const target2 = targetForFolder(targets, location.folder);
4338
4784
  const installations = [];
4339
- for (const entry of await readdir3(root, { withFileTypes: true })) {
4785
+ for (const entry of await readdir4(root, { withFileTypes: true })) {
4340
4786
  if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
4341
- const directory = join4(root, entry.name);
4787
+ const directory = join5(root, entry.name);
4342
4788
  try {
4343
4789
  const skillPackage = await readPackage(directory);
4344
- const relativeDirectory = location.folder ? relative(location.folder, directory).split(sep).join("/") : "";
4790
+ const relativeDirectory = location.folder ? relative2(location.folder, directory).split(sep2).join("/") : "";
4345
4791
  installations.push({
4346
4792
  ...location,
4347
4793
  codebaseId: target2?.codebaseId ?? null,
@@ -4417,7 +4863,7 @@ var readSkills = async (payload) => {
4417
4863
  }
4418
4864
  packages.push({
4419
4865
  ...request,
4420
- package: await readPackage(join4(root, request.skillName))
4866
+ package: await readPackage(join5(root, request.skillName))
4421
4867
  });
4422
4868
  }
4423
4869
  return { ...successfulProcess3, packages };
@@ -4452,7 +4898,7 @@ async function updateManagedExclude(folder, relativeSkillPath, present) {
4452
4898
  if (line.trim()) managed.add(line.trim());
4453
4899
  }
4454
4900
  }
4455
- const pattern = `/${relativeSkillPath.split(sep).join("/").replace(/^\/+/, "")}/`;
4901
+ const pattern = `/${relativeSkillPath.split(sep2).join("/").replace(/^\/+/, "")}/`;
4456
4902
  if (present) managed.add(pattern);
4457
4903
  else managed.delete(pattern);
4458
4904
  const block = managed.size ? `${MANAGED_EXCLUDE_START}
@@ -4467,7 +4913,7 @@ ${MANAGED_EXCLUDE_END}` : "";
4467
4913
  }
4468
4914
  async function assertUntracked(location, skillDirectory) {
4469
4915
  if (location.scope !== "PROJECT") return;
4470
- const relativeDirectory = relative(location.folder, skillDirectory).split(sep).join("/");
4916
+ const relativeDirectory = relative2(location.folder, skillDirectory).split(sep2).join("/");
4471
4917
  if (await trackedProjectPath(location.folder, relativeDirectory)) {
4472
4918
  throw new Error(
4473
4919
  `${relativeDirectory} is tracked by Git and cannot be managed automatically`
@@ -4479,16 +4925,16 @@ async function writePackage(location, skillPackage) {
4479
4925
  throw new Error("New skill copies may only be written to shared roots");
4480
4926
  }
4481
4927
  const root = rootPath(location);
4482
- const destination = join4(root, skillPackage.name);
4928
+ const destination = join5(root, skillPackage.name);
4483
4929
  await assertUntracked(location, destination);
4484
4930
  await mkdir2(root, { recursive: true });
4485
- const temporary = join4(root, `.${skillPackage.name}.${randomUUID()}.tmp`);
4486
- const backup = join4(root, `.${skillPackage.name}.${randomUUID()}.bak`);
4931
+ const temporary = join5(root, `.${skillPackage.name}.${randomUUID()}.tmp`);
4932
+ const backup = join5(root, `.${skillPackage.name}.${randomUUID()}.bak`);
4487
4933
  await mkdir2(temporary, { recursive: true });
4488
4934
  try {
4489
4935
  for (const file of skillPackage.files) {
4490
4936
  const path = resolve3(temporary, ...file.path.split("/"));
4491
- if (!path.startsWith(`${temporary}${sep}`))
4937
+ if (!path.startsWith(`${temporary}${sep2}`))
4492
4938
  throw new Error("Skill file escaped temporary directory");
4493
4939
  await mkdir2(dirname4(path), { recursive: true });
4494
4940
  await writeFile2(path, Buffer.from(file.contentsBase64, "base64"));
@@ -4521,7 +4967,7 @@ var applySkills = async (payload) => {
4521
4967
  for (const operation of operations) {
4522
4968
  const root = rootPath(operation);
4523
4969
  const skillName = operation.kind === "WRITE" ? operation.package.name : operation.skillName;
4524
- const destination = join4(root, skillName);
4970
+ const destination = join5(root, skillName);
4525
4971
  await assertUntracked(operation, destination);
4526
4972
  if (operation.kind === "WRITE") {
4527
4973
  await writePackage(operation, operation.package);
@@ -4541,7 +4987,7 @@ var applySkills = async (payload) => {
4541
4987
  if (operation.scope === "PROJECT" && operation.manageGitExclude) {
4542
4988
  await updateManagedExclude(
4543
4989
  operation.folder,
4544
- relative(operation.folder, destination),
4990
+ relative2(operation.folder, destination),
4545
4991
  operation.kind === "WRITE"
4546
4992
  );
4547
4993
  }
@@ -4551,16 +4997,16 @@ var applySkills = async (payload) => {
4551
4997
 
4552
4998
  // src/handlers/worktrees.ts
4553
4999
  import { createWriteStream, watch } from "node:fs";
4554
- import { mkdtemp, open, readFile as readFile3, realpath as realpath4, rm as rm3, stat as stat4 } from "node:fs/promises";
5000
+ import { mkdtemp, open, readFile as readFile3, realpath as realpath4, rm as rm3, stat as stat5 } from "node:fs/promises";
4555
5001
  import { tmpdir } from "node:os";
4556
- import { basename as basename2, dirname as dirname5, join as join5, relative as relative3 } from "node:path";
5002
+ import { basename as basename3, dirname as dirname5, join as join6, relative as relative4 } from "node:path";
4557
5003
  import { spawn as spawn4 } from "node:child_process";
4558
5004
 
4559
5005
  // src/git-code-state.ts
4560
- import { createHash as createHash2 } from "node:crypto";
5006
+ import { createHash as createHash3 } from "node:crypto";
4561
5007
  import { createReadStream as createReadStream2 } from "node:fs";
4562
5008
  import { lstat as lstat3, readlink } from "node:fs/promises";
4563
- import { isAbsolute as isAbsolute3, relative as relative2, resolve as resolve4, sep as sep2 } from "node:path";
5009
+ import { isAbsolute as isAbsolute3, relative as relative3, resolve as resolve4, sep as sep3 } from "node:path";
4564
5010
  import { spawn as spawn3 } from "node:child_process";
4565
5011
  function gitEnvironment() {
4566
5012
  return {
@@ -4574,8 +5020,8 @@ function remainingTimeoutMs(deadline) {
4574
5020
  }
4575
5021
  function containedPath(folder, path) {
4576
5022
  const absolutePath = resolve4(folder, path);
4577
- const difference = relative2(folder, absolutePath);
4578
- if (!difference || difference === ".." || difference.startsWith(`..${sep2}`) || isAbsolute3(difference)) {
5023
+ const difference = relative3(folder, absolutePath);
5024
+ if (!difference || difference === ".." || difference.startsWith(`..${sep3}`) || isAbsolute3(difference)) {
4579
5025
  return null;
4580
5026
  }
4581
5027
  return absolutePath;
@@ -4684,7 +5130,7 @@ async function worktreeCodeStateHash(folder, timeoutMs, signal) {
4684
5130
  if (head.exitCode !== 0 || untracked.exitCode !== 0 || submodules.exitCode !== 0 || operation.signal.aborted) {
4685
5131
  return null;
4686
5132
  }
4687
- const hash = createHash2("sha256");
5133
+ const hash = createHash3("sha256");
4688
5134
  hash.update("head\0");
4689
5135
  hash.update(head.stdout.trim());
4690
5136
  hash.update("\0diff\0");
@@ -4963,7 +5409,7 @@ async function origin(folder, timeoutMs, signal) {
4963
5409
  }
4964
5410
  async function validateWorktree(input, timeoutMs, signal) {
4965
5411
  const folder = await realpath4(input.folder);
4966
- if (!(await stat4(folder)).isDirectory())
5412
+ if (!(await stat5(folder)).isDirectory())
4967
5413
  throw new Error("Worktree is missing");
4968
5414
  const observedGitDirectory = await gitDirectory(folder, timeoutMs, signal);
4969
5415
  if (observedGitDirectory !== input.gitDirectory) {
@@ -5047,7 +5493,7 @@ async function inspectWorktreeItem(folderValue2, rootFolder, baseBranch, primary
5047
5493
  return {
5048
5494
  gitDirectory: gitDir,
5049
5495
  folder,
5050
- relativePath: relative3(rootFolder, folder) || ".",
5496
+ relativePath: relative4(rootFolder, folder) || ".",
5051
5497
  primary,
5052
5498
  branch,
5053
5499
  headSha: headResult.exitCode === 0 ? headResult.stdout.trim() : null,
@@ -5074,7 +5520,7 @@ async function inspectWorktreeItem(folderValue2, rootFolder, baseBranch, primary
5074
5520
  return {
5075
5521
  gitDirectory: folderValue2,
5076
5522
  folder: folderValue2,
5077
- relativePath: relative3(rootFolder, folderValue2) || ".",
5523
+ relativePath: relative4(rootFolder, folderValue2) || ".",
5078
5524
  primary,
5079
5525
  branch: null,
5080
5526
  headSha: null,
@@ -5190,12 +5636,12 @@ function parseNumstat(value) {
5190
5636
  }
5191
5637
  async function untrackedLines(folder, path) {
5192
5638
  try {
5193
- const file = await stat4(`${folder}/${path}`);
5639
+ const file = await stat5(`${folder}/${path}`);
5194
5640
  if (!file.isFile() || file.size > 1024 * 1024) return null;
5195
5641
  const contents = await readFile3(`${folder}/${path}`);
5196
5642
  if (contents.includes(0)) return null;
5197
- const text = contents.toString("utf8");
5198
- return text ? text.split("\n").length - (text.endsWith("\n") ? 1 : 0) : 0;
5643
+ const text2 = contents.toString("utf8");
5644
+ return text2 ? text2.split("\n").length - (text2.endsWith("\n") ? 1 : 0) : 0;
5199
5645
  } catch {
5200
5646
  return null;
5201
5647
  }
@@ -5401,7 +5847,7 @@ async function comparisonSides(input, folder, timeoutMs, signal) {
5401
5847
  if (!input.path) return { before: null, after: null };
5402
5848
  const path = input.path;
5403
5849
  const previousPath = input.previousPath ?? path;
5404
- const currentPath = join5(folder, path);
5850
+ const currentPath = join6(folder, path);
5405
5851
  if (input.scope === "UNTRACKED") {
5406
5852
  return { before: null, after: { kind: "FILE", path: currentPath } };
5407
5853
  }
@@ -5485,8 +5931,8 @@ async function availableSide(side, folder, timeoutMs, signal) {
5485
5931
  }
5486
5932
  try {
5487
5933
  const resolved = await realpath4(side.path);
5488
- const difference = relative3(folder, resolved);
5489
- return difference !== ".." && !difference.startsWith("../") && (await stat4(resolved)).isFile();
5934
+ const difference = relative4(folder, resolved);
5935
+ return difference !== ".." && !difference.startsWith("../") && (await stat5(resolved)).isFile();
5490
5936
  } catch {
5491
5937
  return false;
5492
5938
  }
@@ -5557,7 +6003,7 @@ async function inspectRequestedDiff(input, folder, timeoutMs, signal) {
5557
6003
  let patch;
5558
6004
  if (input.scope === "UNTRACKED") {
5559
6005
  const bounded = await readBoundedFile(
5560
- join5(folder, input.path),
6006
+ join6(folder, input.path),
5561
6007
  MAX_DIFF_BYTES
5562
6008
  );
5563
6009
  const contents = bounded.contents;
@@ -5728,7 +6174,7 @@ var downloadWorktreeDiffAsset = async (payload, timeoutMs, signal, _onLog, conte
5728
6174
  try {
5729
6175
  if (selected.kind === "FILE") {
5730
6176
  uploadPath = await realpath4(selected.path);
5731
- const difference = relative3(folder, uploadPath);
6177
+ const difference = relative4(folder, uploadPath);
5732
6178
  if (difference === ".." || difference.startsWith("../")) {
5733
6179
  throw new Error("Diff image resolves outside the worktree");
5734
6180
  }
@@ -5745,8 +6191,8 @@ var downloadWorktreeDiffAsset = async (payload, timeoutMs, signal, _onLog, conte
5745
6191
  if (!/^\d+$/.test(size) || Number(size) > 20 * 1024 * 1024) {
5746
6192
  throw new Error("Diff image exceeds the 20 MiB limit");
5747
6193
  }
5748
- temporaryDirectory = await mkdtemp(join5(tmpdir(), "ade-diff-image-"));
5749
- uploadPath = join5(temporaryDirectory, basename2(input.path));
6194
+ temporaryDirectory = await mkdtemp(join6(tmpdir(), "ade-diff-image-"));
6195
+ uploadPath = join6(temporaryDirectory, basename3(input.path));
5750
6196
  await writeGitObject(
5751
6197
  folder,
5752
6198
  selected.specification,
@@ -5755,14 +6201,14 @@ var downloadWorktreeDiffAsset = async (payload, timeoutMs, signal, _onLog, conte
5755
6201
  signal
5756
6202
  );
5757
6203
  }
5758
- const information = await stat4(uploadPath);
6204
+ const information = await stat5(uploadPath);
5759
6205
  if (!information.isFile() || information.size > 20 * 1024 * 1024) {
5760
6206
  throw new Error("Diff image exceeds the 20 MiB limit");
5761
6207
  }
5762
6208
  await context.uploadBuildArtifact({
5763
6209
  uploadId: input.uploadId,
5764
6210
  path: uploadPath,
5765
- filename: basename2(input.path),
6211
+ filename: basename3(input.path),
5766
6212
  contentType: imageContentType(input.path)
5767
6213
  });
5768
6214
  return successfulProcess4;
@@ -5906,7 +6352,7 @@ async function chooseNewBranch(folder, candidates, allowedCurrentBranch, timeout
5906
6352
  }
5907
6353
  async function validateBranchRoot(input, timeoutMs, signal) {
5908
6354
  const rootFolder = await realpath4(input.rootFolder);
5909
- if (!(await stat4(rootFolder)).isDirectory()) {
6355
+ if (!(await stat5(rootFolder)).isDirectory()) {
5910
6356
  throw new Error("Base repository is missing");
5911
6357
  }
5912
6358
  if (await origin(rootFolder, timeoutMs, signal) !== input.expectedOrigin) {
@@ -5969,13 +6415,13 @@ var branchWorktree = async (payload, timeoutMs, signal) => {
5969
6415
  timeoutMs,
5970
6416
  signal
5971
6417
  ) : input.candidates[0];
5972
- const targetFolder = input.action === "CREATE" ? join5(
6418
+ const targetFolder = input.action === "CREATE" ? join6(
5973
6419
  dirname5(rootFolder),
5974
- `${basename2(rootFolder)}-${branch.replaceAll("/", "-")}`
6420
+ `${basename3(rootFolder)}-${branch.replaceAll("/", "-")}`
5975
6421
  ) : changeFolder;
5976
6422
  if (input.action === "CREATE") {
5977
6423
  try {
5978
- await stat4(targetFolder);
6424
+ await stat5(targetFolder);
5979
6425
  throw new Error(`Worktree folder already exists: ${targetFolder}`);
5980
6426
  } catch (error) {
5981
6427
  if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
@@ -6209,12 +6655,12 @@ var checkoutMovedWorktree = async (payload, timeoutMs, signal) => {
6209
6655
  `Branch ${input.branch} is already checked out in ${occupiedFolder}`
6210
6656
  );
6211
6657
  }
6212
- targetFolder = join5(
6658
+ targetFolder = join6(
6213
6659
  dirname5(rootFolder),
6214
- `${basename2(rootFolder)}-${input.branch.replaceAll("/", "-")}`
6660
+ `${basename3(rootFolder)}-${input.branch.replaceAll("/", "-")}`
6215
6661
  );
6216
6662
  try {
6217
- await stat4(targetFolder);
6663
+ await stat5(targetFolder);
6218
6664
  throw new Error(`Worktree folder already exists: ${targetFolder}`);
6219
6665
  } catch (error) {
6220
6666
  if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
@@ -6375,7 +6821,7 @@ var checkoutMovedWorktree = async (payload, timeoutMs, signal) => {
6375
6821
  var deleteWorktree = async (payload, timeoutMs, signal) => {
6376
6822
  const input = worktreeDeleteJobPayload(payload);
6377
6823
  const rootFolder = await realpath4(input.rootFolder);
6378
- if (!(await stat4(rootFolder)).isDirectory()) {
6824
+ if (!(await stat5(rootFolder)).isDirectory()) {
6379
6825
  throw new Error("Base repository is missing");
6380
6826
  }
6381
6827
  if (await origin(rootFolder, timeoutMs, signal) !== input.expectedOrigin) {
@@ -6569,30 +7015,31 @@ var operateWorktree = async (payload, timeoutMs, signal) => {
6569
7015
  };
6570
7016
 
6571
7017
  // src/handlers/builds.ts
6572
- import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypto";
7018
+ import { createHash as createHash4, randomUUID as randomUUID2 } from "node:crypto";
6573
7019
  import {
6574
7020
  cp,
6575
7021
  mkdir as mkdir3,
6576
7022
  mkdtemp as mkdtemp2,
6577
7023
  open as open2,
6578
- readdir as readdir4,
7024
+ readdir as readdir5,
6579
7025
  readFile as readFile4,
6580
7026
  realpath as realpath5,
6581
7027
  rename as rename3,
6582
7028
  rm as rm4,
6583
- stat as stat5,
7029
+ stat as stat6,
6584
7030
  writeFile as writeFile3
6585
7031
  } from "node:fs/promises";
6586
- import { createWriteStream as createWriteStream2 } from "node:fs";
7032
+ import { createReadStream as createReadStream3, createWriteStream as createWriteStream2 } from "node:fs";
6587
7033
  import { tmpdir as tmpdir2 } from "node:os";
7034
+ import { pipeline } from "node:stream/promises";
6588
7035
  import {
6589
- basename as basename3,
7036
+ basename as basename4,
6590
7037
  dirname as dirname6,
6591
7038
  isAbsolute as isAbsolute4,
6592
- join as join6,
6593
- relative as relative4,
7039
+ join as join7,
7040
+ relative as relative5,
6594
7041
  resolve as resolve5,
6595
- sep as sep3
7042
+ sep as sep4
6596
7043
  } from "node:path";
6597
7044
  import { spawn as spawn5 } from "node:child_process";
6598
7045
  var successfulProcess5 = {
@@ -6651,7 +7098,7 @@ function requireSuccess3(result, fallback) {
6651
7098
  }
6652
7099
  async function validateWorktree2(input, timeoutMs, signal) {
6653
7100
  const folder = await realpath5(input.folder);
6654
- if (!(await stat5(folder)).isDirectory())
7101
+ if (!(await stat6(folder)).isDirectory())
6655
7102
  throw new Error("Worktree is missing");
6656
7103
  const gitDirectory2 = requireSuccess3(
6657
7104
  await command2(
@@ -6685,8 +7132,8 @@ async function validateWorktree2(input, timeoutMs, signal) {
6685
7132
  }
6686
7133
  function containedPath2(root, relativePath) {
6687
7134
  const target2 = resolve5(root, relativePath);
6688
- const difference = relative4(root, target2);
6689
- if (difference === ".." || difference.startsWith(`..${sep3}`) || isAbsolute4(difference)) {
7135
+ const difference = relative5(root, target2);
7136
+ if (difference === ".." || difference.startsWith(`..${sep4}`) || isAbsolute4(difference)) {
6690
7137
  throw new Error("Path must stay within the worktree");
6691
7138
  }
6692
7139
  return target2;
@@ -6694,11 +7141,11 @@ function containedPath2(root, relativePath) {
6694
7141
  async function validateSource(root, source) {
6695
7142
  const target2 = containedPath2(root, source.relativePath);
6696
7143
  const resolved = await realpath5(target2);
6697
- const difference = relative4(root, resolved);
6698
- if (difference === ".." || difference.startsWith(`..${sep3}`) || isAbsolute4(difference)) {
7144
+ const difference = relative5(root, resolved);
7145
+ if (difference === ".." || difference.startsWith(`..${sep4}`) || isAbsolute4(difference)) {
6699
7146
  throw new Error("Build source resolves outside the worktree");
6700
7147
  }
6701
- const information = await stat5(resolved);
7148
+ const information = await stat6(resolved);
6702
7149
  if (source.kind === "PACKAGE" && !information.isFile()) {
6703
7150
  throw new Error("Package.swift is missing");
6704
7151
  }
@@ -6712,10 +7159,10 @@ async function discoverSourcesInFolder(folder) {
6712
7159
  const queue = [folder];
6713
7160
  while (queue.length && sources.length < DISCOVERY_LIMIT) {
6714
7161
  const current = queue.shift();
6715
- for (const entry of await readdir4(current, { withFileTypes: true })) {
7162
+ for (const entry of await readdir5(current, { withFileTypes: true })) {
6716
7163
  if (entry.isSymbolicLink()) continue;
6717
- const absolute = join6(current, entry.name);
6718
- const path = relative4(folder, absolute).split(sep3).join("/");
7164
+ const absolute = join7(current, entry.name);
7165
+ const path = relative5(folder, absolute).split(sep4).join("/");
6719
7166
  if (entry.isDirectory()) {
6720
7167
  if (entry.name.endsWith(".xcodeproj")) {
6721
7168
  sources.push({ kind: "PROJECT", relativePath: path });
@@ -6785,7 +7232,7 @@ function metadataFromList(value) {
6785
7232
  };
6786
7233
  }
6787
7234
  async function workspaceProjectPaths(workspacePath2, folder) {
6788
- const contentsPath = join6(workspacePath2, "contents.xcworkspacedata");
7235
+ const contentsPath = join7(workspacePath2, "contents.xcworkspacedata");
6789
7236
  let contents;
6790
7237
  try {
6791
7238
  contents = await readFile4(contentsPath, "utf8");
@@ -6807,14 +7254,14 @@ async function workspaceProjectPaths(workspacePath2, folder) {
6807
7254
  const projects = [];
6808
7255
  for (const location of locations) {
6809
7256
  const candidate = resolve5(dirname6(workspacePath2), location);
6810
- const difference = relative4(folder, candidate);
6811
- if (difference === ".." || difference.startsWith(`..${sep3}`) || isAbsolute4(difference)) {
7257
+ const difference = relative5(folder, candidate);
7258
+ if (difference === ".." || difference.startsWith(`..${sep4}`) || isAbsolute4(difference)) {
6812
7259
  continue;
6813
7260
  }
6814
7261
  try {
6815
7262
  const resolved = await realpath5(candidate);
6816
- const resolvedDifference = relative4(folder, resolved);
6817
- if (resolvedDifference === ".." || resolvedDifference.startsWith(`..${sep3}`) || isAbsolute4(resolvedDifference) || !(await stat5(resolved)).isDirectory()) {
7263
+ const resolvedDifference = relative5(folder, resolved);
7264
+ if (resolvedDifference === ".." || resolvedDifference.startsWith(`..${sep4}`) || isAbsolute4(resolvedDifference) || !(await stat6(resolved)).isDirectory()) {
6818
7265
  continue;
6819
7266
  }
6820
7267
  projects.push(resolved);
@@ -7029,8 +7476,8 @@ function genericBuildDestinations(action) {
7029
7476
  ];
7030
7477
  }
7031
7478
  async function listPhysicalDevices(timeoutMs, signal) {
7032
- const directory = await mkdtemp2(join6(tmpdir2(), "ade-devices-"));
7033
- const output = join6(directory, "devices.json");
7479
+ const directory = await mkdtemp2(join7(tmpdir2(), "ade-devices-"));
7480
+ const output = join7(directory, "devices.json");
7034
7481
  try {
7035
7482
  const result = await command2(
7036
7483
  "xcrun",
@@ -7428,7 +7875,7 @@ function actionArgument(action) {
7428
7875
  }[action];
7429
7876
  }
7430
7877
  function xcodeBuildArguments(input) {
7431
- const resultBundle = join6(input.artifactDirectory, "result.xcresult");
7878
+ const resultBundle = join7(input.artifactDirectory, "result.xcresult");
7432
7879
  const sourcePath = containedPath2(input.folder, input.source.relativePath);
7433
7880
  const usesCapturedTestProducts = input.action === "TEST_WITHOUT_BUILDING";
7434
7881
  const args = [
@@ -7450,13 +7897,13 @@ function xcodeBuildArguments(input) {
7450
7897
  if (input.action === "ARCHIVE") {
7451
7898
  args.push(
7452
7899
  "-archivePath",
7453
- join6(input.artifactDirectory, "archive.xcarchive")
7900
+ join7(input.artifactDirectory, "archive.xcarchive")
7454
7901
  );
7455
7902
  }
7456
7903
  if (input.action === "BUILD_FOR_TESTING") {
7457
7904
  args.push(
7458
7905
  "-testProductsPath",
7459
- join6(input.artifactDirectory, "test-products.xctestproducts")
7906
+ join7(input.artifactDirectory, "test-products.xctestproducts")
7460
7907
  );
7461
7908
  }
7462
7909
  if (input.action === "TEST_WITHOUT_BUILDING" && input.advancedSettings.priorTestProductsPath) {
@@ -7519,15 +7966,15 @@ function classifyFailure(output) {
7519
7966
  }
7520
7967
  async function runHook(options) {
7521
7968
  const phaseDirectory = options.phase === "PRE_BUILD" ? "pre" : "post";
7522
- const directory = join6(
7969
+ const directory = join7(
7523
7970
  options.input.artifactDirectory,
7524
7971
  "hooks",
7525
7972
  phaseDirectory
7526
7973
  );
7527
7974
  const prefix = `${String(options.script.position).padStart(3, "0")}-${options.script.id}`;
7528
- const file = join6(directory, `${prefix}.mjs`);
7529
- const runner = join6(directory, `${prefix}.runner.mjs`);
7530
- const log = join6(directory, `${prefix}.log`);
7975
+ const file = join7(directory, `${prefix}.mjs`);
7976
+ const runner = join7(directory, `${prefix}.runner.mjs`);
7977
+ const log = join7(directory, `${prefix}.log`);
7531
7978
  const started = Date.now();
7532
7979
  try {
7533
7980
  await mkdir3(directory, { recursive: true, mode: 448 });
@@ -7542,7 +7989,7 @@ async function runHook(options) {
7542
7989
  await writeFile3(
7543
7990
  runner,
7544
7991
  `import { readFile } from "node:fs/promises";
7545
- const hookModule = await import(${JSON.stringify(`./${basename3(file)}`)});
7992
+ const hookModule = await import(${JSON.stringify(`./${basename4(file)}`)});
7546
7993
  if (hookModule.default !== undefined && typeof hookModule.default !== "function") {
7547
7994
  throw new TypeError("The default build hook export must be a function");
7548
7995
  }
@@ -7579,7 +8026,7 @@ if (typeof hookModule.default === "function") {
7579
8026
  exitCode: result.exitCode,
7580
8027
  durationMs: Date.now() - started,
7581
8028
  causedBuildFailure: failed && options.script.failureBehavior === "FAIL_BUILD",
7582
- outputRelativePath: relative4(options.input.artifactDirectory, log),
8029
+ outputRelativePath: relative5(options.input.artifactDirectory, log),
7583
8030
  error: failed ? cleanError3(result.output || "Build hook failed") : null
7584
8031
  };
7585
8032
  } catch (hookError) {
@@ -7592,44 +8039,75 @@ if (typeof hookModule.default === "function") {
7592
8039
  exitCode: null,
7593
8040
  durationMs: Date.now() - started,
7594
8041
  causedBuildFailure: !options.signal.aborted && options.script.failureBehavior === "FAIL_BUILD",
7595
- outputRelativePath: relative4(options.input.artifactDirectory, log),
8042
+ outputRelativePath: relative5(options.input.artifactDirectory, log),
7596
8043
  error
7597
8044
  };
7598
8045
  }
7599
8046
  }
7600
8047
  async function pathExists(path) {
7601
8048
  try {
7602
- await stat5(path);
8049
+ await stat6(path);
7603
8050
  return true;
7604
8051
  } catch {
7605
8052
  return false;
7606
8053
  }
7607
8054
  }
7608
8055
  async function pathSize(path) {
7609
- const information = await stat5(path);
8056
+ const information = await stat6(path);
7610
8057
  if (information.isFile()) return information.size;
7611
8058
  if (!information.isDirectory()) return 0;
7612
8059
  let size = 0;
7613
- for (const entry of await readdir4(path, { withFileTypes: true })) {
8060
+ for (const entry of await readdir5(path, { withFileTypes: true })) {
7614
8061
  if (entry.isSymbolicLink()) continue;
7615
- size += await pathSize(join6(path, entry.name));
8062
+ size += await pathSize(join7(path, entry.name));
7616
8063
  }
7617
8064
  return size;
7618
8065
  }
7619
8066
  async function fileChecksum(path) {
7620
8067
  try {
7621
- const information = await stat5(path);
7622
- if (!information.isFile() || information.size > 256 * 1024 * 1024)
7623
- return null;
7624
- return createHash3("sha256").update(await readFile4(path)).digest("hex");
8068
+ const information = await stat6(path);
8069
+ if (!information.isFile()) return null;
8070
+ const hash = createHash4("sha256");
8071
+ await pipeline(createReadStream3(path), hash);
8072
+ return hash.digest("hex");
8073
+ } catch {
8074
+ return null;
8075
+ }
8076
+ }
8077
+ async function plistString(plistPath, keyPath, timeoutMs, signal) {
8078
+ try {
8079
+ const result = await command2(
8080
+ "/usr/bin/plutil",
8081
+ ["-extract", keyPath, "raw", "-o", "-", plistPath],
8082
+ Math.min(timeoutMs, 5e3),
8083
+ signal
8084
+ );
8085
+ const value = result.stdout.trim();
8086
+ return result.exitCode === 0 && value ? value : null;
7625
8087
  } catch {
7626
8088
  return null;
7627
8089
  }
7628
8090
  }
8091
+ async function archiveApplicationProperties(archivePath, timeoutMs, signal) {
8092
+ const plistPath = join7(archivePath, "Info.plist");
8093
+ const read = (key) => plistString(plistPath, `ApplicationProperties.${key}`, timeoutMs, signal);
8094
+ const [bundleIdentifier2, bundleShortVersion, bundleVersion, applicationPath] = await Promise.all([
8095
+ read("CFBundleIdentifier"),
8096
+ read("CFBundleShortVersionString"),
8097
+ read("CFBundleVersion"),
8098
+ read("ApplicationPath")
8099
+ ]);
8100
+ return {
8101
+ bundleIdentifier: bundleIdentifier2,
8102
+ bundleShortVersion,
8103
+ bundleVersion,
8104
+ applicationName: applicationPath ? basename4(applicationPath).replace(/\.app$/, "") : null
8105
+ };
8106
+ }
7629
8107
  async function artifact(root, kind, path, metadata = {}) {
7630
8108
  return {
7631
8109
  kind,
7632
- relativePath: relative4(root, path).split(sep3).join("/"),
8110
+ relativePath: relative5(root, path).split(sep4).join("/"),
7633
8111
  sizeBytes: await pathSize(path),
7634
8112
  checksum: await fileChecksum(path),
7635
8113
  metadata
@@ -7657,7 +8135,7 @@ function testSourceFile(node, suite) {
7657
8135
  node.sourceFile
7658
8136
  ].find((value) => typeof value === "string" && !!value);
7659
8137
  if (explicit) {
7660
- return { file: basename3(explicit), filePath: explicit };
8138
+ return { file: basename4(explicit), filePath: explicit };
7661
8139
  }
7662
8140
  const identifier = typeof node.nodeIdentifier === "string" ? node.nodeIdentifier : "";
7663
8141
  const identifierOwner = identifier.split("/")[0]?.trim();
@@ -7770,9 +8248,9 @@ function normalizeCoverage(value) {
7770
8248
  };
7771
8249
  }
7772
8250
  async function writeJsonReport(input, kind, timeoutMs, signal) {
7773
- const resultBundle = join6(input.artifactDirectory, "result.xcresult");
8251
+ const resultBundle = join7(input.artifactDirectory, "result.xcresult");
7774
8252
  const filename = kind === "TEST_RESULTS" ? "test-results.json" : "code-coverage.json";
7775
- const destination = join6(input.artifactDirectory, filename);
8253
+ const destination = join7(input.artifactDirectory, filename);
7776
8254
  const temporary = `${destination}.tmp-${randomUUID2()}`;
7777
8255
  try {
7778
8256
  if (!await pathExists(resultBundle)) {
@@ -7907,7 +8385,7 @@ async function snapshotCoverageChanges(input, folder, timeoutMs, signal) {
7907
8385
  const types = changeTypesFromStatus(status2.stdout);
7908
8386
  for (const path of untracked.stdout.split("\0").filter(Boolean)) {
7909
8387
  try {
7910
- const contents = await readFile4(join6(folder, path));
8388
+ const contents = await readFile4(join7(folder, path));
7911
8389
  if (contents.includes(0)) continue;
7912
8390
  const lineCount = contents.length ? contents.toString("utf8").split("\n").length - (contents.toString("utf8").endsWith("\n") ? 1 : 0) : 0;
7913
8391
  lines.set(
@@ -7936,12 +8414,12 @@ async function addChangedCoverage(report, changes, input, folder, timeoutMs, sig
7936
8414
  let changedExecutableLines = 0;
7937
8415
  for (const change of changes) {
7938
8416
  const coveragePath = coveragePaths.find(
7939
- (candidate) => relative4(folder, candidate).split(sep3).join("/") === change.path
8417
+ (candidate) => relative5(folder, candidate).split(sep4).join("/") === change.path
7940
8418
  );
7941
8419
  let covered = 0;
7942
8420
  let executable = 0;
7943
8421
  if (coveragePath && change.lines.length) {
7944
- const temporary2 = join6(
8422
+ const temporary2 = join7(
7945
8423
  input.artifactDirectory,
7946
8424
  `.changed-coverage-${randomUUID2()}.json`
7947
8425
  );
@@ -8002,7 +8480,7 @@ async function addChangedCoverage(report, changes, input, folder, timeoutMs, sig
8002
8480
  changedLineCoverage: changedExecutableLines ? changedCoveredLines / changedExecutableLines : null
8003
8481
  };
8004
8482
  const data = { ...report.data, changedFiles };
8005
- const destination = join6(input.artifactDirectory, "worktree-coverage.json");
8483
+ const destination = join7(input.artifactDirectory, "worktree-coverage.json");
8006
8484
  const temporary = `${destination}.tmp-${randomUUID2()}`;
8007
8485
  await writeFile3(temporary, JSON.stringify({ summary, data }, null, 2), {
8008
8486
  mode: 384
@@ -8050,13 +8528,13 @@ async function findFiles(root, predicate, limit = 20) {
8050
8528
  const current = queue.shift();
8051
8529
  let entries;
8052
8530
  try {
8053
- entries = await readdir4(current, { withFileTypes: true });
8531
+ entries = await readdir5(current, { withFileTypes: true });
8054
8532
  } catch {
8055
8533
  continue;
8056
8534
  }
8057
8535
  for (const entry of entries) {
8058
8536
  if (entry.isSymbolicLink()) continue;
8059
- const path = join6(current, entry.name);
8537
+ const path = join7(current, entry.name);
8060
8538
  if (entry.isDirectory()) queue.push(path);
8061
8539
  else if (entry.isFile() && predicate(entry.name)) results.push(path);
8062
8540
  if (results.length >= limit) break;
@@ -8066,7 +8544,7 @@ async function findFiles(root, predicate, limit = 20) {
8066
8544
  }
8067
8545
  async function captureArtifacts(input, folder, signal, includeProducts = true) {
8068
8546
  const artifacts = [];
8069
- const resultBundle = join6(input.artifactDirectory, "result.xcresult");
8547
+ const resultBundle = join7(input.artifactDirectory, "result.xcresult");
8070
8548
  if (await pathExists(resultBundle)) {
8071
8549
  const coverageProbe = await command2(
8072
8550
  "xcrun",
@@ -8093,7 +8571,7 @@ async function captureArtifacts(input, folder, signal, includeProducts = true) {
8093
8571
  );
8094
8572
  }
8095
8573
  if (!includeProducts) return artifacts;
8096
- const archivePath = join6(input.artifactDirectory, "archive.xcarchive");
8574
+ const archivePath = join7(input.artifactDirectory, "archive.xcarchive");
8097
8575
  if (await pathExists(archivePath)) {
8098
8576
  artifacts.push(
8099
8577
  await artifact(input.artifactDirectory, "ARCHIVE", archivePath)
@@ -8116,10 +8594,10 @@ async function captureArtifacts(input, folder, signal, includeProducts = true) {
8116
8594
  if (apps.length === 1) {
8117
8595
  const settings = apps[0].buildSettings;
8118
8596
  const productName = settings.FULL_PRODUCT_NAME ?? settings.WRAPPER_NAME;
8119
- const source = settings.TARGET_BUILD_DIR && productName ? join6(settings.TARGET_BUILD_DIR, productName) : null;
8597
+ const source = settings.TARGET_BUILD_DIR && productName ? join7(settings.TARGET_BUILD_DIR, productName) : null;
8120
8598
  if (source && await pathExists(source)) {
8121
- const productDirectory = join6(input.artifactDirectory, "products");
8122
- const destination = join6(productDirectory, basename3(source));
8599
+ const productDirectory = join7(input.artifactDirectory, "products");
8600
+ const destination = join7(productDirectory, basename4(source));
8123
8601
  await mkdir3(productDirectory, { recursive: true, mode: 448 });
8124
8602
  await rm4(destination, { recursive: true, force: true });
8125
8603
  await cp(source, destination, {
@@ -8136,7 +8614,7 @@ async function captureArtifacts(input, folder, signal, includeProducts = true) {
8136
8614
  }
8137
8615
  }
8138
8616
  if (input.action === "BUILD_FOR_TESTING") {
8139
- const testDirectory = join6(
8617
+ const testDirectory = join7(
8140
8618
  input.artifactDirectory,
8141
8619
  "test-products.xctestproducts"
8142
8620
  );
@@ -8151,7 +8629,7 @@ async function captureArtifacts(input, folder, signal, includeProducts = true) {
8151
8629
  )) {
8152
8630
  artifacts.push(
8153
8631
  await artifact(input.artifactDirectory, "XCTESTRUN", file, {
8154
- testPlan: basename3(file, ".xctestrun")
8632
+ testPlan: basename4(file, ".xctestrun")
8155
8633
  })
8156
8634
  );
8157
8635
  }
@@ -8171,14 +8649,14 @@ var runIosBuild = async (payload, timeoutMs, signal, onLog, context) => {
8171
8649
  const testProductsPath = input.advancedSettings.priorTestProductsPath;
8172
8650
  const xctestrunPath = input.advancedSettings.priorXctestrunPath;
8173
8651
  if (testProductsPath) {
8174
- if (!testProductsPath.endsWith(".xctestproducts") || !await pathExists(testProductsPath) || !(await stat5(testProductsPath)).isDirectory()) {
8652
+ if (!testProductsPath.endsWith(".xctestproducts") || !await pathExists(testProductsPath) || !(await stat6(testProductsPath)).isDirectory()) {
8175
8653
  throw new Error("The captured test-products artifact is unavailable");
8176
8654
  }
8177
- } else if (!xctestrunPath?.endsWith(".xctestrun") || !await pathExists(xctestrunPath) || !(await stat5(xctestrunPath)).isFile()) {
8655
+ } else if (!xctestrunPath?.endsWith(".xctestrun") || !await pathExists(xctestrunPath) || !(await stat6(xctestrunPath)).isFile()) {
8178
8656
  throw new Error("The captured .xctestrun artifact is unavailable");
8179
8657
  }
8180
8658
  }
8181
- if (!isAbsolute4(input.artifactDirectory) || basename3(input.artifactDirectory) !== input.buildId) {
8659
+ if (!isAbsolute4(input.artifactDirectory) || basename4(input.artifactDirectory) !== input.buildId) {
8182
8660
  throw new Error("Build artifact directory is invalid");
8183
8661
  }
8184
8662
  const sourceStateHash = await worktreeCodeStateHash(
@@ -8187,11 +8665,11 @@ var runIosBuild = async (payload, timeoutMs, signal, onLog, context) => {
8187
8665
  signal
8188
8666
  );
8189
8667
  await mkdir3(input.artifactDirectory, { recursive: true, mode: 448 });
8190
- const rawLog = join6(input.artifactDirectory, "build.log");
8668
+ const rawLog = join7(input.artifactDirectory, "build.log");
8191
8669
  const logger = new BuildLogger(input.buildId, context, rawLog, process.env);
8192
8670
  try {
8193
8671
  const scriptExecutions = [];
8194
- const contextPath = join6(input.artifactDirectory, "context.json");
8672
+ const contextPath = join7(input.artifactDirectory, "context.json");
8195
8673
  const baseHookContext = {
8196
8674
  buildId: input.buildId,
8197
8675
  branch: input.branch ?? null,
@@ -8424,7 +8902,7 @@ var runIosBuild = async (payload, timeoutMs, signal, onLog, context) => {
8424
8902
  };
8425
8903
  var generateIosBuildReport = async (payload, timeoutMs, signal, onLog) => {
8426
8904
  const input = parseBuildReportPayload(payload);
8427
- if (!isAbsolute4(input.artifactDirectory) || basename3(input.artifactDirectory) !== input.buildId) {
8905
+ if (!isAbsolute4(input.artifactDirectory) || basename4(input.artifactDirectory) !== input.buildId) {
8428
8906
  throw new Error("Build artifact directory is invalid");
8429
8907
  }
8430
8908
  if (signal.aborted) return { ...successfulProcess5, cancelled: true };
@@ -8467,25 +8945,25 @@ var downloadIosBuildArtifact = async (payload, timeoutMs, signal, onLog, context
8467
8945
  const target2 = await realpath5(
8468
8946
  containedPath2(root, input.artifactRelativePath)
8469
8947
  );
8470
- const difference = relative4(root, target2);
8471
- if (!difference || difference === ".." || difference.startsWith(`..${sep3}`) || isAbsolute4(difference)) {
8948
+ const difference = relative5(root, target2);
8949
+ if (!difference || difference === ".." || difference.startsWith(`..${sep4}`) || isAbsolute4(difference)) {
8472
8950
  throw new Error("Build artifact resolves outside the build folder");
8473
8951
  }
8474
- const information = await stat5(target2);
8952
+ const information = await stat6(target2);
8475
8953
  let uploadPath = target2;
8476
- let filename = basename3(target2);
8954
+ let filename = basename4(target2);
8477
8955
  let contentType = "application/octet-stream";
8478
8956
  let temporaryArchive = null;
8479
8957
  try {
8480
8958
  if (information.isDirectory()) {
8481
- temporaryArchive = join6(
8959
+ temporaryArchive = join7(
8482
8960
  tmpdir2(),
8483
8961
  `ade-build-artifact-${randomUUID2()}.tar.gz`
8484
8962
  );
8485
8963
  requireSuccess3(
8486
8964
  await command2(
8487
8965
  "tar",
8488
- ["-czf", temporaryArchive, "-C", dirname6(target2), basename3(target2)],
8966
+ ["-czf", temporaryArchive, "-C", dirname6(target2), basename4(target2)],
8489
8967
  timeoutMs,
8490
8968
  signal
8491
8969
  ),
@@ -8548,24 +9026,24 @@ var deployIosBuild = async (payload, timeoutMs, signal, _onLog, context) => {
8548
9026
  if (!await pathExists(appPath) || !appPath.endsWith(".app")) {
8549
9027
  throw new Error("Runnable app artifact is missing");
8550
9028
  }
8551
- const deploymentsDirectory = join6(input.artifactDirectory, "deployments");
9029
+ const deploymentsDirectory = join7(input.artifactDirectory, "deployments");
8552
9030
  await mkdir3(deploymentsDirectory, { recursive: true, mode: 448 });
8553
9031
  const logger = new BuildLogger(
8554
9032
  input.buildId,
8555
9033
  context,
8556
- join6(deploymentsDirectory, "deployments.log"),
9034
+ join7(deploymentsDirectory, "deployments.log"),
8557
9035
  process.env
8558
9036
  );
8559
9037
  try {
8560
9038
  const outcomes = [];
8561
9039
  for (const deployment of input.deployments) {
8562
- const directory = join6(
9040
+ const directory = join7(
8563
9041
  input.artifactDirectory,
8564
9042
  "deployments",
8565
9043
  deployment.id
8566
9044
  );
8567
9045
  await mkdir3(directory, { recursive: true, mode: 448 });
8568
- const logPath = join6(directory, "deployment.log");
9046
+ const logPath = join7(directory, "deployment.log");
8569
9047
  const started = Date.now();
8570
9048
  let failure = null;
8571
9049
  try {
@@ -8719,7 +9197,7 @@ var deployIosBuild = async (payload, timeoutMs, signal, _onLog, context) => {
8719
9197
  status: signal.aborted ? "CANCELLED" : failure ? "FAILED" : "SUCCEEDED",
8720
9198
  error: failure,
8721
9199
  durationMs: Date.now() - started,
8722
- outputRelativePath: relative4(input.artifactDirectory, logPath)
9200
+ outputRelativePath: relative5(input.artifactDirectory, logPath)
8723
9201
  });
8724
9202
  if (signal.aborted) break;
8725
9203
  }
@@ -8735,17 +9213,6 @@ var deployIosBuild = async (payload, timeoutMs, signal, _onLog, context) => {
8735
9213
  await logger.close();
8736
9214
  }
8737
9215
  };
8738
- function xml(value) {
8739
- return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
8740
- }
8741
- function plistValue(value) {
8742
- if (typeof value === "boolean") return value ? "<true/>" : "<false/>";
8743
- if (typeof value === "string") return `<string>${xml(value)}</string>`;
8744
- if (value && typeof value === "object" && !Array.isArray(value)) {
8745
- return `<dict>${Object.entries(value).map(([key, entry]) => `<key>${xml(key)}</key>${plistValue(entry)}`).join("")}</dict>`;
8746
- }
8747
- throw new Error("Unsupported export plist value");
8748
- }
8749
9216
  function exportPlist(settings) {
8750
9217
  const method = {
8751
9218
  DEBUGGING: "debugging",
@@ -8767,10 +9234,7 @@ function exportPlist(settings) {
8767
9234
  if (Object.keys(settings.provisioningProfiles).length) {
8768
9235
  values.provisioningProfiles = settings.provisioningProfiles;
8769
9236
  }
8770
- return `<?xml version="1.0" encoding="UTF-8"?>
8771
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
8772
- <plist version="1.0">${plistValue(values)}</plist>
8773
- `;
9237
+ return plistDocument(values);
8774
9238
  }
8775
9239
  var exportIosArchive = async (payload, timeoutMs, signal, _onLog, context) => {
8776
9240
  const input = parseBuildExportPayload(payload);
@@ -8786,14 +9250,14 @@ var exportIosArchive = async (payload, timeoutMs, signal, _onLog, context) => {
8786
9250
  if (!await pathExists(archivePath) || !archivePath.endsWith(".xcarchive")) {
8787
9251
  throw new Error("Archive artifact is missing");
8788
9252
  }
8789
- const exportDirectory = join6(
9253
+ const exportDirectory = join7(
8790
9254
  input.artifactDirectory,
8791
9255
  "exports",
8792
9256
  input.exportId
8793
9257
  );
8794
9258
  await mkdir3(exportDirectory, { recursive: true, mode: 448 });
8795
- const plistPath = join6(exportDirectory, "ExportOptions.plist");
8796
- const logPath = join6(exportDirectory, "export.log");
9259
+ const plistPath = join7(exportDirectory, "ExportOptions.plist");
9260
+ const logPath = join7(exportDirectory, "export.log");
8797
9261
  await writeFile3(plistPath, exportPlist(input.settings), { mode: 384 });
8798
9262
  const logger = new BuildLogger(input.buildId, context, logPath, process.env);
8799
9263
  try {
@@ -8825,14 +9289,30 @@ var exportIosArchive = async (payload, timeoutMs, signal, _onLog, context) => {
8825
9289
  signal: result.signal,
8826
9290
  timedOut: result.timedOut,
8827
9291
  cancelled: result.cancelled,
8828
- outputRelativePath: relative4(input.artifactDirectory, exportDirectory),
9292
+ outputRelativePath: relative5(input.artifactDirectory, exportDirectory),
8829
9293
  sizeBytes: result.exitCode === 0 ? await pathSize(exportDirectory) : null,
8830
- error: result.exitCode === 0 ? null : cleanError3(result.output)
9294
+ error: result.exitCode === 0 ? null : cleanError3(result.output),
9295
+ artifacts: result.exitCode === 0 ? await exportedArtifacts(input, exportDirectory, archivePath, signal) : []
8831
9296
  };
8832
9297
  } finally {
8833
9298
  await logger.close();
8834
9299
  }
8835
9300
  };
9301
+ async function exportedArtifacts(input, exportDirectory, archivePath, signal) {
9302
+ const [ipaPath] = await findFiles(
9303
+ exportDirectory,
9304
+ (name) => name.endsWith(".ipa"),
9305
+ 5
9306
+ );
9307
+ if (!ipaPath) return [];
9308
+ return [
9309
+ await artifact(input.artifactDirectory, "IPA", ipaPath, {
9310
+ exportId: input.exportId,
9311
+ exportMethod: input.settings.method,
9312
+ ...await archiveApplicationProperties(archivePath, 1e4, signal)
9313
+ })
9314
+ ];
9315
+ }
8836
9316
 
8837
9317
  // src/handlers/index.ts
8838
9318
  var handlers = {
@@ -8869,7 +9349,8 @@ var handlers = {
8869
9349
  [IOS_DEPLOY_JOB_KIND]: deployIosBuild,
8870
9350
  [IOS_EXPORT_JOB_KIND]: exportIosArchive,
8871
9351
  [IOS_TEST_RESULTS_JOB_KIND]: generateIosBuildReport,
8872
- [IOS_COVERAGE_REPORT_JOB_KIND]: generateIosBuildReport
9352
+ [IOS_COVERAGE_REPORT_JOB_KIND]: generateIosBuildReport,
9353
+ [IOS_SIGNING_INSPECT_JOB_KIND]: inspectIosSigning
8873
9354
  };
8874
9355
 
8875
9356
  // src/repository-coordinator.ts