@ai-development-environment/control-agent 0.0.96 → 0.0.98
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/dist/control-agent.js +1089 -120
- package/package.json +1 -1
package/dist/control-agent.js
CHANGED
|
@@ -3225,7 +3225,7 @@ var require_stream = __commonJS({
|
|
|
3225
3225
|
};
|
|
3226
3226
|
duplex._final = function(callback) {
|
|
3227
3227
|
if (ws.readyState === ws.CONNECTING) {
|
|
3228
|
-
ws.once("open", function
|
|
3228
|
+
ws.once("open", function open6() {
|
|
3229
3229
|
duplex._final(callback);
|
|
3230
3230
|
});
|
|
3231
3231
|
return;
|
|
@@ -3246,7 +3246,7 @@ var require_stream = __commonJS({
|
|
|
3246
3246
|
};
|
|
3247
3247
|
duplex._write = function(chunk, encoding, callback) {
|
|
3248
3248
|
if (ws.readyState === ws.CONNECTING) {
|
|
3249
|
-
ws.once("open", function
|
|
3249
|
+
ws.once("open", function open6() {
|
|
3250
3250
|
duplex._write(chunk, encoding, callback);
|
|
3251
3251
|
});
|
|
3252
3252
|
return;
|
|
@@ -4280,7 +4280,7 @@ async function saveConfig(config, path = configPath()) {
|
|
|
4280
4280
|
|
|
4281
4281
|
// src/graphql-client.ts
|
|
4282
4282
|
import { createReadStream } from "node:fs";
|
|
4283
|
-
import { rm, stat } from "node:fs/promises";
|
|
4283
|
+
import { open, rm, stat } from "node:fs/promises";
|
|
4284
4284
|
import { createHash } from "node:crypto";
|
|
4285
4285
|
import { Readable } from "node:stream";
|
|
4286
4286
|
|
|
@@ -4988,6 +4988,39 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
|
4988
4988
|
var wrapper_default = import_websocket.default;
|
|
4989
4989
|
|
|
4990
4990
|
// src/graphql-client.ts
|
|
4991
|
+
async function writeArtifactTransferBytes(writer, bytes, position) {
|
|
4992
|
+
let written = 0;
|
|
4993
|
+
while (written < bytes.byteLength) {
|
|
4994
|
+
const remaining = bytes.byteLength - written;
|
|
4995
|
+
const { bytesWritten } = await writer.write(
|
|
4996
|
+
bytes,
|
|
4997
|
+
written,
|
|
4998
|
+
remaining,
|
|
4999
|
+
position + written
|
|
5000
|
+
);
|
|
5001
|
+
if (!Number.isSafeInteger(bytesWritten) || bytesWritten <= 0 || bytesWritten > remaining) {
|
|
5002
|
+
throw new Error("Artifact transfer file write made no progress");
|
|
5003
|
+
}
|
|
5004
|
+
written += bytesWritten;
|
|
5005
|
+
}
|
|
5006
|
+
}
|
|
5007
|
+
async function artifactTransferRetryDelay(attempt, signal) {
|
|
5008
|
+
await new Promise((resolve12, reject) => {
|
|
5009
|
+
const timeout = setTimeout(
|
|
5010
|
+
() => {
|
|
5011
|
+
signal.removeEventListener("abort", abort);
|
|
5012
|
+
resolve12();
|
|
5013
|
+
},
|
|
5014
|
+
Math.min(4e3, 250 * 2 ** attempt)
|
|
5015
|
+
);
|
|
5016
|
+
const abort = () => {
|
|
5017
|
+
clearTimeout(timeout);
|
|
5018
|
+
reject(signal.reason ?? new Error("Artifact transfer was cancelled"));
|
|
5019
|
+
};
|
|
5020
|
+
if (signal.aborted) abort();
|
|
5021
|
+
else signal.addEventListener("abort", abort, { once: true });
|
|
5022
|
+
});
|
|
5023
|
+
}
|
|
4991
5024
|
var RUN_COMMAND_FIELDS = `
|
|
4992
5025
|
id runId agentId sequence type payload status error
|
|
4993
5026
|
run {
|
|
@@ -5257,6 +5290,205 @@ var AgentGraphQLClient = class {
|
|
|
5257
5290
|
);
|
|
5258
5291
|
}
|
|
5259
5292
|
}
|
|
5293
|
+
artifactTransferHeaders(values = {}) {
|
|
5294
|
+
return mergeHeaders(this.headers, {
|
|
5295
|
+
...this.credential ? { authorization: `Bearer ${this.credential}` } : {},
|
|
5296
|
+
...values
|
|
5297
|
+
});
|
|
5298
|
+
}
|
|
5299
|
+
async uploadBuildArtifactTransfer(input) {
|
|
5300
|
+
const information = await stat(input.path);
|
|
5301
|
+
const base = `${this.server}/api/agent/build-artifact-transfers/${encodeURIComponent(input.transferId)}`;
|
|
5302
|
+
const metadata = await fetch(base, {
|
|
5303
|
+
method: "POST",
|
|
5304
|
+
headers: this.artifactTransferHeaders({
|
|
5305
|
+
"content-type": "application/json"
|
|
5306
|
+
}),
|
|
5307
|
+
body: JSON.stringify({
|
|
5308
|
+
uploadLength: information.size,
|
|
5309
|
+
checksum: input.checksum,
|
|
5310
|
+
filename: input.filename,
|
|
5311
|
+
contentType: input.contentType
|
|
5312
|
+
}),
|
|
5313
|
+
signal: input.signal
|
|
5314
|
+
});
|
|
5315
|
+
if (!metadata.ok) {
|
|
5316
|
+
throw new Error(
|
|
5317
|
+
`Artifact transfer initialization failed: HTTP ${metadata.status} ${await metadata.text()}`
|
|
5318
|
+
);
|
|
5319
|
+
}
|
|
5320
|
+
const confirmedOffset = async () => {
|
|
5321
|
+
const response = await fetch(`${base}/upload`, {
|
|
5322
|
+
method: "HEAD",
|
|
5323
|
+
headers: this.artifactTransferHeaders(),
|
|
5324
|
+
signal: input.signal
|
|
5325
|
+
});
|
|
5326
|
+
if (!response.ok) {
|
|
5327
|
+
throw new Error(
|
|
5328
|
+
`Could not inspect artifact upload offset: HTTP ${response.status}`
|
|
5329
|
+
);
|
|
5330
|
+
}
|
|
5331
|
+
const offset = Number(response.headers.get("upload-offset"));
|
|
5332
|
+
if (!Number.isSafeInteger(offset) || offset < 0 || offset > information.size) {
|
|
5333
|
+
throw new Error("Artifact relay returned an invalid upload offset");
|
|
5334
|
+
}
|
|
5335
|
+
return offset;
|
|
5336
|
+
};
|
|
5337
|
+
const handle = await open(input.path, "r");
|
|
5338
|
+
try {
|
|
5339
|
+
let offset = await confirmedOffset();
|
|
5340
|
+
const buffer = Buffer.allocUnsafe(16 * 1024 * 1024);
|
|
5341
|
+
while (offset < information.size) {
|
|
5342
|
+
if (input.signal.aborted) throw input.signal.reason;
|
|
5343
|
+
const length = Math.min(buffer.length, information.size - offset);
|
|
5344
|
+
const { bytesRead } = await handle.read(buffer, 0, length, offset);
|
|
5345
|
+
if (bytesRead !== length)
|
|
5346
|
+
throw new Error("Artifact changed while uploading");
|
|
5347
|
+
let attempt = 0;
|
|
5348
|
+
while (true) {
|
|
5349
|
+
let failure;
|
|
5350
|
+
try {
|
|
5351
|
+
const response = await fetch(`${base}/upload`, {
|
|
5352
|
+
method: "PATCH",
|
|
5353
|
+
headers: this.artifactTransferHeaders({
|
|
5354
|
+
"content-length": String(bytesRead),
|
|
5355
|
+
"content-type": "application/offset+octet-stream",
|
|
5356
|
+
"upload-offset": String(offset)
|
|
5357
|
+
}),
|
|
5358
|
+
body: buffer.subarray(0, bytesRead),
|
|
5359
|
+
signal: input.signal
|
|
5360
|
+
});
|
|
5361
|
+
if (response.ok) {
|
|
5362
|
+
const nextOffset = Number(response.headers.get("upload-offset"));
|
|
5363
|
+
if (Number.isSafeInteger(nextOffset) && nextOffset > offset && nextOffset <= information.size) {
|
|
5364
|
+
offset = nextOffset;
|
|
5365
|
+
break;
|
|
5366
|
+
}
|
|
5367
|
+
failure = new Error(
|
|
5368
|
+
"Artifact relay returned an invalid upload offset"
|
|
5369
|
+
);
|
|
5370
|
+
} else {
|
|
5371
|
+
failure = new Error(
|
|
5372
|
+
`Artifact chunk upload failed: HTTP ${response.status} ${await response.text()}`
|
|
5373
|
+
);
|
|
5374
|
+
}
|
|
5375
|
+
} catch (error) {
|
|
5376
|
+
if (input.signal.aborted) throw error;
|
|
5377
|
+
failure = error instanceof Error ? error : new Error(String(error));
|
|
5378
|
+
}
|
|
5379
|
+
if (++attempt >= 5) throw failure;
|
|
5380
|
+
await artifactTransferRetryDelay(attempt, input.signal);
|
|
5381
|
+
const resumedOffset = await confirmedOffset();
|
|
5382
|
+
if (resumedOffset !== offset) {
|
|
5383
|
+
offset = resumedOffset;
|
|
5384
|
+
break;
|
|
5385
|
+
}
|
|
5386
|
+
}
|
|
5387
|
+
}
|
|
5388
|
+
} finally {
|
|
5389
|
+
await handle.close();
|
|
5390
|
+
}
|
|
5391
|
+
const completion = await fetch(`${base}/complete`, {
|
|
5392
|
+
method: "POST",
|
|
5393
|
+
headers: this.artifactTransferHeaders(),
|
|
5394
|
+
signal: input.signal
|
|
5395
|
+
});
|
|
5396
|
+
if (!completion.ok) {
|
|
5397
|
+
throw new Error(
|
|
5398
|
+
`Artifact transfer completion failed: HTTP ${completion.status} ${await completion.text()}`
|
|
5399
|
+
);
|
|
5400
|
+
}
|
|
5401
|
+
}
|
|
5402
|
+
async downloadBuildArtifactTransfer(input) {
|
|
5403
|
+
const base = `${this.server}/api/agent/build-artifact-transfers/${encodeURIComponent(input.transferId)}`;
|
|
5404
|
+
let metadata;
|
|
5405
|
+
while (true) {
|
|
5406
|
+
const response = await fetch(base, {
|
|
5407
|
+
headers: this.artifactTransferHeaders(),
|
|
5408
|
+
signal: input.signal
|
|
5409
|
+
});
|
|
5410
|
+
if (!response.ok) {
|
|
5411
|
+
throw new Error(
|
|
5412
|
+
`Could not inspect artifact transfer: HTTP ${response.status} ${await response.text()}`
|
|
5413
|
+
);
|
|
5414
|
+
}
|
|
5415
|
+
metadata = await response.json();
|
|
5416
|
+
if (["READY", "DOWNLOADING", "SUCCEEDED"].includes(metadata.status))
|
|
5417
|
+
break;
|
|
5418
|
+
if (["FAILED", "CANCELLED", "EXPIRED"].includes(metadata.status)) {
|
|
5419
|
+
throw new Error(
|
|
5420
|
+
metadata.error || `Artifact transfer ${metadata.status.toLowerCase()}`
|
|
5421
|
+
);
|
|
5422
|
+
}
|
|
5423
|
+
await new Promise((resolve12) => setTimeout(resolve12, 1e3));
|
|
5424
|
+
}
|
|
5425
|
+
if (!Number.isSafeInteger(metadata.uploadLength) || !metadata.uploadLength || !metadata.checksum || !metadata.filename) {
|
|
5426
|
+
throw new Error("Artifact transfer metadata is incomplete");
|
|
5427
|
+
}
|
|
5428
|
+
let offset = 0;
|
|
5429
|
+
try {
|
|
5430
|
+
offset = (await stat(input.path)).size;
|
|
5431
|
+
} catch {
|
|
5432
|
+
}
|
|
5433
|
+
if (offset > metadata.uploadLength) {
|
|
5434
|
+
await rm(input.path, { force: true });
|
|
5435
|
+
offset = 0;
|
|
5436
|
+
}
|
|
5437
|
+
const handle = await open(input.path, offset === 0 ? "w" : "r+");
|
|
5438
|
+
try {
|
|
5439
|
+
while (offset < metadata.uploadLength) {
|
|
5440
|
+
const end = Math.min(
|
|
5441
|
+
metadata.uploadLength - 1,
|
|
5442
|
+
offset + 16 * 1024 * 1024 - 1
|
|
5443
|
+
);
|
|
5444
|
+
let bytes = null;
|
|
5445
|
+
let attempt = 0;
|
|
5446
|
+
while (!bytes) {
|
|
5447
|
+
let failure = new Error("Artifact range download failed");
|
|
5448
|
+
try {
|
|
5449
|
+
const response = await fetch(`${base}/download`, {
|
|
5450
|
+
headers: this.artifactTransferHeaders({
|
|
5451
|
+
range: `bytes=${offset}-${end}`
|
|
5452
|
+
}),
|
|
5453
|
+
signal: input.signal
|
|
5454
|
+
});
|
|
5455
|
+
if (response.status !== 206) {
|
|
5456
|
+
failure = new Error(
|
|
5457
|
+
`Artifact range download failed: HTTP ${response.status} ${await response.text()}`
|
|
5458
|
+
);
|
|
5459
|
+
} else {
|
|
5460
|
+
const received = Buffer.from(await response.arrayBuffer());
|
|
5461
|
+
if (received.length === end - offset + 1) bytes = received;
|
|
5462
|
+
else failure = new Error("Artifact range length did not match");
|
|
5463
|
+
}
|
|
5464
|
+
} catch (error) {
|
|
5465
|
+
if (input.signal.aborted) throw error;
|
|
5466
|
+
failure = error instanceof Error ? error : new Error(String(error));
|
|
5467
|
+
}
|
|
5468
|
+
if (bytes) break;
|
|
5469
|
+
if (++attempt >= 5) throw failure;
|
|
5470
|
+
await artifactTransferRetryDelay(attempt, input.signal);
|
|
5471
|
+
}
|
|
5472
|
+
await writeArtifactTransferBytes(handle, bytes, offset);
|
|
5473
|
+
offset += bytes.length;
|
|
5474
|
+
}
|
|
5475
|
+
await handle.sync();
|
|
5476
|
+
} finally {
|
|
5477
|
+
await handle.close();
|
|
5478
|
+
}
|
|
5479
|
+
const digest = createHash("sha256");
|
|
5480
|
+
for await (const chunk of createReadStream(input.path))
|
|
5481
|
+
digest.update(chunk);
|
|
5482
|
+
if (digest.digest("hex") !== metadata.checksum) {
|
|
5483
|
+
await rm(input.path, { force: true });
|
|
5484
|
+
throw new Error("Downloaded artifact checksum did not match");
|
|
5485
|
+
}
|
|
5486
|
+
return {
|
|
5487
|
+
filename: metadata.filename,
|
|
5488
|
+
contentType: metadata.contentType ?? "application/octet-stream",
|
|
5489
|
+
checksum: metadata.checksum
|
|
5490
|
+
};
|
|
5491
|
+
}
|
|
5260
5492
|
completeJob(jobId, status2, result, error) {
|
|
5261
5493
|
return this.request(
|
|
5262
5494
|
`mutation CompleteJob($jobId: ID!, $status: AgentJobStatus!, $result: JSON, $error: String) {
|
|
@@ -7423,6 +7655,9 @@ var IOS_BUILD_JOB_KIND = "ios.build.run";
|
|
|
7423
7655
|
var IOS_BUILD_DELETE_JOB_KIND = "ios.build.delete";
|
|
7424
7656
|
var IOS_ARTIFACT_DOWNLOAD_JOB_KIND = "ios.artifact.download";
|
|
7425
7657
|
var IOS_DEPLOY_JOB_KIND = "ios.build.deploy";
|
|
7658
|
+
var IOS_AGENT_RUN_DESTINATIONS_JOB_KIND = "ios.agentRunDestinations.inspect";
|
|
7659
|
+
var IOS_ARTIFACT_TRANSFER_UPLOAD_JOB_KIND = "ios.artifact.transfer.upload";
|
|
7660
|
+
var IOS_REMOTE_DEPLOY_JOB_KIND = "ios.build.remoteDeploy";
|
|
7426
7661
|
var IOS_EXPORT_JOB_KIND = "ios.archive.export";
|
|
7427
7662
|
var IOS_TEST_RESULTS_JOB_KIND = "ios.test-results.parse";
|
|
7428
7663
|
var IOS_COVERAGE_REPORT_JOB_KIND = "ios.coverage.generate";
|
|
@@ -7436,6 +7671,9 @@ var IOS_BUILD_JOB_KINDS = [
|
|
|
7436
7671
|
IOS_BUILD_DELETE_JOB_KIND,
|
|
7437
7672
|
IOS_ARTIFACT_DOWNLOAD_JOB_KIND,
|
|
7438
7673
|
IOS_DEPLOY_JOB_KIND,
|
|
7674
|
+
IOS_AGENT_RUN_DESTINATIONS_JOB_KIND,
|
|
7675
|
+
IOS_ARTIFACT_TRANSFER_UPLOAD_JOB_KIND,
|
|
7676
|
+
IOS_REMOTE_DEPLOY_JOB_KIND,
|
|
7439
7677
|
IOS_EXPORT_JOB_KIND,
|
|
7440
7678
|
IOS_TEST_RESULTS_JOB_KIND,
|
|
7441
7679
|
IOS_COVERAGE_REPORT_JOB_KIND,
|
|
@@ -7476,6 +7714,31 @@ var APPROVED_BUILD_SETTING_OVERRIDES = [
|
|
|
7476
7714
|
"MARKETING_VERSION",
|
|
7477
7715
|
"CURRENT_PROJECT_VERSION"
|
|
7478
7716
|
];
|
|
7717
|
+
function genericBuildDestinations(action) {
|
|
7718
|
+
if (!GENERIC_BUILD_DESTINATION_ACTIONS.includes(action)) return [];
|
|
7719
|
+
const physical = {
|
|
7720
|
+
type: "PHYSICAL_DEVICE",
|
|
7721
|
+
id: "generic-ios",
|
|
7722
|
+
name: "Any Physical iOS Device",
|
|
7723
|
+
platform: "iOS",
|
|
7724
|
+
osVersion: null,
|
|
7725
|
+
state: null,
|
|
7726
|
+
generic: true
|
|
7727
|
+
};
|
|
7728
|
+
if (action === "ARCHIVE") return [physical];
|
|
7729
|
+
return [
|
|
7730
|
+
{
|
|
7731
|
+
type: "SIMULATOR",
|
|
7732
|
+
id: "generic-ios-simulator",
|
|
7733
|
+
name: "Any iOS Simulator",
|
|
7734
|
+
platform: "iOS Simulator",
|
|
7735
|
+
osVersion: null,
|
|
7736
|
+
state: null,
|
|
7737
|
+
generic: true
|
|
7738
|
+
},
|
|
7739
|
+
physical
|
|
7740
|
+
];
|
|
7741
|
+
}
|
|
7479
7742
|
var DEFAULT_BUILD_ADVANCED_SETTINGS = {
|
|
7480
7743
|
packageResolution: "DEFAULT",
|
|
7481
7744
|
disablePackageRepositoryCache: false,
|
|
@@ -7592,6 +7855,12 @@ function parseBuildDestination(value) {
|
|
|
7592
7855
|
destination.generic,
|
|
7593
7856
|
"build destination.generic"
|
|
7594
7857
|
)
|
|
7858
|
+
},
|
|
7859
|
+
...destination.available === void 0 ? {} : {
|
|
7860
|
+
available: booleanValue2(
|
|
7861
|
+
destination.available,
|
|
7862
|
+
"build destination.available"
|
|
7863
|
+
)
|
|
7595
7864
|
}
|
|
7596
7865
|
};
|
|
7597
7866
|
}
|
|
@@ -7925,13 +8194,50 @@ function parseBuildArtifactDownloadPayload(value) {
|
|
|
7925
8194
|
)
|
|
7926
8195
|
};
|
|
7927
8196
|
}
|
|
7928
|
-
function
|
|
7929
|
-
const input = objectValue7(value, "build
|
|
8197
|
+
function parseBuildArtifactTransferUploadPayload(value) {
|
|
8198
|
+
const input = objectValue7(value, "build artifact transfer upload payload");
|
|
8199
|
+
const buildId = stringValue7(
|
|
8200
|
+
input.buildId,
|
|
8201
|
+
"build artifact transfer upload payload.buildId"
|
|
8202
|
+
);
|
|
8203
|
+
const artifactDirectory = safeAbsolutePath(
|
|
8204
|
+
input.artifactDirectory,
|
|
8205
|
+
"build artifact transfer upload payload.artifactDirectory"
|
|
8206
|
+
);
|
|
8207
|
+
if (artifactDirectory.split("/").at(-1) !== buildId) {
|
|
8208
|
+
throw new Error(
|
|
8209
|
+
"Build artifact transfer folder must end with the build ID"
|
|
8210
|
+
);
|
|
8211
|
+
}
|
|
8212
|
+
return {
|
|
8213
|
+
transferId: stringValue7(
|
|
8214
|
+
input.transferId,
|
|
8215
|
+
"build artifact transfer upload payload.transferId"
|
|
8216
|
+
),
|
|
8217
|
+
buildId,
|
|
8218
|
+
artifactDirectory,
|
|
8219
|
+
artifactRelativePath: safeRelativePath2(
|
|
8220
|
+
input.artifactRelativePath,
|
|
8221
|
+
"build artifact transfer upload payload.artifactRelativePath"
|
|
8222
|
+
)
|
|
8223
|
+
};
|
|
8224
|
+
}
|
|
8225
|
+
function parseBuildAgentRunDestinationsPayload(value) {
|
|
8226
|
+
const input = objectValue7(value, "agent run destinations payload");
|
|
8227
|
+
return {
|
|
8228
|
+
destinationType: enumValue3(
|
|
8229
|
+
input.destinationType,
|
|
8230
|
+
BUILD_DESTINATION_TYPES,
|
|
8231
|
+
"agent run destinations payload.destinationType"
|
|
8232
|
+
)
|
|
8233
|
+
};
|
|
8234
|
+
}
|
|
8235
|
+
function parseDeployments(input) {
|
|
7930
8236
|
if (!Array.isArray(input.deployments) || !input.deployments.length) {
|
|
7931
8237
|
throw new Error("build deployment payload.deployments must not be empty");
|
|
7932
8238
|
}
|
|
7933
|
-
const deployments = input.deployments.map((
|
|
7934
|
-
const deployment = objectValue7(
|
|
8239
|
+
const deployments = input.deployments.map((value, index) => {
|
|
8240
|
+
const deployment = objectValue7(value, `deployments[${index}]`);
|
|
7935
8241
|
return {
|
|
7936
8242
|
id: stringValue7(deployment.id, `deployments[${index}].id`),
|
|
7937
8243
|
destination: parseBuildDestination(deployment.destination)
|
|
@@ -7950,6 +8256,11 @@ function parseBuildDeploymentPayload(value) {
|
|
|
7950
8256
|
if (new Set(deployments.map(({ destination }) => destination.type)).size > 1) {
|
|
7951
8257
|
throw new Error("deployment destinations must use one destination type");
|
|
7952
8258
|
}
|
|
8259
|
+
return deployments;
|
|
8260
|
+
}
|
|
8261
|
+
function parseBuildDeploymentPayload(value) {
|
|
8262
|
+
const input = objectValue7(value, "build deployment payload");
|
|
8263
|
+
const deployments = parseDeployments(input);
|
|
7953
8264
|
return {
|
|
7954
8265
|
...worktreeIdentity(input),
|
|
7955
8266
|
buildId: stringValue7(input.buildId, "build deployment payload.buildId"),
|
|
@@ -7968,6 +8279,24 @@ function parseBuildDeploymentPayload(value) {
|
|
|
7968
8279
|
deployments
|
|
7969
8280
|
};
|
|
7970
8281
|
}
|
|
8282
|
+
function parseBuildRemoteDeploymentPayload(value) {
|
|
8283
|
+
const input = objectValue7(value, "remote build deployment payload");
|
|
8284
|
+
return {
|
|
8285
|
+
transferId: stringValue7(
|
|
8286
|
+
input.transferId,
|
|
8287
|
+
"remote build deployment payload.transferId"
|
|
8288
|
+
),
|
|
8289
|
+
buildId: stringValue7(
|
|
8290
|
+
input.buildId,
|
|
8291
|
+
"remote build deployment payload.buildId"
|
|
8292
|
+
),
|
|
8293
|
+
bundleIdentifier: stringValue7(
|
|
8294
|
+
input.bundleIdentifier,
|
|
8295
|
+
"remote build deployment payload.bundleIdentifier"
|
|
8296
|
+
),
|
|
8297
|
+
deployments: parseDeployments(input)
|
|
8298
|
+
};
|
|
8299
|
+
}
|
|
7971
8300
|
function parseBuildExportSettings(value) {
|
|
7972
8301
|
const input = objectValue7(value, "export settings");
|
|
7973
8302
|
const profiles = objectValue7(
|
|
@@ -8576,6 +8905,215 @@ function parseCliHealthJobPayload(value) {
|
|
|
8576
8905
|
return { checks };
|
|
8577
8906
|
}
|
|
8578
8907
|
|
|
8908
|
+
// ../agent-contract/src/tailscale.ts
|
|
8909
|
+
var TAILSCALE_SERVE_INSPECT_JOB_KIND = "tailscale.serve.inspect.v1";
|
|
8910
|
+
var TAILSCALE_SERVE_UPSERT_JOB_KIND = "tailscale.serve.upsert.v1";
|
|
8911
|
+
var TAILSCALE_SERVE_REMOVE_JOB_KIND = "tailscale.serve.remove.v1";
|
|
8912
|
+
var TAILSCALE_SERVE_JOB_KINDS = [
|
|
8913
|
+
TAILSCALE_SERVE_INSPECT_JOB_KIND,
|
|
8914
|
+
TAILSCALE_SERVE_UPSERT_JOB_KIND,
|
|
8915
|
+
TAILSCALE_SERVE_REMOVE_JOB_KIND
|
|
8916
|
+
];
|
|
8917
|
+
var TAILSCALE_SERVE_PROTOCOLS = [
|
|
8918
|
+
"HTTP",
|
|
8919
|
+
"HTTPS",
|
|
8920
|
+
"TCP",
|
|
8921
|
+
"TLS_TERMINATED_TCP"
|
|
8922
|
+
];
|
|
8923
|
+
var TAILSCALE_DESTINATION_PROTOCOLS = [
|
|
8924
|
+
"HTTP",
|
|
8925
|
+
"HTTPS",
|
|
8926
|
+
"HTTPS_INSECURE",
|
|
8927
|
+
"TCP"
|
|
8928
|
+
];
|
|
8929
|
+
var TAILSCALE_PROXY_PROTOCOLS = ["NONE", "V1", "V2"];
|
|
8930
|
+
function objectValue13(value, name) {
|
|
8931
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
8932
|
+
throw new Error(`${name} must be an object`);
|
|
8933
|
+
}
|
|
8934
|
+
return value;
|
|
8935
|
+
}
|
|
8936
|
+
function exactKeys3(value, keys, name) {
|
|
8937
|
+
const unexpected = Object.keys(value).find((key) => !keys.includes(key));
|
|
8938
|
+
if (unexpected) throw new Error(`Unexpected ${name} field: ${unexpected}`);
|
|
8939
|
+
}
|
|
8940
|
+
function requiredString(value, name) {
|
|
8941
|
+
if (typeof value !== "string" || value.trim().length === 0 || value.includes("\0")) {
|
|
8942
|
+
throw new Error(`${name} must be a non-empty string`);
|
|
8943
|
+
}
|
|
8944
|
+
return value.trim();
|
|
8945
|
+
}
|
|
8946
|
+
function integerPort(value, name) {
|
|
8947
|
+
if (!Number.isInteger(value) || value < 1 || value > 65535) {
|
|
8948
|
+
throw new Error(`${name} must be an integer from 1 through 65535`);
|
|
8949
|
+
}
|
|
8950
|
+
return value;
|
|
8951
|
+
}
|
|
8952
|
+
function enumValue4(value, values, name) {
|
|
8953
|
+
if (typeof value !== "string" || !values.includes(value)) {
|
|
8954
|
+
throw new Error(`${name} is invalid`);
|
|
8955
|
+
}
|
|
8956
|
+
return value;
|
|
8957
|
+
}
|
|
8958
|
+
function normalizeTailscaleMountPath(value) {
|
|
8959
|
+
if (typeof value !== "string" || value.includes("\0") || value.includes("?")) {
|
|
8960
|
+
throw new Error("Tailscale mount path is invalid");
|
|
8961
|
+
}
|
|
8962
|
+
const path = value.trim() || "/";
|
|
8963
|
+
const prefixed = path.startsWith("/") ? path : `/${path}`;
|
|
8964
|
+
return prefixed === "/" ? prefixed : prefixed.replace(/\/+$/, "");
|
|
8965
|
+
}
|
|
8966
|
+
function normalizeDestinationPath(value) {
|
|
8967
|
+
if (typeof value !== "string" || value.includes("\0")) {
|
|
8968
|
+
throw new Error("Tailscale destination path is invalid");
|
|
8969
|
+
}
|
|
8970
|
+
const path = value.trim();
|
|
8971
|
+
if (!path) return "";
|
|
8972
|
+
return path.startsWith("/") ? path : `/${path}`;
|
|
8973
|
+
}
|
|
8974
|
+
function appCapabilities(value) {
|
|
8975
|
+
if (!Array.isArray(value) || value.length > 100) {
|
|
8976
|
+
throw new Error("Tailscale app capabilities must be an array");
|
|
8977
|
+
}
|
|
8978
|
+
const pattern = /^(?:[\p{L}\p{N}-]+\.)+[\p{L}\p{N}-]+\/[\p{L}\p{N}-]+(?:\/[\p{L}\p{N}-]+)*$/u;
|
|
8979
|
+
return [
|
|
8980
|
+
...new Set(
|
|
8981
|
+
value.map((item, index) => {
|
|
8982
|
+
const capability = requiredString(
|
|
8983
|
+
item,
|
|
8984
|
+
`Tailscale app capabilities[${index}]`
|
|
8985
|
+
);
|
|
8986
|
+
if (!pattern.test(capability)) {
|
|
8987
|
+
throw new Error(
|
|
8988
|
+
`Tailscale app capabilities[${index}] must use domain/name format`
|
|
8989
|
+
);
|
|
8990
|
+
}
|
|
8991
|
+
return capability;
|
|
8992
|
+
})
|
|
8993
|
+
)
|
|
8994
|
+
].sort();
|
|
8995
|
+
}
|
|
8996
|
+
function parseTailscaleServeRoute(value) {
|
|
8997
|
+
const route = objectValue13(value, "Tailscale Serve route");
|
|
8998
|
+
exactKeys3(
|
|
8999
|
+
route,
|
|
9000
|
+
[
|
|
9001
|
+
"protocol",
|
|
9002
|
+
"listenPort",
|
|
9003
|
+
"mountPath",
|
|
9004
|
+
"destination",
|
|
9005
|
+
"funnel",
|
|
9006
|
+
"appCapabilities",
|
|
9007
|
+
"proxyProtocol"
|
|
9008
|
+
],
|
|
9009
|
+
"Tailscale Serve route"
|
|
9010
|
+
);
|
|
9011
|
+
const protocol = enumValue4(
|
|
9012
|
+
route.protocol,
|
|
9013
|
+
TAILSCALE_SERVE_PROTOCOLS,
|
|
9014
|
+
"Tailscale Serve protocol"
|
|
9015
|
+
);
|
|
9016
|
+
const listenPort = integerPort(route.listenPort, "Tailscale listen port");
|
|
9017
|
+
const destination = objectValue13(
|
|
9018
|
+
route.destination,
|
|
9019
|
+
"Tailscale Serve destination"
|
|
9020
|
+
);
|
|
9021
|
+
exactKeys3(
|
|
9022
|
+
destination,
|
|
9023
|
+
["protocol", "port", "path"],
|
|
9024
|
+
"Tailscale Serve destination"
|
|
9025
|
+
);
|
|
9026
|
+
const destinationProtocol = enumValue4(
|
|
9027
|
+
destination.protocol,
|
|
9028
|
+
TAILSCALE_DESTINATION_PROTOCOLS,
|
|
9029
|
+
"Tailscale destination protocol"
|
|
9030
|
+
);
|
|
9031
|
+
const funnel = route.funnel;
|
|
9032
|
+
if (typeof funnel !== "boolean")
|
|
9033
|
+
throw new Error("Tailscale Funnel must be a boolean");
|
|
9034
|
+
const proxyProtocol = enumValue4(
|
|
9035
|
+
route.proxyProtocol,
|
|
9036
|
+
TAILSCALE_PROXY_PROTOCOLS,
|
|
9037
|
+
"Tailscale PROXY protocol"
|
|
9038
|
+
);
|
|
9039
|
+
const capabilities = appCapabilities(route.appCapabilities);
|
|
9040
|
+
const isWeb = protocol === "HTTP" || protocol === "HTTPS";
|
|
9041
|
+
if (isWeb && destinationProtocol === "TCP") {
|
|
9042
|
+
throw new Error("Web listeners require an HTTP(S) destination");
|
|
9043
|
+
}
|
|
9044
|
+
if (!isWeb && destinationProtocol !== "TCP") {
|
|
9045
|
+
throw new Error("TCP listeners require a TCP destination");
|
|
9046
|
+
}
|
|
9047
|
+
if (!isWeb && normalizeTailscaleMountPath(route.mountPath) !== "/") {
|
|
9048
|
+
throw new Error("TCP listeners do not support mount paths");
|
|
9049
|
+
}
|
|
9050
|
+
if (funnel && (protocol === "HTTP" || ![443, 8443, 1e4].includes(listenPort))) {
|
|
9051
|
+
throw new Error(
|
|
9052
|
+
"Funnel requires HTTPS or TLS TCP on port 443, 8443, or 10000"
|
|
9053
|
+
);
|
|
9054
|
+
}
|
|
9055
|
+
if (capabilities.length && (!isWeb || funnel)) {
|
|
9056
|
+
throw new Error(
|
|
9057
|
+
"App capabilities are supported only by private HTTP(S) listeners"
|
|
9058
|
+
);
|
|
9059
|
+
}
|
|
9060
|
+
if (proxyProtocol !== "NONE" && protocol !== "TCP") {
|
|
9061
|
+
throw new Error("PROXY protocol is supported only by TCP listeners");
|
|
9062
|
+
}
|
|
9063
|
+
return {
|
|
9064
|
+
protocol,
|
|
9065
|
+
listenPort,
|
|
9066
|
+
mountPath: isWeb ? normalizeTailscaleMountPath(route.mountPath) : "/",
|
|
9067
|
+
destination: {
|
|
9068
|
+
protocol: destinationProtocol,
|
|
9069
|
+
port: integerPort(destination.port, "Tailscale destination port"),
|
|
9070
|
+
path: isWeb ? normalizeDestinationPath(destination.path) : ""
|
|
9071
|
+
},
|
|
9072
|
+
funnel,
|
|
9073
|
+
appCapabilities: capabilities,
|
|
9074
|
+
proxyProtocol
|
|
9075
|
+
};
|
|
9076
|
+
}
|
|
9077
|
+
function tailscaleServeInspectPayload(value) {
|
|
9078
|
+
const payload = objectValue13(value, "Tailscale Serve inspect payload");
|
|
9079
|
+
exactKeys3(payload, ["operationId"], "Tailscale Serve inspect payload");
|
|
9080
|
+
return { operationId: requiredString(payload.operationId, "operationId") };
|
|
9081
|
+
}
|
|
9082
|
+
function mutationPayload(value, allowPrevious) {
|
|
9083
|
+
const payload = objectValue13(value, "Tailscale Serve mutation payload");
|
|
9084
|
+
exactKeys3(
|
|
9085
|
+
payload,
|
|
9086
|
+
[
|
|
9087
|
+
"operationId",
|
|
9088
|
+
"templateId",
|
|
9089
|
+
"revision",
|
|
9090
|
+
"route",
|
|
9091
|
+
...allowPrevious ? ["previousRoute"] : []
|
|
9092
|
+
],
|
|
9093
|
+
"Tailscale Serve mutation payload"
|
|
9094
|
+
);
|
|
9095
|
+
if (!Number.isInteger(payload.revision) || payload.revision < 1) {
|
|
9096
|
+
throw new Error("Tailscale template revision must be a positive integer");
|
|
9097
|
+
}
|
|
9098
|
+
return {
|
|
9099
|
+
operationId: requiredString(payload.operationId, "operationId"),
|
|
9100
|
+
templateId: requiredString(payload.templateId, "templateId"),
|
|
9101
|
+
revision: payload.revision,
|
|
9102
|
+
route: parseTailscaleServeRoute(payload.route)
|
|
9103
|
+
};
|
|
9104
|
+
}
|
|
9105
|
+
function tailscaleServeUpsertPayload(value) {
|
|
9106
|
+
const base = mutationPayload(value, true);
|
|
9107
|
+
const payload = value;
|
|
9108
|
+
return {
|
|
9109
|
+
...base,
|
|
9110
|
+
previousRoute: payload.previousRoute === null ? null : parseTailscaleServeRoute(payload.previousRoute)
|
|
9111
|
+
};
|
|
9112
|
+
}
|
|
9113
|
+
function tailscaleServeRemovePayload(value) {
|
|
9114
|
+
return mutationPayload(value, false);
|
|
9115
|
+
}
|
|
9116
|
+
|
|
8579
9117
|
// src/inventory.ts
|
|
8580
9118
|
var AGENT_VERSION = process.env.CONTROL_AGENT_VERSION ?? "0.1.0";
|
|
8581
9119
|
var PORTABLE_AGENT_CAPABILITIES = [
|
|
@@ -8599,9 +9137,11 @@ var MACOS_AGENT_CAPABILITIES = [
|
|
|
8599
9137
|
...IOS_BUILD_JOB_KINDS,
|
|
8600
9138
|
...SIGNING_ASSET_JOB_KINDS
|
|
8601
9139
|
];
|
|
9140
|
+
var UNIX_AGENT_CAPABILITIES = [...TAILSCALE_SERVE_JOB_KINDS];
|
|
8602
9141
|
function capabilitiesForPlatform(targetPlatform) {
|
|
8603
9142
|
return [
|
|
8604
9143
|
...PORTABLE_AGENT_CAPABILITIES,
|
|
9144
|
+
...targetPlatform === "darwin" || targetPlatform === "linux" ? UNIX_AGENT_CAPABILITIES : [],
|
|
8605
9145
|
...targetPlatform === "darwin" ? MACOS_AGENT_CAPABILITIES : []
|
|
8606
9146
|
];
|
|
8607
9147
|
}
|
|
@@ -9031,7 +9571,7 @@ import {
|
|
|
9031
9571
|
cp,
|
|
9032
9572
|
mkdir as mkdir2,
|
|
9033
9573
|
mkdtemp as mkdtemp2,
|
|
9034
|
-
open,
|
|
9574
|
+
open as open2,
|
|
9035
9575
|
readdir,
|
|
9036
9576
|
readFile as readFile2,
|
|
9037
9577
|
realpath as realpath2,
|
|
@@ -9426,7 +9966,7 @@ function command(executable, args, timeoutMs, signal, cwd, env = process.env) {
|
|
|
9426
9966
|
});
|
|
9427
9967
|
}
|
|
9428
9968
|
async function commandToFile(executable, args, timeoutMs, signal, cwd, env, destination) {
|
|
9429
|
-
const output = await
|
|
9969
|
+
const output = await open2(destination, "wx", 384);
|
|
9430
9970
|
try {
|
|
9431
9971
|
return await captureCommand({
|
|
9432
9972
|
command: executable,
|
|
@@ -9936,7 +10476,7 @@ var parseBuildSourceMetadata = async (payload, timeoutMs, signal) => {
|
|
|
9936
10476
|
headSha: input.headSha
|
|
9937
10477
|
};
|
|
9938
10478
|
};
|
|
9939
|
-
function simulatorDestinations(value) {
|
|
10479
|
+
function simulatorDestinations(value, includeUnavailable = false) {
|
|
9940
10480
|
if (!value || typeof value !== "object" || Array.isArray(value)) return [];
|
|
9941
10481
|
const devices = value.devices;
|
|
9942
10482
|
if (!devices || typeof devices !== "object" || Array.isArray(devices))
|
|
@@ -9949,7 +10489,8 @@ function simulatorDestinations(value) {
|
|
|
9949
10489
|
for (const raw of entries) {
|
|
9950
10490
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
|
|
9951
10491
|
const device = raw;
|
|
9952
|
-
|
|
10492
|
+
const available = device.isAvailable === true;
|
|
10493
|
+
if (!available && !includeUnavailable || typeof device.udid !== "string")
|
|
9953
10494
|
continue;
|
|
9954
10495
|
result.push({
|
|
9955
10496
|
type: "SIMULATOR",
|
|
@@ -9957,7 +10498,8 @@ function simulatorDestinations(value) {
|
|
|
9957
10498
|
name: typeof device.name === "string" ? device.name : device.udid,
|
|
9958
10499
|
platform: "iOS Simulator",
|
|
9959
10500
|
osVersion,
|
|
9960
|
-
state: typeof device.state === "string" ? device.state : null
|
|
10501
|
+
state: typeof device.state === "string" ? device.state : available ? null : "Unavailable",
|
|
10502
|
+
available
|
|
9961
10503
|
});
|
|
9962
10504
|
}
|
|
9963
10505
|
}
|
|
@@ -9977,7 +10519,7 @@ function nestedString(value, paths) {
|
|
|
9977
10519
|
}
|
|
9978
10520
|
return null;
|
|
9979
10521
|
}
|
|
9980
|
-
function physicalDestinations(value) {
|
|
10522
|
+
function physicalDestinations(value, includeUnavailable = false) {
|
|
9981
10523
|
if (!value || typeof value !== "object" || Array.isArray(value)) return [];
|
|
9982
10524
|
const result = value.result;
|
|
9983
10525
|
const devices = result && typeof result === "object" && !Array.isArray(result) ? result.devices : null;
|
|
@@ -9998,7 +10540,8 @@ function physicalDestinations(value) {
|
|
|
9998
10540
|
["connectionProperties", "tunnelState"],
|
|
9999
10541
|
["deviceProperties", "connectionState"]
|
|
10000
10542
|
]);
|
|
10001
|
-
|
|
10543
|
+
const available = !(state && /(disconnected|unavailable|not.?connected|offline)/i.test(state));
|
|
10544
|
+
if (!available && !includeUnavailable) {
|
|
10002
10545
|
return [];
|
|
10003
10546
|
}
|
|
10004
10547
|
return [
|
|
@@ -10011,37 +10554,13 @@ function physicalDestinations(value) {
|
|
|
10011
10554
|
["deviceProperties", "osVersionNumber"],
|
|
10012
10555
|
["deviceProperties", "osVersion"]
|
|
10013
10556
|
]),
|
|
10014
|
-
state
|
|
10557
|
+
state,
|
|
10558
|
+
available
|
|
10015
10559
|
}
|
|
10016
10560
|
];
|
|
10017
10561
|
});
|
|
10018
10562
|
}
|
|
10019
|
-
function
|
|
10020
|
-
if (!GENERIC_BUILD_DESTINATION_ACTIONS.includes(action)) return [];
|
|
10021
|
-
const physical = {
|
|
10022
|
-
type: "PHYSICAL_DEVICE",
|
|
10023
|
-
id: "generic-ios",
|
|
10024
|
-
name: "Any Physical iOS Device",
|
|
10025
|
-
platform: "iOS",
|
|
10026
|
-
osVersion: null,
|
|
10027
|
-
state: null,
|
|
10028
|
-
generic: true
|
|
10029
|
-
};
|
|
10030
|
-
if (action === "ARCHIVE") return [physical];
|
|
10031
|
-
return [
|
|
10032
|
-
{
|
|
10033
|
-
type: "SIMULATOR",
|
|
10034
|
-
id: "generic-ios-simulator",
|
|
10035
|
-
name: "Any iOS Simulator",
|
|
10036
|
-
platform: "iOS Simulator",
|
|
10037
|
-
osVersion: null,
|
|
10038
|
-
state: null,
|
|
10039
|
-
generic: true
|
|
10040
|
-
},
|
|
10041
|
-
physical
|
|
10042
|
-
];
|
|
10043
|
-
}
|
|
10044
|
-
async function listPhysicalDevices(timeoutMs, signal) {
|
|
10563
|
+
async function listPhysicalDevices(timeoutMs, signal, includeUnavailable = false) {
|
|
10045
10564
|
const directory = await mkdtemp2(join3(tmpdir2(), "ade-devices-"));
|
|
10046
10565
|
const output = join3(directory, "devices.json");
|
|
10047
10566
|
try {
|
|
@@ -10061,7 +10580,10 @@ async function listPhysicalDevices(timeoutMs, signal) {
|
|
|
10061
10580
|
signal
|
|
10062
10581
|
);
|
|
10063
10582
|
if (result.exitCode !== 0) return [];
|
|
10064
|
-
return physicalDestinations(
|
|
10583
|
+
return physicalDestinations(
|
|
10584
|
+
JSON.parse(await readFile2(output, "utf8")),
|
|
10585
|
+
includeUnavailable
|
|
10586
|
+
);
|
|
10065
10587
|
} finally {
|
|
10066
10588
|
await rm3(directory, { recursive: true, force: true });
|
|
10067
10589
|
}
|
|
@@ -10069,24 +10591,7 @@ async function listPhysicalDevices(timeoutMs, signal) {
|
|
|
10069
10591
|
var inspectBuildDestinations = async (payload, timeoutMs, signal) => {
|
|
10070
10592
|
const input = parseBuildDestinationsPayload(payload);
|
|
10071
10593
|
const folder = await validateWorktree(input, timeoutMs, signal);
|
|
10072
|
-
|
|
10073
|
-
const preflight = await command(
|
|
10074
|
-
"xcrun",
|
|
10075
|
-
[
|
|
10076
|
-
"xcodebuild",
|
|
10077
|
-
...sourceArguments(input.source, absolutePath),
|
|
10078
|
-
"-scheme",
|
|
10079
|
-
input.scheme,
|
|
10080
|
-
"-configuration",
|
|
10081
|
-
input.configuration,
|
|
10082
|
-
"-showBuildSettings",
|
|
10083
|
-
"-json"
|
|
10084
|
-
],
|
|
10085
|
-
Math.min(timeoutMs, 6e4),
|
|
10086
|
-
signal,
|
|
10087
|
-
folder
|
|
10088
|
-
);
|
|
10089
|
-
requireSuccess(preflight, "The saved scheme or configuration is unavailable");
|
|
10594
|
+
await validateSource(folder, input.source);
|
|
10090
10595
|
const [simulators, physical] = await Promise.all([
|
|
10091
10596
|
command(
|
|
10092
10597
|
"xcrun",
|
|
@@ -10112,7 +10617,7 @@ var inspectBuildRunDestinations = async (payload, timeoutMs, signal) => {
|
|
|
10112
10617
|
if (input.destinationType === "SIMULATOR") {
|
|
10113
10618
|
const simulators = await command(
|
|
10114
10619
|
"xcrun",
|
|
10115
|
-
["simctl", "list", "devices", "
|
|
10620
|
+
["simctl", "list", "devices", "-j"],
|
|
10116
10621
|
Math.min(timeoutMs, 3e4),
|
|
10117
10622
|
signal,
|
|
10118
10623
|
folder
|
|
@@ -10120,12 +10625,33 @@ var inspectBuildRunDestinations = async (payload, timeoutMs, signal) => {
|
|
|
10120
10625
|
requireSuccess(simulators, "Could not inspect available simulators");
|
|
10121
10626
|
return {
|
|
10122
10627
|
...successfulProcess,
|
|
10123
|
-
destinations: simulatorDestinations(JSON.parse(simulators.stdout))
|
|
10628
|
+
destinations: simulatorDestinations(JSON.parse(simulators.stdout), true)
|
|
10124
10629
|
};
|
|
10125
10630
|
}
|
|
10126
10631
|
return {
|
|
10127
10632
|
...successfulProcess,
|
|
10128
|
-
destinations: await listPhysicalDevices(timeoutMs, signal)
|
|
10633
|
+
destinations: await listPhysicalDevices(timeoutMs, signal, true)
|
|
10634
|
+
};
|
|
10635
|
+
};
|
|
10636
|
+
var inspectAgentBuildRunDestinations = async (payload, timeoutMs, signal) => {
|
|
10637
|
+
const input = parseBuildAgentRunDestinationsPayload(payload);
|
|
10638
|
+
if (input.destinationType === "SIMULATOR") {
|
|
10639
|
+
const simulators = await command(
|
|
10640
|
+
"xcrun",
|
|
10641
|
+
["simctl", "list", "devices", "-j"],
|
|
10642
|
+
Math.min(timeoutMs, 3e4),
|
|
10643
|
+
signal,
|
|
10644
|
+
process.cwd()
|
|
10645
|
+
);
|
|
10646
|
+
requireSuccess(simulators, "Could not inspect available simulators");
|
|
10647
|
+
return {
|
|
10648
|
+
...successfulProcess,
|
|
10649
|
+
destinations: simulatorDestinations(JSON.parse(simulators.stdout), true)
|
|
10650
|
+
};
|
|
10651
|
+
}
|
|
10652
|
+
return {
|
|
10653
|
+
...successfulProcess,
|
|
10654
|
+
destinations: await listPhysicalDevices(timeoutMs, signal, true)
|
|
10129
10655
|
};
|
|
10130
10656
|
};
|
|
10131
10657
|
function sensitiveValues(env) {
|
|
@@ -10897,8 +11423,8 @@ async function writeJsonReport(input, kind, timeoutMs, signal) {
|
|
|
10897
11423
|
),
|
|
10898
11424
|
`Could not generate ${filename}`
|
|
10899
11425
|
);
|
|
10900
|
-
const
|
|
10901
|
-
const parsed = JSON.parse(
|
|
11426
|
+
const serialized2 = await readFile2(temporary, "utf8");
|
|
11427
|
+
const parsed = JSON.parse(serialized2);
|
|
10902
11428
|
const normalized = kind === "TEST_RESULTS" ? normalizeTestResults(parsed) : normalizeCoverage(parsed);
|
|
10903
11429
|
await rename2(temporary, destination);
|
|
10904
11430
|
return {
|
|
@@ -11246,7 +11772,10 @@ async function captureArtifacts(input, folder, signal, includeProducts = true) {
|
|
|
11246
11772
|
await artifact(input.artifactDirectory, "RUNNABLE_APP", destination, {
|
|
11247
11773
|
bundleIdentifier: settings.PRODUCT_BUNDLE_IDENTIFIER ?? null,
|
|
11248
11774
|
target: apps[0].target,
|
|
11249
|
-
destinationType: input.destination.type
|
|
11775
|
+
destinationType: input.destination.type,
|
|
11776
|
+
architectures: (settings.ARCHS ?? settings.VALID_ARCHS ?? "").split(/\s+/).filter(Boolean),
|
|
11777
|
+
minimumOSVersion: settings.IPHONEOS_DEPLOYMENT_TARGET ?? null,
|
|
11778
|
+
sdkName: settings.SDK_NAME ?? null
|
|
11250
11779
|
})
|
|
11251
11780
|
);
|
|
11252
11781
|
}
|
|
@@ -11325,6 +11854,7 @@ var runIosBuild = async (payload, timeoutMs, signal, onLog, context) => {
|
|
|
11325
11854
|
telemetry: input.telemetry ?? null
|
|
11326
11855
|
};
|
|
11327
11856
|
let buildResult = null;
|
|
11857
|
+
let preflightResult = null;
|
|
11328
11858
|
let coverageChanges = [];
|
|
11329
11859
|
let errorCode = null;
|
|
11330
11860
|
let error = null;
|
|
@@ -11350,29 +11880,50 @@ var runIosBuild = async (payload, timeoutMs, signal, onLog, context) => {
|
|
|
11350
11880
|
} catch (progressError) {
|
|
11351
11881
|
logger.emit("PREPARING", "SYSTEM", cleanError(progressError));
|
|
11352
11882
|
}
|
|
11353
|
-
|
|
11354
|
-
|
|
11355
|
-
|
|
11356
|
-
|
|
11357
|
-
|
|
11358
|
-
|
|
11359
|
-
|
|
11360
|
-
|
|
11361
|
-
phase: "PRE_BUILD",
|
|
11362
|
-
source: script.preBuildScript,
|
|
11363
|
-
contextPath,
|
|
11364
|
-
hookContext: baseHookContext,
|
|
11883
|
+
if (!signal.aborted) {
|
|
11884
|
+
preflightResult = await runLoggedCommand({
|
|
11885
|
+
command: "xcrun",
|
|
11886
|
+
args: xcodeBuildSettingsArguments(input, folder),
|
|
11887
|
+
cwd: folder,
|
|
11888
|
+
env: xcodeEnvironment(),
|
|
11889
|
+
timeoutMs: Math.min(timeoutMs, 6e4),
|
|
11890
|
+
signal,
|
|
11365
11891
|
logger,
|
|
11366
|
-
|
|
11892
|
+
phase: "PREFLIGHT"
|
|
11367
11893
|
});
|
|
11368
|
-
|
|
11369
|
-
if (execution.causedBuildFailure) {
|
|
11894
|
+
if (preflightResult.exitCode !== 0 && !preflightResult.cancelled) {
|
|
11370
11895
|
failBuild = true;
|
|
11371
|
-
errorCode = "
|
|
11372
|
-
error =
|
|
11373
|
-
|
|
11896
|
+
errorCode = preflightResult.timedOut ? "TIMEOUT" : classifyFailure(preflightResult.output);
|
|
11897
|
+
error = cleanError(
|
|
11898
|
+
preflightResult.output || "The saved scheme, configuration, or destination is unavailable"
|
|
11899
|
+
);
|
|
11900
|
+
}
|
|
11901
|
+
}
|
|
11902
|
+
if (!failBuild && !signal.aborted) {
|
|
11903
|
+
for (const script of [...input.scripts].sort(
|
|
11904
|
+
(a, b) => a.position - b.position
|
|
11905
|
+
)) {
|
|
11906
|
+
if (!script.preBuildScript) continue;
|
|
11907
|
+
const execution = await runHook({
|
|
11908
|
+
input,
|
|
11909
|
+
folder,
|
|
11910
|
+
script,
|
|
11911
|
+
phase: "PRE_BUILD",
|
|
11912
|
+
source: script.preBuildScript,
|
|
11913
|
+
contextPath,
|
|
11914
|
+
hookContext: baseHookContext,
|
|
11915
|
+
logger,
|
|
11916
|
+
signal
|
|
11917
|
+
});
|
|
11918
|
+
scriptExecutions.push(execution);
|
|
11919
|
+
if (execution.causedBuildFailure) {
|
|
11920
|
+
failBuild = true;
|
|
11921
|
+
errorCode = "SCRIPT_FAILED";
|
|
11922
|
+
error = execution.error;
|
|
11923
|
+
break;
|
|
11924
|
+
}
|
|
11925
|
+
if (signal.aborted) break;
|
|
11374
11926
|
}
|
|
11375
|
-
if (signal.aborted) break;
|
|
11376
11927
|
}
|
|
11377
11928
|
if (!failBuild && !signal.aborted) {
|
|
11378
11929
|
try {
|
|
@@ -11430,7 +11981,7 @@ var runIosBuild = async (payload, timeoutMs, signal, onLog, context) => {
|
|
|
11430
11981
|
...baseHookContext,
|
|
11431
11982
|
buildFolder: input.artifactDirectory,
|
|
11432
11983
|
failed: failBuild || !signal.aborted && buildResult !== null && buildResult.exitCode !== 0,
|
|
11433
|
-
cancelled: signal.aborted || buildResult?.cancelled === true,
|
|
11984
|
+
cancelled: signal.aborted || preflightResult?.cancelled === true || buildResult?.cancelled === true,
|
|
11434
11985
|
errorCode,
|
|
11435
11986
|
error
|
|
11436
11987
|
},
|
|
@@ -11466,7 +12017,7 @@ var runIosBuild = async (payload, timeoutMs, signal, onLog, context) => {
|
|
|
11466
12017
|
}
|
|
11467
12018
|
const reports = [];
|
|
11468
12019
|
const testAction = ["TEST", "TEST_WITHOUT_BUILDING"].includes(input.action);
|
|
11469
|
-
const canGenerateReports = () => !signal.aborted && buildResult
|
|
12020
|
+
const canGenerateReports = () => !signal.aborted && buildResult !== null && !buildResult.cancelled && !buildResult.timedOut;
|
|
11470
12021
|
if (canGenerateReports() && testAction && input.advancedSettings.parseTestResults) {
|
|
11471
12022
|
const report = await writeJsonReport(
|
|
11472
12023
|
input,
|
|
@@ -11510,8 +12061,8 @@ var runIosBuild = async (payload, timeoutMs, signal, onLog, context) => {
|
|
|
11510
12061
|
await artifact(input.artifactDirectory, "RAW_LOG", rawLog)
|
|
11511
12062
|
);
|
|
11512
12063
|
}
|
|
11513
|
-
const cancelled = signal.aborted || buildResult?.cancelled === true;
|
|
11514
|
-
const timedOut = buildResult?.timedOut === true;
|
|
12064
|
+
const cancelled = signal.aborted || preflightResult?.cancelled === true || buildResult?.cancelled === true;
|
|
12065
|
+
const timedOut = preflightResult?.timedOut === true || buildResult?.timedOut === true;
|
|
11515
12066
|
const exitCode = cancelled ? null : failBuild || !buildResult ? 1 : buildResult.exitCode;
|
|
11516
12067
|
const args = xcodeBuildArguments(input);
|
|
11517
12068
|
const finalStateHash = await worktreeCodeStateHash(
|
|
@@ -11522,7 +12073,7 @@ var runIosBuild = async (payload, timeoutMs, signal, onLog, context) => {
|
|
|
11522
12073
|
const codeStateObservedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
11523
12074
|
return {
|
|
11524
12075
|
exitCode,
|
|
11525
|
-
signal: buildResult?.signal ?? null,
|
|
12076
|
+
signal: buildResult?.signal ?? preflightResult?.signal ?? null,
|
|
11526
12077
|
timedOut,
|
|
11527
12078
|
cancelled,
|
|
11528
12079
|
errorCode,
|
|
@@ -11633,6 +12184,67 @@ var downloadIosBuildArtifact = async (payload, timeoutMs, signal, onLog, context
|
|
|
11633
12184
|
}
|
|
11634
12185
|
}
|
|
11635
12186
|
};
|
|
12187
|
+
var uploadTransferredIosBuildArtifact = async (payload, timeoutMs, signal, onLog, context) => {
|
|
12188
|
+
const input = parseBuildArtifactTransferUploadPayload(payload);
|
|
12189
|
+
if (!context?.uploadBuildArtifactTransfer) {
|
|
12190
|
+
throw new Error("This agent cannot upload transferred build artifacts");
|
|
12191
|
+
}
|
|
12192
|
+
const root = await realpath2(input.artifactDirectory);
|
|
12193
|
+
const target2 = await realpath2(
|
|
12194
|
+
containedPath2(root, input.artifactRelativePath)
|
|
12195
|
+
);
|
|
12196
|
+
const difference = relative3(root, target2);
|
|
12197
|
+
if (!difference || difference === ".." || difference.startsWith(`..${sep3}`) || isAbsolute3(difference)) {
|
|
12198
|
+
throw new Error("Build artifact resolves outside the build folder");
|
|
12199
|
+
}
|
|
12200
|
+
const information = await stat2(target2);
|
|
12201
|
+
let uploadPath = target2;
|
|
12202
|
+
let filename = basename2(target2);
|
|
12203
|
+
let contentType = "application/octet-stream";
|
|
12204
|
+
let temporaryArchive = null;
|
|
12205
|
+
try {
|
|
12206
|
+
if (information.isDirectory()) {
|
|
12207
|
+
temporaryArchive = join3(
|
|
12208
|
+
tmpdir2(),
|
|
12209
|
+
`ade-build-transfer-${input.transferId}-${randomUUID()}.tar.gz`
|
|
12210
|
+
);
|
|
12211
|
+
requireSuccess(
|
|
12212
|
+
await command(
|
|
12213
|
+
"tar",
|
|
12214
|
+
["-czf", temporaryArchive, "-C", dirname2(target2), basename2(target2)],
|
|
12215
|
+
timeoutMs,
|
|
12216
|
+
signal
|
|
12217
|
+
),
|
|
12218
|
+
"Could not package the runnable app"
|
|
12219
|
+
);
|
|
12220
|
+
uploadPath = temporaryArchive;
|
|
12221
|
+
filename = `${filename}.tar.gz`;
|
|
12222
|
+
contentType = "application/gzip";
|
|
12223
|
+
} else if (!information.isFile()) {
|
|
12224
|
+
throw new Error("Build artifact is not transferable");
|
|
12225
|
+
}
|
|
12226
|
+
const checksum = await fileChecksum(uploadPath);
|
|
12227
|
+
if (!checksum)
|
|
12228
|
+
throw new Error("Could not checksum the runnable app archive");
|
|
12229
|
+
await onLog({
|
|
12230
|
+
sequence: 0,
|
|
12231
|
+
stream: "SYSTEM",
|
|
12232
|
+
message: `Uploading ${filename} in resumable 16 MiB chunks`,
|
|
12233
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
12234
|
+
});
|
|
12235
|
+
await context.uploadBuildArtifactTransfer({
|
|
12236
|
+
transferId: input.transferId,
|
|
12237
|
+
path: uploadPath,
|
|
12238
|
+
filename,
|
|
12239
|
+
contentType,
|
|
12240
|
+
checksum,
|
|
12241
|
+
signal
|
|
12242
|
+
});
|
|
12243
|
+
return successfulProcess;
|
|
12244
|
+
} finally {
|
|
12245
|
+
if (temporaryArchive) await rm3(temporaryArchive, { force: true });
|
|
12246
|
+
}
|
|
12247
|
+
};
|
|
11636
12248
|
async function runSimpleLogged(logger, options) {
|
|
11637
12249
|
return runLoggedCommand({
|
|
11638
12250
|
command: "xcrun",
|
|
@@ -11651,17 +12263,7 @@ async function runSimpleLogged(logger, options) {
|
|
|
11651
12263
|
function simulatorAppArguments(destinationId) {
|
|
11652
12264
|
return ["-a", "Simulator", "--args", "-CurrentDeviceUDID", destinationId];
|
|
11653
12265
|
}
|
|
11654
|
-
|
|
11655
|
-
const input = parseBuildDeploymentPayload(payload);
|
|
11656
|
-
const folder = await validateWorktree(
|
|
11657
|
-
input,
|
|
11658
|
-
Math.min(timeoutMs, 6e4),
|
|
11659
|
-
signal
|
|
11660
|
-
);
|
|
11661
|
-
const appPath = containedPath2(
|
|
11662
|
-
input.artifactDirectory,
|
|
11663
|
-
input.artifactRelativePath
|
|
11664
|
-
);
|
|
12266
|
+
async function deployPreparedIosBuild(input, folder, appPath, timeoutMs, signal, context) {
|
|
11665
12267
|
if (!await pathExists(appPath) || !appPath.endsWith(".app")) {
|
|
11666
12268
|
throw new Error("Runnable app artifact is missing");
|
|
11667
12269
|
}
|
|
@@ -11851,6 +12453,99 @@ var deployIosBuild = async (payload, timeoutMs, signal, _onLog, context) => {
|
|
|
11851
12453
|
} finally {
|
|
11852
12454
|
await logger.close();
|
|
11853
12455
|
}
|
|
12456
|
+
}
|
|
12457
|
+
var deployIosBuild = async (payload, timeoutMs, signal, _onLog, context) => {
|
|
12458
|
+
const input = parseBuildDeploymentPayload(payload);
|
|
12459
|
+
const folder = await validateWorktree(
|
|
12460
|
+
input,
|
|
12461
|
+
Math.min(timeoutMs, 6e4),
|
|
12462
|
+
signal
|
|
12463
|
+
);
|
|
12464
|
+
const appPath = containedPath2(
|
|
12465
|
+
input.artifactDirectory,
|
|
12466
|
+
input.artifactRelativePath
|
|
12467
|
+
);
|
|
12468
|
+
return deployPreparedIosBuild(
|
|
12469
|
+
input,
|
|
12470
|
+
folder,
|
|
12471
|
+
appPath,
|
|
12472
|
+
timeoutMs,
|
|
12473
|
+
signal,
|
|
12474
|
+
context
|
|
12475
|
+
);
|
|
12476
|
+
};
|
|
12477
|
+
async function createRemoteBuildStagingDirectory(buildId) {
|
|
12478
|
+
return realpath2(
|
|
12479
|
+
await mkdtemp2(join3(tmpdir2(), `ade-remote-build-${buildId}-`))
|
|
12480
|
+
);
|
|
12481
|
+
}
|
|
12482
|
+
var deployTransferredIosBuild = async (payload, timeoutMs, signal, onLog, context) => {
|
|
12483
|
+
const input = parseBuildRemoteDeploymentPayload(payload);
|
|
12484
|
+
if (!context?.downloadBuildArtifactTransfer) {
|
|
12485
|
+
throw new Error("This agent cannot download transferred build artifacts");
|
|
12486
|
+
}
|
|
12487
|
+
const staging = await createRemoteBuildStagingDirectory(input.buildId);
|
|
12488
|
+
const archive = join3(staging, "artifact.tar.gz");
|
|
12489
|
+
try {
|
|
12490
|
+
await onLog({
|
|
12491
|
+
sequence: 0,
|
|
12492
|
+
stream: "SYSTEM",
|
|
12493
|
+
message: "Waiting for the relayed build artifact",
|
|
12494
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
12495
|
+
});
|
|
12496
|
+
await context.downloadBuildArtifactTransfer({
|
|
12497
|
+
transferId: input.transferId,
|
|
12498
|
+
path: archive,
|
|
12499
|
+
signal
|
|
12500
|
+
});
|
|
12501
|
+
const listing = await command(
|
|
12502
|
+
"tar",
|
|
12503
|
+
["-tzf", archive],
|
|
12504
|
+
Math.min(timeoutMs, 12e4),
|
|
12505
|
+
signal,
|
|
12506
|
+
staging
|
|
12507
|
+
);
|
|
12508
|
+
requireSuccess(listing, "Transferred build archive is malformed");
|
|
12509
|
+
const entries = listing.stdout.split("\n").map((entry) => entry.trim().replace(/^\.\//, "")).filter(Boolean);
|
|
12510
|
+
if (!entries.length || entries.some(
|
|
12511
|
+
(entry) => entry.startsWith("/") || entry.split("/").some((component) => component === "..")
|
|
12512
|
+
)) {
|
|
12513
|
+
throw new Error("Transferred build archive contains an unsafe path");
|
|
12514
|
+
}
|
|
12515
|
+
const roots = new Set(entries.map((entry) => entry.split("/")[0]));
|
|
12516
|
+
const root = [...roots][0];
|
|
12517
|
+
if (roots.size !== 1 || !root?.endsWith(".app")) {
|
|
12518
|
+
throw new Error("Transferred build archive must contain one app bundle");
|
|
12519
|
+
}
|
|
12520
|
+
requireSuccess(
|
|
12521
|
+
await command(
|
|
12522
|
+
"tar",
|
|
12523
|
+
["-xzf", archive, "-C", staging],
|
|
12524
|
+
Math.min(timeoutMs, 12e4),
|
|
12525
|
+
signal,
|
|
12526
|
+
staging
|
|
12527
|
+
),
|
|
12528
|
+
"Could not extract the transferred app"
|
|
12529
|
+
);
|
|
12530
|
+
const appPath = await realpath2(join3(staging, root));
|
|
12531
|
+
const pathDifference = relative3(staging, appPath);
|
|
12532
|
+
if (pathDifference === ".." || pathDifference.startsWith(`..${sep3}`) || isAbsolute3(pathDifference)) {
|
|
12533
|
+
throw new Error("Transferred app resolves outside its staging directory");
|
|
12534
|
+
}
|
|
12535
|
+
return await deployPreparedIosBuild(
|
|
12536
|
+
{
|
|
12537
|
+
...input,
|
|
12538
|
+
artifactDirectory: staging
|
|
12539
|
+
},
|
|
12540
|
+
staging,
|
|
12541
|
+
appPath,
|
|
12542
|
+
timeoutMs,
|
|
12543
|
+
signal,
|
|
12544
|
+
context
|
|
12545
|
+
);
|
|
12546
|
+
} finally {
|
|
12547
|
+
await rm3(staging, { recursive: true, force: true });
|
|
12548
|
+
}
|
|
11854
12549
|
};
|
|
11855
12550
|
function exportPlist(settings) {
|
|
11856
12551
|
const method = {
|
|
@@ -15174,7 +15869,7 @@ var runWorkflowGitCheckpoint = async (payloadValue, _timeoutMs, signal) => {
|
|
|
15174
15869
|
|
|
15175
15870
|
// src/handlers/worktrees.ts
|
|
15176
15871
|
import { createWriteStream as createWriteStream2, watch } from "node:fs";
|
|
15177
|
-
import { mkdtemp as mkdtemp6, open as
|
|
15872
|
+
import { mkdtemp as mkdtemp6, open as open3, readFile as readFile9, realpath as realpath9, rm as rm10, stat as stat10 } from "node:fs/promises";
|
|
15178
15873
|
import { tmpdir as tmpdir6 } from "node:os";
|
|
15179
15874
|
import { basename as basename5, dirname as dirname8, isAbsolute as isAbsolute7, join as join14, relative as relative8 } from "node:path";
|
|
15180
15875
|
import { spawn as spawn7 } from "node:child_process";
|
|
@@ -16438,7 +17133,7 @@ function requestedDiffPaths(input) {
|
|
|
16438
17133
|
return [input.path];
|
|
16439
17134
|
}
|
|
16440
17135
|
async function readBoundedFile(path, limit) {
|
|
16441
|
-
const file = await
|
|
17136
|
+
const file = await open3(path, "r");
|
|
16442
17137
|
try {
|
|
16443
17138
|
const metadata = await file.stat();
|
|
16444
17139
|
if (!metadata.isFile()) throw new Error("Changed path is not a file");
|
|
@@ -18385,6 +19080,280 @@ var autoSyncWorktree = async (payload, timeoutMs, signal) => {
|
|
|
18385
19080
|
};
|
|
18386
19081
|
};
|
|
18387
19082
|
|
|
19083
|
+
// src/handlers/tailscale.ts
|
|
19084
|
+
import { accessSync as accessSync2, constants as constants2 } from "node:fs";
|
|
19085
|
+
var MAX_OUTPUT_BYTES2 = 2 * 1024 * 1024;
|
|
19086
|
+
var mutationQueues = /* @__PURE__ */ new Map();
|
|
19087
|
+
function objectValue14(value) {
|
|
19088
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
19089
|
+
}
|
|
19090
|
+
function resolveTailscaleExecutable(platform2 = process.platform, resolve12 = findExecutable, isExecutable2 = (path) => {
|
|
19091
|
+
accessSync2(path, constants2.X_OK);
|
|
19092
|
+
return true;
|
|
19093
|
+
}) {
|
|
19094
|
+
const resolved = resolve12("tailscale", {
|
|
19095
|
+
overrideVariable: "CONTROL_AGENT_TAILSCALE_EXECUTABLE"
|
|
19096
|
+
});
|
|
19097
|
+
if (resolved) return resolved;
|
|
19098
|
+
if (platform2 === "darwin") {
|
|
19099
|
+
for (const path of [
|
|
19100
|
+
"/Applications/Tailscale.app/Contents/MacOS/Tailscale",
|
|
19101
|
+
"/Applications/Tailscale.app/Contents/MacOS/tailscale"
|
|
19102
|
+
]) {
|
|
19103
|
+
try {
|
|
19104
|
+
if (isExecutable2(path)) return path;
|
|
19105
|
+
} catch {
|
|
19106
|
+
}
|
|
19107
|
+
}
|
|
19108
|
+
}
|
|
19109
|
+
throw new Error(
|
|
19110
|
+
"Tailscale CLI was not found. Install Tailscale or set CONTROL_AGENT_TAILSCALE_EXECUTABLE."
|
|
19111
|
+
);
|
|
19112
|
+
}
|
|
19113
|
+
function parseTailscaleJson(value, label) {
|
|
19114
|
+
try {
|
|
19115
|
+
const parsed = JSON.parse(value);
|
|
19116
|
+
const object = objectValue14(parsed);
|
|
19117
|
+
if (!object) throw new Error("expected an object");
|
|
19118
|
+
return object;
|
|
19119
|
+
} catch (error) {
|
|
19120
|
+
throw new Error(
|
|
19121
|
+
`${label} returned malformed JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
19122
|
+
);
|
|
19123
|
+
}
|
|
19124
|
+
}
|
|
19125
|
+
function tailscaleCommandFailure(command3, result) {
|
|
19126
|
+
const reason = result.timedOut ? "timed out" : result.cancelled ? "was cancelled" : result.stderr.trim() || `exited with status ${result.exitCode}`;
|
|
19127
|
+
return new Error(`${command3} failed: ${reason}`);
|
|
19128
|
+
}
|
|
19129
|
+
async function captureTailscale(executable, args, timeoutMs, signal) {
|
|
19130
|
+
const result = await captureCommand({
|
|
19131
|
+
command: executable,
|
|
19132
|
+
args,
|
|
19133
|
+
timeoutMs,
|
|
19134
|
+
signal,
|
|
19135
|
+
maxOutputBytes: MAX_OUTPUT_BYTES2
|
|
19136
|
+
});
|
|
19137
|
+
if (result.outputTruncated) {
|
|
19138
|
+
throw new Error(`tailscale ${args.join(" ")} exceeded the output limit`);
|
|
19139
|
+
}
|
|
19140
|
+
if (result.exitCode !== 0) {
|
|
19141
|
+
throw tailscaleCommandFailure(`tailscale ${args.join(" ")}`, result);
|
|
19142
|
+
}
|
|
19143
|
+
return result;
|
|
19144
|
+
}
|
|
19145
|
+
function backendIdentity(status2) {
|
|
19146
|
+
const self2 = objectValue14(status2.Self);
|
|
19147
|
+
const rawAddresses = Array.isArray(self2?.TailscaleIPs) ? self2.TailscaleIPs : status2.TailscaleIPs;
|
|
19148
|
+
const addresses = Array.isArray(rawAddresses) ? rawAddresses.filter((value) => typeof value === "string") : [];
|
|
19149
|
+
const dnsName = typeof self2?.DNSName === "string" ? self2.DNSName : null;
|
|
19150
|
+
return {
|
|
19151
|
+
dnsHostname: dnsName?.replace(/\.$/, "") ?? null,
|
|
19152
|
+
ipv4: addresses.filter((value) => value.includes(".")),
|
|
19153
|
+
ipv6: addresses.filter((value) => value.includes(":")),
|
|
19154
|
+
backendState: typeof status2.BackendState === "string" ? status2.BackendState : "Unknown"
|
|
19155
|
+
};
|
|
19156
|
+
}
|
|
19157
|
+
function destinationFromProxy(proxy) {
|
|
19158
|
+
const parsed = new URL(proxy);
|
|
19159
|
+
if (parsed.hostname !== "127.0.0.1") {
|
|
19160
|
+
throw new Error("Only 127.0.0.1 Tailscale Serve targets are managed");
|
|
19161
|
+
}
|
|
19162
|
+
const rawProtocol = parsed.protocol.replace(/:$/, "");
|
|
19163
|
+
const protocol = rawProtocol === "https+insecure" ? "HTTPS_INSECURE" : rawProtocol === "https" ? "HTTPS" : rawProtocol === "tcp" ? "TCP" : "HTTP";
|
|
19164
|
+
return {
|
|
19165
|
+
protocol,
|
|
19166
|
+
port: Number(parsed.port),
|
|
19167
|
+
path: protocol === "TCP" ? "" : parsed.pathname === "/" ? "" : parsed.pathname
|
|
19168
|
+
};
|
|
19169
|
+
}
|
|
19170
|
+
function portFromListener(value) {
|
|
19171
|
+
const match = value.match(/:(\d+)$/) ?? value.match(/^(\d+)$/);
|
|
19172
|
+
return match ? Number(match[1]) : null;
|
|
19173
|
+
}
|
|
19174
|
+
function normalizeTailscaleServeSnapshot(status2, serve, inspectedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
19175
|
+
const routes = [];
|
|
19176
|
+
const tcp = objectValue14(serve.TCP) ?? {};
|
|
19177
|
+
const web = objectValue14(serve.Web) ?? {};
|
|
19178
|
+
const funnel = objectValue14(serve.AllowFunnel) ?? {};
|
|
19179
|
+
for (const [listener, rawWeb] of Object.entries(web)) {
|
|
19180
|
+
const listenPort = portFromListener(listener);
|
|
19181
|
+
const webConfig = objectValue14(rawWeb);
|
|
19182
|
+
const handlers2 = objectValue14(webConfig?.Handlers);
|
|
19183
|
+
if (!listenPort || !handlers2) continue;
|
|
19184
|
+
const tcpConfig = objectValue14(tcp[String(listenPort)]);
|
|
19185
|
+
const protocol = tcpConfig?.HTTPS === true ? "HTTPS" : "HTTP";
|
|
19186
|
+
const isFunnel = Object.entries(funnel).some(
|
|
19187
|
+
([key, enabled]) => enabled === true && portFromListener(key) === listenPort
|
|
19188
|
+
);
|
|
19189
|
+
for (const [mountPath, rawHandler] of Object.entries(handlers2)) {
|
|
19190
|
+
const handler = objectValue14(rawHandler);
|
|
19191
|
+
if (!handler || typeof handler.Proxy !== "string") continue;
|
|
19192
|
+
const capabilities = Array.isArray(handler.AcceptAppCaps) ? handler.AcceptAppCaps.filter(
|
|
19193
|
+
(value) => typeof value === "string"
|
|
19194
|
+
) : [];
|
|
19195
|
+
try {
|
|
19196
|
+
routes.push(
|
|
19197
|
+
parseTailscaleServeRoute({
|
|
19198
|
+
protocol,
|
|
19199
|
+
listenPort,
|
|
19200
|
+
mountPath,
|
|
19201
|
+
destination: destinationFromProxy(handler.Proxy),
|
|
19202
|
+
funnel: isFunnel,
|
|
19203
|
+
appCapabilities: capabilities,
|
|
19204
|
+
proxyProtocol: "NONE"
|
|
19205
|
+
})
|
|
19206
|
+
);
|
|
19207
|
+
} catch {
|
|
19208
|
+
}
|
|
19209
|
+
}
|
|
19210
|
+
}
|
|
19211
|
+
for (const [listener, rawTcp] of Object.entries(tcp)) {
|
|
19212
|
+
const listenPort = portFromListener(listener);
|
|
19213
|
+
const config = objectValue14(rawTcp);
|
|
19214
|
+
if (!listenPort || !config || typeof config.TCPForward !== "string")
|
|
19215
|
+
continue;
|
|
19216
|
+
const destination = config.TCPForward.startsWith("tcp://") ? config.TCPForward : `tcp://${config.TCPForward}`;
|
|
19217
|
+
const proxy = config.ProxyProtocol;
|
|
19218
|
+
try {
|
|
19219
|
+
routes.push(
|
|
19220
|
+
parseTailscaleServeRoute({
|
|
19221
|
+
protocol: config.TerminateTLS ? "TLS_TERMINATED_TCP" : "TCP",
|
|
19222
|
+
listenPort,
|
|
19223
|
+
mountPath: "/",
|
|
19224
|
+
destination: destinationFromProxy(destination),
|
|
19225
|
+
funnel: Object.entries(funnel).some(
|
|
19226
|
+
([key, enabled]) => enabled === true && portFromListener(key) === listenPort
|
|
19227
|
+
),
|
|
19228
|
+
appCapabilities: [],
|
|
19229
|
+
proxyProtocol: proxy === 1 || proxy === "1" || proxy === "V1" ? "V1" : proxy === 2 || proxy === "2" || proxy === "V2" ? "V2" : "NONE"
|
|
19230
|
+
})
|
|
19231
|
+
);
|
|
19232
|
+
} catch {
|
|
19233
|
+
}
|
|
19234
|
+
}
|
|
19235
|
+
return {
|
|
19236
|
+
identity: backendIdentity(status2),
|
|
19237
|
+
routes: routes.sort(
|
|
19238
|
+
(first, second) => `${first.listenPort}:${first.mountPath}`.localeCompare(
|
|
19239
|
+
`${second.listenPort}:${second.mountPath}`
|
|
19240
|
+
)
|
|
19241
|
+
),
|
|
19242
|
+
inspectedAt
|
|
19243
|
+
};
|
|
19244
|
+
}
|
|
19245
|
+
async function inspect(executable, timeoutMs, signal) {
|
|
19246
|
+
const status2 = await captureTailscale(
|
|
19247
|
+
executable,
|
|
19248
|
+
["status", "--json"],
|
|
19249
|
+
timeoutMs,
|
|
19250
|
+
signal
|
|
19251
|
+
);
|
|
19252
|
+
const serve = await captureTailscale(
|
|
19253
|
+
executable,
|
|
19254
|
+
["serve", "status", "--json"],
|
|
19255
|
+
timeoutMs,
|
|
19256
|
+
signal
|
|
19257
|
+
);
|
|
19258
|
+
return normalizeTailscaleServeSnapshot(
|
|
19259
|
+
parseTailscaleJson(status2.stdout, "tailscale status --json"),
|
|
19260
|
+
parseTailscaleJson(serve.stdout, "tailscale serve status --json")
|
|
19261
|
+
);
|
|
19262
|
+
}
|
|
19263
|
+
function listenerArguments(route) {
|
|
19264
|
+
const flag = route.protocol === "TLS_TERMINATED_TCP" ? "tls-terminated-tcp" : route.protocol.toLowerCase();
|
|
19265
|
+
return [
|
|
19266
|
+
`--${flag}=${route.listenPort}`,
|
|
19267
|
+
...route.protocol === "HTTP" || route.protocol === "HTTPS" ? [`--set-path=${route.mountPath}`] : []
|
|
19268
|
+
];
|
|
19269
|
+
}
|
|
19270
|
+
function routeTarget(route) {
|
|
19271
|
+
const scheme = route.destination.protocol === "HTTPS_INSECURE" ? "https+insecure" : route.destination.protocol.toLowerCase();
|
|
19272
|
+
return `${scheme}://127.0.0.1:${route.destination.port}${route.destination.path}`;
|
|
19273
|
+
}
|
|
19274
|
+
function tailscaleUpsertArguments(route) {
|
|
19275
|
+
const normalized = parseTailscaleServeRoute(route);
|
|
19276
|
+
return [
|
|
19277
|
+
normalized.funnel ? "funnel" : "serve",
|
|
19278
|
+
"--bg",
|
|
19279
|
+
"--yes",
|
|
19280
|
+
...listenerArguments(normalized),
|
|
19281
|
+
...normalized.appCapabilities.length ? [`--accept-app-caps=${normalized.appCapabilities.join(",")}`] : [],
|
|
19282
|
+
...normalized.proxyProtocol === "NONE" ? [] : [`--proxy-protocol=${normalized.proxyProtocol === "V1" ? "1" : "2"}`],
|
|
19283
|
+
routeTarget(normalized)
|
|
19284
|
+
];
|
|
19285
|
+
}
|
|
19286
|
+
function tailscaleRemoveArguments(route) {
|
|
19287
|
+
const normalized = parseTailscaleServeRoute(route);
|
|
19288
|
+
return [
|
|
19289
|
+
normalized.funnel ? "funnel" : "serve",
|
|
19290
|
+
...listenerArguments(normalized),
|
|
19291
|
+
"off"
|
|
19292
|
+
];
|
|
19293
|
+
}
|
|
19294
|
+
function tailscaleUpsertCommandSequence(route, previousRoute) {
|
|
19295
|
+
return [
|
|
19296
|
+
...previousRoute && JSON.stringify(previousRoute) !== JSON.stringify(route) ? [tailscaleRemoveArguments(previousRoute)] : [],
|
|
19297
|
+
tailscaleUpsertArguments(route)
|
|
19298
|
+
];
|
|
19299
|
+
}
|
|
19300
|
+
async function serialized(agentId, work) {
|
|
19301
|
+
const previous = mutationQueues.get(agentId) ?? Promise.resolve();
|
|
19302
|
+
let release2;
|
|
19303
|
+
const current = new Promise((resolve12) => {
|
|
19304
|
+
release2 = resolve12;
|
|
19305
|
+
});
|
|
19306
|
+
const tail = previous.then(() => current);
|
|
19307
|
+
mutationQueues.set(agentId, tail);
|
|
19308
|
+
await previous;
|
|
19309
|
+
try {
|
|
19310
|
+
return await work();
|
|
19311
|
+
} finally {
|
|
19312
|
+
release2();
|
|
19313
|
+
if (mutationQueues.get(agentId) === tail) mutationQueues.delete(agentId);
|
|
19314
|
+
}
|
|
19315
|
+
}
|
|
19316
|
+
function success2(snapshot) {
|
|
19317
|
+
return {
|
|
19318
|
+
exitCode: 0,
|
|
19319
|
+
signal: null,
|
|
19320
|
+
timedOut: false,
|
|
19321
|
+
cancelled: false,
|
|
19322
|
+
snapshot
|
|
19323
|
+
};
|
|
19324
|
+
}
|
|
19325
|
+
var inspectTailscaleServe = async (rawPayload, timeoutMs, signal) => {
|
|
19326
|
+
tailscaleServeInspectPayload(rawPayload);
|
|
19327
|
+
const executable = resolveTailscaleExecutable();
|
|
19328
|
+
return success2(await inspect(executable, timeoutMs, signal));
|
|
19329
|
+
};
|
|
19330
|
+
var upsertTailscaleServe = async (rawPayload, timeoutMs, signal, _onLog, context) => {
|
|
19331
|
+
const payload = tailscaleServeUpsertPayload(rawPayload);
|
|
19332
|
+
const executable = resolveTailscaleExecutable();
|
|
19333
|
+
return serialized(context?.agentId ?? "default", async () => {
|
|
19334
|
+
for (const args of tailscaleUpsertCommandSequence(
|
|
19335
|
+
payload.route,
|
|
19336
|
+
payload.previousRoute
|
|
19337
|
+
)) {
|
|
19338
|
+
await captureTailscale(executable, args, timeoutMs, signal);
|
|
19339
|
+
}
|
|
19340
|
+
return success2(await inspect(executable, timeoutMs, signal));
|
|
19341
|
+
});
|
|
19342
|
+
};
|
|
19343
|
+
var removeTailscaleServe = async (rawPayload, timeoutMs, signal, _onLog, context) => {
|
|
19344
|
+
const payload = tailscaleServeRemovePayload(rawPayload);
|
|
19345
|
+
const executable = resolveTailscaleExecutable();
|
|
19346
|
+
return serialized(context?.agentId ?? "default", async () => {
|
|
19347
|
+
await captureTailscale(
|
|
19348
|
+
executable,
|
|
19349
|
+
tailscaleRemoveArguments(payload.route),
|
|
19350
|
+
timeoutMs,
|
|
19351
|
+
signal
|
|
19352
|
+
);
|
|
19353
|
+
return success2(await inspect(executable, timeoutMs, signal));
|
|
19354
|
+
});
|
|
19355
|
+
};
|
|
19356
|
+
|
|
18388
19357
|
// src/handlers/index.ts
|
|
18389
19358
|
var handlers = {
|
|
18390
19359
|
[CLI_HEALTH_JOB_KIND]: runCliHealth,
|
|
@@ -18423,10 +19392,13 @@ var handlers = {
|
|
|
18423
19392
|
[IOS_SOURCE_PARSE_JOB_KIND]: parseBuildSourceMetadata,
|
|
18424
19393
|
[IOS_DESTINATIONS_JOB_KIND]: inspectBuildDestinations,
|
|
18425
19394
|
[IOS_RUN_DESTINATIONS_JOB_KIND]: inspectBuildRunDestinations,
|
|
19395
|
+
[IOS_AGENT_RUN_DESTINATIONS_JOB_KIND]: inspectAgentBuildRunDestinations,
|
|
18426
19396
|
[IOS_BUILD_JOB_KIND]: runIosBuild,
|
|
18427
19397
|
[IOS_BUILD_DELETE_JOB_KIND]: deleteIosBuild,
|
|
18428
19398
|
[IOS_ARTIFACT_DOWNLOAD_JOB_KIND]: downloadIosBuildArtifact,
|
|
19399
|
+
[IOS_ARTIFACT_TRANSFER_UPLOAD_JOB_KIND]: uploadTransferredIosBuildArtifact,
|
|
18429
19400
|
[IOS_DEPLOY_JOB_KIND]: deployIosBuild,
|
|
19401
|
+
[IOS_REMOTE_DEPLOY_JOB_KIND]: deployTransferredIosBuild,
|
|
18430
19402
|
[IOS_EXPORT_JOB_KIND]: exportIosArchive,
|
|
18431
19403
|
[IOS_TEST_RESULTS_JOB_KIND]: generateIosBuildReport,
|
|
18432
19404
|
[IOS_COVERAGE_REPORT_JOB_KIND]: generateIosBuildReport,
|
|
@@ -18437,7 +19409,10 @@ var handlers = {
|
|
|
18437
19409
|
[SIGNING_PROFILE_INSTALL_JOB_KIND]: installSigningProfile,
|
|
18438
19410
|
[SIGNING_PROFILE_DELETE_JOB_KIND]: deleteSigningProfile,
|
|
18439
19411
|
[SIGNING_IDENTITY_IMPORT_JOB_KIND]: importSigningIdentity,
|
|
18440
|
-
[SIGNING_IDENTITY_DELETE_JOB_KIND]: deleteSigningIdentity
|
|
19412
|
+
[SIGNING_IDENTITY_DELETE_JOB_KIND]: deleteSigningIdentity,
|
|
19413
|
+
[TAILSCALE_SERVE_INSPECT_JOB_KIND]: inspectTailscaleServe,
|
|
19414
|
+
[TAILSCALE_SERVE_UPSERT_JOB_KIND]: upsertTailscaleServe,
|
|
19415
|
+
[TAILSCALE_SERVE_REMOVE_JOB_KIND]: removeTailscaleServe
|
|
18441
19416
|
};
|
|
18442
19417
|
|
|
18443
19418
|
// src/repository-coordinator.ts
|
|
@@ -18520,6 +19495,8 @@ var JobExecutor = class {
|
|
|
18520
19495
|
reportBuildProgress: (input) => this.client.reportBuildProgress(input),
|
|
18521
19496
|
appendBuildLogChunks: (buildId, chunks) => this.client.appendBuildLogChunks(buildId, chunks),
|
|
18522
19497
|
uploadBuildArtifact: (input) => this.client.uploadBuildArtifact(input),
|
|
19498
|
+
uploadBuildArtifactTransfer: (input) => this.client.uploadBuildArtifactTransfer(input),
|
|
19499
|
+
downloadBuildArtifactTransfer: (input) => this.client.downloadBuildArtifactTransfer(input),
|
|
18523
19500
|
claimSigningSecretTransfer: (transferId) => this.client.claimSigningSecretTransfer(transferId),
|
|
18524
19501
|
claimWorkflowJobSecrets: () => this.client.claimWorkflowJobSecrets(claimed.id),
|
|
18525
19502
|
appendCommandOutput: (attemptId, chunks) => this.client.appendCommandOutput(claimed.id, attemptId, chunks)
|
|
@@ -52441,8 +53418,8 @@ var ClaudeAdapter = class {
|
|
|
52441
53418
|
dir: worktree.folder,
|
|
52442
53419
|
limit: 500
|
|
52443
53420
|
});
|
|
52444
|
-
const
|
|
52445
|
-
if (
|
|
53421
|
+
const serialized2 = JSON.stringify(history);
|
|
53422
|
+
if (serialized2.includes('"permissionMode":"plan"')) kind = "PLAN";
|
|
52446
53423
|
finalOutput = firstString([...history].reverse());
|
|
52447
53424
|
this.hydrationCache.set(session.sessionId, {
|
|
52448
53425
|
lastModified: lastModifiedKey,
|
|
@@ -53008,8 +53985,8 @@ var CodexAdapter = class {
|
|
|
53008
53985
|
})
|
|
53009
53986
|
);
|
|
53010
53987
|
const hydrated = asRecord(detail.thread);
|
|
53011
|
-
const
|
|
53012
|
-
if (
|
|
53988
|
+
const serialized2 = JSON.stringify(hydrated.turns ?? []);
|
|
53989
|
+
if (serialized2.includes('"type":"plan"')) kind = "PLAN";
|
|
53013
53990
|
finalOutput = firstString(
|
|
53014
53991
|
Array.isArray(hydrated.turns) ? [...hydrated.turns].reverse() : []
|
|
53015
53992
|
);
|
|
@@ -60376,15 +61353,6 @@ var defaultDependencies = {
|
|
|
60376
61353
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
60377
61354
|
})
|
|
60378
61355
|
};
|
|
60379
|
-
function assertLoopbackServer(server) {
|
|
60380
|
-
const hostname2 = new URL(server).hostname.toLowerCase();
|
|
60381
|
-
const isIpv4Loopback = /^127(?:\.\d{1,3}){3}$/.test(hostname2);
|
|
60382
|
-
if (hostname2 !== "localhost" && hostname2 !== "[::1]" && !isIpv4Loopback) {
|
|
60383
|
-
throw new Error(
|
|
60384
|
-
`Development auto-enrollment is restricted to loopback servers; received ${hostname2}`
|
|
60385
|
-
);
|
|
60386
|
-
}
|
|
60387
|
-
}
|
|
60388
61356
|
async function waitForServer(client2, timeoutMs, signal, dependencies) {
|
|
60389
61357
|
const deadline = Date.now() + timeoutMs;
|
|
60390
61358
|
let lastError;
|
|
@@ -60398,17 +61366,18 @@ async function waitForServer(client2, timeoutMs, signal, dependencies) {
|
|
|
60398
61366
|
}
|
|
60399
61367
|
}
|
|
60400
61368
|
const detail = lastError instanceof Error ? `: ${lastError.message}` : "";
|
|
60401
|
-
throw new Error(
|
|
61369
|
+
throw new Error(
|
|
61370
|
+
`Timed out waiting for the development control plane${detail}`
|
|
61371
|
+
);
|
|
60402
61372
|
}
|
|
60403
61373
|
async function prepareDevelopmentAgent(options, signal, dependencies = defaultDependencies) {
|
|
60404
61374
|
const server = normalizeServer(options.server);
|
|
60405
|
-
assertLoopbackServer(server);
|
|
60406
61375
|
const websocketServer = options.websocketServer ?? defaultWebSocketServer(server);
|
|
60407
61376
|
const configFile = options.configFile ?? developmentConfigPath();
|
|
60408
61377
|
const inventory = dependencies.inventory();
|
|
60409
61378
|
const name = options.name ?? `${inventory.hostname}-dev`;
|
|
60410
61379
|
const anonymousClient = dependencies.createClient(server);
|
|
60411
|
-
console.log(`Waiting for
|
|
61380
|
+
console.log(`Waiting for development control plane at ${server} ...`);
|
|
60412
61381
|
await waitForServer(
|
|
60413
61382
|
anonymousClient,
|
|
60414
61383
|
options.waitTimeoutMs ?? DEFAULT_SERVER_WAIT_MS,
|