@devrouter/cli 0.0.60 → 0.0.61
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/devrouter-lifecycle-worker.js +4353 -3818
- package/dist/devrouter.js +963 -551
- package/package.json +2 -1
- package/upgrade-prompts/0.0.61.md +40 -0
package/dist/devrouter.js
CHANGED
|
@@ -630,9 +630,9 @@ function tryReclaimStaleLock(lockPath, staleLinkPath, livenessCache) {
|
|
|
630
630
|
throw error;
|
|
631
631
|
}
|
|
632
632
|
try {
|
|
633
|
-
const
|
|
634
|
-
if (
|
|
635
|
-
return { kind: "live", pid:
|
|
633
|
+
const record4 = parseLockRecord(import_node_fs3.default.readFileSync(fd, "utf-8"));
|
|
634
|
+
if (record4.owner && isLockOwnerLive(record4.owner, livenessCache)) {
|
|
635
|
+
return { kind: "live", pid: record4.owner.pid, acquiredAtMs: record4.acquiredAtMs };
|
|
636
636
|
}
|
|
637
637
|
const staleStat = import_node_fs3.default.fstatSync(fd);
|
|
638
638
|
if (staleStat.nlink !== 1) {
|
|
@@ -724,12 +724,12 @@ function acquireFileLock(lockPath, options) {
|
|
|
724
724
|
continue;
|
|
725
725
|
}
|
|
726
726
|
const acquiredAtMs = Date.now();
|
|
727
|
-
const
|
|
728
|
-
import_node_fs3.default.writeFileSync(candidatePath, `${
|
|
727
|
+
const record4 = `${owner}:${acquiredAtMs}`;
|
|
728
|
+
import_node_fs3.default.writeFileSync(candidatePath, `${record4}
|
|
729
729
|
`, "utf-8");
|
|
730
730
|
try {
|
|
731
731
|
import_node_fs3.default.linkSync(candidatePath, lockPath);
|
|
732
|
-
return
|
|
732
|
+
return record4;
|
|
733
733
|
} catch (error) {
|
|
734
734
|
if (error.code !== "EEXIST") {
|
|
735
735
|
throw error;
|
|
@@ -1350,11 +1350,11 @@ function listHostRouteState() {
|
|
|
1350
1350
|
function upsertHostRoute(input2) {
|
|
1351
1351
|
return withStateLock(() => {
|
|
1352
1352
|
const routes = readHostRouteStateLocked();
|
|
1353
|
-
const
|
|
1354
|
-
const existing = routes.find((route) => route.id ===
|
|
1353
|
+
const id3 = buildHostRouteId(input2.repoPath, input2.name);
|
|
1354
|
+
const existing = routes.find((route) => route.id === id3);
|
|
1355
1355
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1356
1356
|
const next = hostRouteFromInput(input2, existing, now);
|
|
1357
|
-
const remaining = routes.filter((route) => route.id !==
|
|
1357
|
+
const remaining = routes.filter((route) => route.id !== id3);
|
|
1358
1358
|
remaining.push(next);
|
|
1359
1359
|
remaining.sort((a, b) => a.name.localeCompare(b.name) || a.repoPath.localeCompare(b.repoPath));
|
|
1360
1360
|
writeState(remaining);
|
|
@@ -1388,10 +1388,10 @@ function replaceHostRoutesForRepo(repoPath, inputs) {
|
|
|
1388
1388
|
}
|
|
1389
1389
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1390
1390
|
const replacements = inputs.map((input2) => {
|
|
1391
|
-
const
|
|
1391
|
+
const id3 = buildHostRouteId(input2.repoPath, input2.name);
|
|
1392
1392
|
return hostRouteFromInput(
|
|
1393
1393
|
input2,
|
|
1394
|
-
routes.find((route) => route.id ===
|
|
1394
|
+
routes.find((route) => route.id === id3),
|
|
1395
1395
|
now
|
|
1396
1396
|
);
|
|
1397
1397
|
});
|
|
@@ -1402,10 +1402,10 @@ function replaceHostRoutesForRepo(repoPath, inputs) {
|
|
|
1402
1402
|
return replacements;
|
|
1403
1403
|
});
|
|
1404
1404
|
}
|
|
1405
|
-
function removeHostRouteById(
|
|
1405
|
+
function removeHostRouteById(id3) {
|
|
1406
1406
|
return withStateLock(() => {
|
|
1407
1407
|
const routes = readHostRouteStateLocked();
|
|
1408
|
-
const next = routes.filter((route) => route.id !==
|
|
1408
|
+
const next = routes.filter((route) => route.id !== id3);
|
|
1409
1409
|
if (next.length === routes.length) {
|
|
1410
1410
|
return false;
|
|
1411
1411
|
}
|
|
@@ -2450,7 +2450,7 @@ function loadRepoConfig(repoPath, read = (file) => import_node_fs6.default.readF
|
|
|
2450
2450
|
const config = parseConfig(parsed ?? {}, configPath);
|
|
2451
2451
|
const requiredVersion = config.devrouter?.version;
|
|
2452
2452
|
if (requiredVersion && !hasWarnedVersionMismatch) {
|
|
2453
|
-
const cliVersion = true ? "0.0.
|
|
2453
|
+
const cliVersion = true ? "0.0.61" : "0.0.0-dev";
|
|
2454
2454
|
if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
|
|
2455
2455
|
hasWarnedVersionMismatch = true;
|
|
2456
2456
|
process.stderr.write(
|
|
@@ -3424,29 +3424,29 @@ var init_reliability_contract = __esm({
|
|
|
3424
3424
|
});
|
|
3425
3425
|
|
|
3426
3426
|
// src/core/reliability-operation-store.ts
|
|
3427
|
-
function identityKey(
|
|
3428
|
-
if (!import_node_path10.default.isAbsolute(
|
|
3427
|
+
function identityKey(identity2) {
|
|
3428
|
+
if (!import_node_path10.default.isAbsolute(identity2.repoPath) || import_node_path10.default.resolve(identity2.repoPath) !== identity2.repoPath || identity2.repoPath.includes("\0") || identity2.workspace !== null && !isReliabilityId(identity2.workspace) || !["devpod", "devsy"].includes(identity2.provider)) {
|
|
3429
3429
|
throw new Error("Invalid reliability workspace identity.");
|
|
3430
3430
|
}
|
|
3431
|
-
return (0, import_node_crypto6.createHash)("sha256").update(
|
|
3431
|
+
return (0, import_node_crypto6.createHash)("sha256").update(identity2.repoPath).digest("hex");
|
|
3432
3432
|
}
|
|
3433
|
-
function reliabilityOperationPath(
|
|
3434
|
-
return import_node_path10.default.join(DEVROUTER_HOME, "reliability", `${identityKey(
|
|
3433
|
+
function reliabilityOperationPath(identity2) {
|
|
3434
|
+
return import_node_path10.default.join(DEVROUTER_HOME, "reliability", `${identityKey(identity2)}.json`);
|
|
3435
3435
|
}
|
|
3436
3436
|
function keys(value, expected) {
|
|
3437
3437
|
if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((key) => !expected.includes(key))) {
|
|
3438
3438
|
throw new Error("Reliability record contains unsupported fields.");
|
|
3439
3439
|
}
|
|
3440
3440
|
}
|
|
3441
|
-
function validate(
|
|
3442
|
-
keys(
|
|
3443
|
-
keys(
|
|
3444
|
-
if (
|
|
3441
|
+
function validate(record4, identity2) {
|
|
3442
|
+
keys(record4, ["version", "identity", "revision", "state", "worker", "effectSequence", "outcome"]);
|
|
3443
|
+
keys(record4.identity, ["repoPath", "workspace", "provider"]);
|
|
3444
|
+
if (record4.version !== 1 || record4.identity.repoPath !== identity2.repoPath || record4.identity.workspace !== identity2.workspace || record4.identity.provider !== identity2.provider || !isReliabilityCounter(record4.revision) || !isReliabilityCounter(record4.effectSequence)) {
|
|
3445
3445
|
throw new Error("Reliability record has invalid version, ownership, or counters.");
|
|
3446
3446
|
}
|
|
3447
|
-
const state =
|
|
3447
|
+
const state = record4.state;
|
|
3448
3448
|
assertReliabilityState(state);
|
|
3449
|
-
if (state.environmentId !== identityKey(
|
|
3449
|
+
if (state.environmentId !== identityKey(identity2) || state.executionPolicy !== "manual") {
|
|
3450
3450
|
throw new Error("Reliability record does not grant manual authority for this workspace.");
|
|
3451
3451
|
}
|
|
3452
3452
|
keys(state, [
|
|
@@ -3490,24 +3490,24 @@ function validate(record3, identity) {
|
|
|
3490
3490
|
throw new Error("Reliability operation and retained result disagree.");
|
|
3491
3491
|
}
|
|
3492
3492
|
}
|
|
3493
|
-
if (
|
|
3494
|
-
keys(
|
|
3495
|
-
keys(
|
|
3496
|
-
const outcome =
|
|
3493
|
+
if (record4.outcome !== null) {
|
|
3494
|
+
keys(record4.outcome, ["operationId", "status", "exitCode", "transport"]);
|
|
3495
|
+
keys(record4.outcome.transport, ["exitCode", "signal"]);
|
|
3496
|
+
const outcome = record4.outcome;
|
|
3497
3497
|
const validExit = (value) => Number.isInteger(value) && Number(value) >= 0 && Number(value) <= 255;
|
|
3498
3498
|
if (outcome.operationId !== state.operation?.id || !["completed", "not-started", "completion-unknown"].includes(outcome.status) || (outcome.status === "completed" ? !validExit(outcome.exitCode) : outcome.exitCode !== null) || outcome.transport.exitCode !== null && !validExit(outcome.transport.exitCode) || outcome.transport.signal !== null && (typeof outcome.transport.signal !== "string" || !/^SIG[A-Z0-9]{1,16}$/.test(outcome.transport.signal))) {
|
|
3499
3499
|
throw new Error("Reliability execution outcome is invalid.");
|
|
3500
3500
|
}
|
|
3501
3501
|
}
|
|
3502
|
-
if (
|
|
3503
|
-
keys(
|
|
3504
|
-
if (!isReliabilityId(
|
|
3502
|
+
if (record4.worker !== null) {
|
|
3503
|
+
keys(record4.worker, ["id", "operationId", "pid", "birth"]);
|
|
3504
|
+
if (!isReliabilityId(record4.worker.id) || !isReliabilityId(record4.worker.operationId) || typeof record4.worker.birth !== "string" || !/^(proc|ps):[a-zA-Z0-9]+$/.test(record4.worker.birth) || !isReliabilityCounter(record4.worker.pid) || record4.worker.pid === 0 || record4.worker.operationId !== state.operation?.id) {
|
|
3505
3505
|
throw new Error("Reliability worker identity is invalid.");
|
|
3506
3506
|
}
|
|
3507
3507
|
}
|
|
3508
3508
|
}
|
|
3509
|
-
function readReliabilityOperation(
|
|
3510
|
-
const file = reliabilityOperationPath(
|
|
3509
|
+
function readReliabilityOperation(identity2) {
|
|
3510
|
+
const file = reliabilityOperationPath(identity2);
|
|
3511
3511
|
let descriptor;
|
|
3512
3512
|
try {
|
|
3513
3513
|
descriptor = import_node_fs9.default.openSync(
|
|
@@ -3526,27 +3526,27 @@ function readReliabilityOperation(identity) {
|
|
|
3526
3526
|
const bytes = Buffer.alloc(MAX_RECORD_BYTES + 1);
|
|
3527
3527
|
const count = import_node_fs9.default.readSync(descriptor, bytes, 0, bytes.length, 0);
|
|
3528
3528
|
if (count > MAX_RECORD_BYTES) throw new Error("Reliability record exceeds its byte limit.");
|
|
3529
|
-
const
|
|
3529
|
+
const record4 = JSON.parse(
|
|
3530
3530
|
bytes.subarray(0, count).toString("utf8")
|
|
3531
3531
|
);
|
|
3532
|
-
validate(
|
|
3533
|
-
return
|
|
3532
|
+
validate(record4, identity2);
|
|
3533
|
+
return record4;
|
|
3534
3534
|
} finally {
|
|
3535
3535
|
import_node_fs9.default.closeSync(descriptor);
|
|
3536
3536
|
}
|
|
3537
3537
|
}
|
|
3538
|
-
function persist(
|
|
3539
|
-
validate(
|
|
3540
|
-
const contents = `${JSON.stringify(
|
|
3538
|
+
function persist(record4) {
|
|
3539
|
+
validate(record4, record4.identity);
|
|
3540
|
+
const contents = `${JSON.stringify(record4)}
|
|
3541
3541
|
`;
|
|
3542
3542
|
if (Buffer.byteLength(contents) > MAX_RECORD_BYTES)
|
|
3543
3543
|
throw new Error("Reliability record exceeds its byte limit.");
|
|
3544
|
-
const file = reliabilityOperationPath(
|
|
3544
|
+
const file = reliabilityOperationPath(record4.identity);
|
|
3545
3545
|
try {
|
|
3546
3546
|
writeFileAtomically(file, contents);
|
|
3547
3547
|
} catch (error) {
|
|
3548
|
-
const reread = readReliabilityOperation(
|
|
3549
|
-
if (!reread || JSON.stringify(reread) !== JSON.stringify(
|
|
3548
|
+
const reread = readReliabilityOperation(record4.identity);
|
|
3549
|
+
if (!reread || JSON.stringify(reread) !== JSON.stringify(record4)) throw error;
|
|
3550
3550
|
const descriptor = import_node_fs9.default.openSync(file, import_node_fs9.default.constants.O_RDONLY | import_node_fs9.default.constants.O_NOFOLLOW);
|
|
3551
3551
|
try {
|
|
3552
3552
|
import_node_fs9.default.fsyncSync(descriptor);
|
|
@@ -3561,38 +3561,38 @@ function persist(record3) {
|
|
|
3561
3561
|
}
|
|
3562
3562
|
}
|
|
3563
3563
|
}
|
|
3564
|
-
function updateReliabilityOperation(
|
|
3565
|
-
const file = reliabilityOperationPath(
|
|
3564
|
+
function updateReliabilityOperation(identity2, operation) {
|
|
3565
|
+
const file = reliabilityOperationPath(identity2);
|
|
3566
3566
|
import_node_fs9.default.mkdirSync(import_node_path10.default.dirname(file), { recursive: true, mode: 448 });
|
|
3567
3567
|
return withFileLockSync(`${file}.lock`, { activity: "reliability intent", waitMs: 2e3 }, () => {
|
|
3568
|
-
const
|
|
3568
|
+
const record4 = readReliabilityOperation(identity2) ?? {
|
|
3569
3569
|
version: 1,
|
|
3570
|
-
identity: { ...
|
|
3570
|
+
identity: { ...identity2 },
|
|
3571
3571
|
revision: 0,
|
|
3572
|
-
state: createReliabilityState(identityKey(
|
|
3572
|
+
state: createReliabilityState(identityKey(identity2), 0, "manual"),
|
|
3573
3573
|
worker: null,
|
|
3574
3574
|
effectSequence: 0,
|
|
3575
3575
|
outcome: null
|
|
3576
3576
|
};
|
|
3577
|
-
if (
|
|
3577
|
+
if (record4.revision === Number.MAX_SAFE_INTEGER)
|
|
3578
3578
|
throw new Error("Reliability record revision is exhausted.");
|
|
3579
|
-
const result = operation(
|
|
3579
|
+
const result = operation(record4);
|
|
3580
3580
|
if (result && typeof result.then === "function") {
|
|
3581
3581
|
throw new Error("Reliability record updates must be synchronous.");
|
|
3582
3582
|
}
|
|
3583
|
-
validate(
|
|
3584
|
-
|
|
3585
|
-
persist(
|
|
3583
|
+
validate(record4, identity2);
|
|
3584
|
+
record4.revision += 1;
|
|
3585
|
+
persist(record4);
|
|
3586
3586
|
return result;
|
|
3587
3587
|
});
|
|
3588
3588
|
}
|
|
3589
|
-
function withReliabilityObservationFence(
|
|
3590
|
-
const file = reliabilityOperationPath(
|
|
3591
|
-
const before = readReliabilityOperation(
|
|
3589
|
+
function withReliabilityObservationFence(identity2, expectedRevision, publish) {
|
|
3590
|
+
const file = reliabilityOperationPath(identity2);
|
|
3591
|
+
const before = readReliabilityOperation(identity2);
|
|
3592
3592
|
if (!before || before.revision !== expectedRevision)
|
|
3593
3593
|
throw new Error("Reliability observation fence changed or is absent.");
|
|
3594
3594
|
return withFileLockSync(`${file}.lock`, { activity: "observer publication", waitMs: 100 }, () => {
|
|
3595
|
-
const current = readReliabilityOperation(
|
|
3595
|
+
const current = readReliabilityOperation(identity2);
|
|
3596
3596
|
if (!current || current.revision !== expectedRevision)
|
|
3597
3597
|
throw new Error("Reliability observation fence changed or is absent.");
|
|
3598
3598
|
const result = publish(current);
|
|
@@ -3731,8 +3731,8 @@ var init_controller_monitor = __esm({
|
|
|
3731
3731
|
if (this.stopped) return;
|
|
3732
3732
|
const snapshot = this.sessions.read();
|
|
3733
3733
|
const current = new Set(snapshot.environments.map((env) => env.id));
|
|
3734
|
-
for (const [
|
|
3735
|
-
for (const
|
|
3734
|
+
for (const [id3, abort] of this.active) if (!current.has(id3)) abort.abort();
|
|
3735
|
+
for (const id3 of this.lastStarted.keys()) if (!current.has(id3)) this.lastStarted.delete(id3);
|
|
3736
3736
|
const now = this.clock();
|
|
3737
3737
|
const candidates = snapshot.environments.filter(
|
|
3738
3738
|
(env) => !this.active.has(env.id) && now - (this.lastStarted.get(env.id) ?? -Infinity) >= 5e3
|
|
@@ -3852,10 +3852,10 @@ async function observeControllerProcess(containerId, name, signal) {
|
|
|
3852
3852
|
],
|
|
3853
3853
|
signal
|
|
3854
3854
|
);
|
|
3855
|
-
const
|
|
3856
|
-
if (!/^[1-9][0-9]* [0-9]+ [a-zA-Z0-9][a-zA-Z0-9._:-]{0,4095}$/.test(
|
|
3855
|
+
const identity2 = output2.trim();
|
|
3856
|
+
if (!/^[1-9][0-9]* [0-9]+ [a-zA-Z0-9][a-zA-Z0-9._:-]{0,4095}$/.test(identity2))
|
|
3857
3857
|
throw new Error("Process observation is unavailable.");
|
|
3858
|
-
return
|
|
3858
|
+
return identity2;
|
|
3859
3859
|
}
|
|
3860
3860
|
var PROCESS_OBSERVATION;
|
|
3861
3861
|
var init_controller_process_observation = __esm({
|
|
@@ -4260,10 +4260,17 @@ function parseDockerLines(stdout) {
|
|
|
4260
4260
|
}
|
|
4261
4261
|
return lines;
|
|
4262
4262
|
}
|
|
4263
|
-
function runManagedStopDocker(args) {
|
|
4264
|
-
|
|
4263
|
+
function runManagedStopDocker(args, endpoint, timeoutMs = MANAGED_STOP_DOCKER_TIMEOUT_MS) {
|
|
4264
|
+
if (endpoint !== void 0) assertManagedStopEndpoint(endpoint);
|
|
4265
|
+
const env = { ...process.env };
|
|
4266
|
+
if (endpoint !== void 0) {
|
|
4267
|
+
delete env.DOCKER_CONTEXT;
|
|
4268
|
+
delete env.DOCKER_HOST;
|
|
4269
|
+
}
|
|
4270
|
+
const result = (0, import_node_child_process6.spawnSync)("docker", endpoint ? ["--host", endpoint, ...args] : args, {
|
|
4271
|
+
...endpoint ? { env } : {},
|
|
4265
4272
|
encoding: "utf-8",
|
|
4266
|
-
timeout:
|
|
4273
|
+
timeout: timeoutMs,
|
|
4267
4274
|
maxBuffer: MANAGED_STOP_DOCKER_MAX_BUFFER
|
|
4268
4275
|
});
|
|
4269
4276
|
if (result.error || result.status !== 0) {
|
|
@@ -4274,17 +4281,21 @@ function runManagedStopDocker(args) {
|
|
|
4274
4281
|
}
|
|
4275
4282
|
return result.stdout;
|
|
4276
4283
|
}
|
|
4277
|
-
function listManagedStopContainerIds(composeProject) {
|
|
4278
|
-
const stdout = runManagedStopDocker(
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4284
|
+
function listManagedStopContainerIds(composeProject, endpoint) {
|
|
4285
|
+
const stdout = runManagedStopDocker(
|
|
4286
|
+
[
|
|
4287
|
+
"ps",
|
|
4288
|
+
"-a",
|
|
4289
|
+
"--no-trunc",
|
|
4290
|
+
"--filter",
|
|
4291
|
+
`label=${MANAGED_STOP_COMPOSE_LABEL}=${composeProject}`,
|
|
4292
|
+
"--format",
|
|
4293
|
+
"{{.ID}}"
|
|
4294
|
+
],
|
|
4295
|
+
endpoint
|
|
4296
|
+
);
|
|
4287
4297
|
const ids = parseDockerLines(stdout);
|
|
4298
|
+
if (ids.length > 256) throw new Error("Managed stop population exceeds its bound.");
|
|
4288
4299
|
ids.forEach(assertFullContainerId);
|
|
4289
4300
|
assertUniqueContainerIds(ids);
|
|
4290
4301
|
return ids;
|
|
@@ -4378,13 +4389,11 @@ function validateManagedStopSnapshot(value, composeProject) {
|
|
|
4378
4389
|
networks: {}
|
|
4379
4390
|
};
|
|
4380
4391
|
}
|
|
4381
|
-
function inspectManagedStopPopulation(ids, composeProject) {
|
|
4382
|
-
const stdout = runManagedStopDocker(
|
|
4383
|
-
"inspect",
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
...ids
|
|
4387
|
-
]);
|
|
4392
|
+
function inspectManagedStopPopulation(ids, composeProject, endpoint) {
|
|
4393
|
+
const stdout = runManagedStopDocker(
|
|
4394
|
+
["inspect", "--format", MANAGED_STOP_INSPECT_TEMPLATE, ...ids],
|
|
4395
|
+
endpoint
|
|
4396
|
+
);
|
|
4388
4397
|
const lines = parseDockerLines(stdout);
|
|
4389
4398
|
if (lines.length !== ids.length) {
|
|
4390
4399
|
throw new Error("Managed stop Docker inspection returned an incomplete population.");
|
|
@@ -4412,22 +4421,22 @@ function requireSameContainerPopulation(expected, actual) {
|
|
|
4412
4421
|
throw new Error("Managed stop Docker inspection observed a changed population.");
|
|
4413
4422
|
}
|
|
4414
4423
|
const expectedSet = new Set(expected);
|
|
4415
|
-
if (actual.some((
|
|
4424
|
+
if (actual.some((id3) => !expectedSet.has(id3))) {
|
|
4416
4425
|
throw new Error("Managed stop Docker inspection observed a changed population.");
|
|
4417
4426
|
}
|
|
4418
4427
|
}
|
|
4419
|
-
function inspectManagedStopContainers(composeProject) {
|
|
4428
|
+
function inspectManagedStopContainers(composeProject, endpoint) {
|
|
4420
4429
|
if (typeof composeProject !== "string" || composeProject.length === 0 || composeProject !== composeProject.trim() || !MANAGED_STOP_PROJECT_PATTERN.test(composeProject)) {
|
|
4421
4430
|
throw new Error("Managed stop Docker inspection requires a safe Compose project.");
|
|
4422
4431
|
}
|
|
4423
|
-
const listedIds = listManagedStopContainerIds(composeProject);
|
|
4432
|
+
const listedIds = listManagedStopContainerIds(composeProject, endpoint);
|
|
4424
4433
|
if (listedIds.length === 0) {
|
|
4425
|
-
const confirmedIds2 = listManagedStopContainerIds(composeProject);
|
|
4434
|
+
const confirmedIds2 = listManagedStopContainerIds(composeProject, endpoint);
|
|
4426
4435
|
requireSameContainerPopulation(listedIds, confirmedIds2);
|
|
4427
4436
|
return [];
|
|
4428
4437
|
}
|
|
4429
|
-
const snapshots = inspectManagedStopPopulation(listedIds, composeProject);
|
|
4430
|
-
const confirmedIds = listManagedStopContainerIds(composeProject);
|
|
4438
|
+
const snapshots = inspectManagedStopPopulation(listedIds, composeProject, endpoint);
|
|
4439
|
+
const confirmedIds = listManagedStopContainerIds(composeProject, endpoint);
|
|
4431
4440
|
requireSameContainerPopulation(listedIds, confirmedIds);
|
|
4432
4441
|
return snapshots;
|
|
4433
4442
|
}
|
|
@@ -4450,7 +4459,7 @@ function inspectWorkspaceContainers(options) {
|
|
|
4450
4459
|
`docker ps failed: ${(listed.stderr || listed.stdout || listed.error?.message || "unknown error").trim()}`
|
|
4451
4460
|
);
|
|
4452
4461
|
}
|
|
4453
|
-
ids = listed.stdout.split(/\r?\n/).map((
|
|
4462
|
+
ids = listed.stdout.split(/\r?\n/).map((id3) => id3.trim()).filter(Boolean);
|
|
4454
4463
|
}
|
|
4455
4464
|
if (ids.length === 0) return [];
|
|
4456
4465
|
const template = options?.withSize ? SIZE_INSPECT_TEMPLATE : SAFE_INSPECT_TEMPLATE;
|
|
@@ -4514,6 +4523,86 @@ function resolveRunningWorkspaceContainer(repoPath) {
|
|
|
4514
4523
|
}
|
|
4515
4524
|
return { id: container.id, workspacePath: repoMount.Destination };
|
|
4516
4525
|
}
|
|
4526
|
+
function supportsManagedStopBaseline(endpoint) {
|
|
4527
|
+
if (!endpoint || endpoint.length > 4096 || endpoint.trim() !== endpoint || [...endpoint].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127)) {
|
|
4528
|
+
throw new Error("Docker endpoint is malformed.");
|
|
4529
|
+
}
|
|
4530
|
+
if (endpoint.startsWith("unix://")) {
|
|
4531
|
+
if (!endpoint.startsWith("unix:///") || endpoint.length === 8 || import_node_path14.default.normalize(endpoint.slice(7)) !== endpoint.slice(7)) {
|
|
4532
|
+
throw new Error("Docker endpoint is malformed.");
|
|
4533
|
+
}
|
|
4534
|
+
return true;
|
|
4535
|
+
}
|
|
4536
|
+
if (/\s/.test(endpoint)) throw new Error("Docker endpoint is malformed.");
|
|
4537
|
+
if (/^npipe:\/\/\/\/\.\/pipe\/[a-zA-Z0-9_.-]+$/.test(endpoint)) return false;
|
|
4538
|
+
let url;
|
|
4539
|
+
try {
|
|
4540
|
+
url = new URL(endpoint);
|
|
4541
|
+
} catch {
|
|
4542
|
+
throw new Error("Docker endpoint is malformed.");
|
|
4543
|
+
}
|
|
4544
|
+
if (url.protocol !== "tcp:" && url.protocol !== "ssh:" || !url.hostname || url.password || url.search || url.hash || url.protocol === "tcp:" && (url.username || url.pathname && url.pathname !== "/") || url.port && (!/^\d+$/.test(url.port) || Number(url.port) < 1 || Number(url.port) > 65535)) {
|
|
4545
|
+
throw new Error("Docker endpoint is malformed or unsupported.");
|
|
4546
|
+
}
|
|
4547
|
+
return false;
|
|
4548
|
+
}
|
|
4549
|
+
function assertManagedStopEndpoint(endpoint) {
|
|
4550
|
+
if (!supportsManagedStopBaseline(endpoint)) {
|
|
4551
|
+
throw new Error("Managed stop requires an exact local Docker endpoint.");
|
|
4552
|
+
}
|
|
4553
|
+
}
|
|
4554
|
+
function parseManagedStopJson(output2) {
|
|
4555
|
+
try {
|
|
4556
|
+
return JSON.parse(output2);
|
|
4557
|
+
} catch {
|
|
4558
|
+
throw new Error("Managed stop Docker response is malformed.");
|
|
4559
|
+
}
|
|
4560
|
+
}
|
|
4561
|
+
function inspectManagedStopDaemon(endpoint) {
|
|
4562
|
+
const value = parseManagedStopJson(
|
|
4563
|
+
runManagedStopDocker(["info", "--format", "{{json .ID}}"], endpoint)
|
|
4564
|
+
);
|
|
4565
|
+
if (typeof value !== "string" || !value.length || value.length > 256 || /\s/.test(value) || [...value].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127)) {
|
|
4566
|
+
throw new Error("Docker daemon identity is unavailable.");
|
|
4567
|
+
}
|
|
4568
|
+
return value;
|
|
4569
|
+
}
|
|
4570
|
+
function inspectManagedStopRunnerId(endpoint, containerId) {
|
|
4571
|
+
assertFullContainerId(containerId);
|
|
4572
|
+
const value = parseManagedStopJson(
|
|
4573
|
+
runManagedStopDocker(
|
|
4574
|
+
["inspect", "--format", '{{json (index .Config.Labels "dev.containers.id")}}', containerId],
|
|
4575
|
+
endpoint
|
|
4576
|
+
)
|
|
4577
|
+
);
|
|
4578
|
+
if (typeof value !== "string" || value.length > 4096)
|
|
4579
|
+
throw new Error("Provider container binding is unavailable.");
|
|
4580
|
+
return value;
|
|
4581
|
+
}
|
|
4582
|
+
function inspectManagedStopWorkspaceIds(endpoint, composeDirectory) {
|
|
4583
|
+
const ids = parseDockerLines(
|
|
4584
|
+
runManagedStopDocker(
|
|
4585
|
+
[
|
|
4586
|
+
"ps",
|
|
4587
|
+
"-a",
|
|
4588
|
+
"--no-trunc",
|
|
4589
|
+
"--filter",
|
|
4590
|
+
`label=com.docker.compose.project.working_dir=${composeDirectory}`,
|
|
4591
|
+
"--format",
|
|
4592
|
+
"{{.ID}}"
|
|
4593
|
+
],
|
|
4594
|
+
endpoint
|
|
4595
|
+
)
|
|
4596
|
+
);
|
|
4597
|
+
if (ids.length > 256) throw new Error("Managed stop population exceeds its bound.");
|
|
4598
|
+
ids.forEach(assertFullContainerId);
|
|
4599
|
+
assertUniqueContainerIds(ids);
|
|
4600
|
+
return ids;
|
|
4601
|
+
}
|
|
4602
|
+
function stopPinnedManagedContainer(endpoint, containerId) {
|
|
4603
|
+
assertFullContainerId(containerId);
|
|
4604
|
+
runManagedStopDocker(["stop", containerId], endpoint, 3e4);
|
|
4605
|
+
}
|
|
4517
4606
|
var import_node_child_process6, import_node_path14, SAFE_INSPECT_TEMPLATE, SIZE_INSPECT_TEMPLATE, MANAGED_STOP_INSPECT_TEMPLATE, MANAGED_STOP_PROJECT_PATTERN, FULL_CONTAINER_ID_PATTERN, MANAGED_STOP_DOCKER_TIMEOUT_MS, MANAGED_STOP_DOCKER_MAX_BUFFER, MANAGED_STOP_COMPOSE_LABEL, MANAGED_STOP_IDENTITY_LABELS;
|
|
4518
4607
|
var init_devpod_environment = __esm({
|
|
4519
4608
|
"src/core/devpod-environment.ts"() {
|
|
@@ -5084,12 +5173,147 @@ var init_http_route_probe = __esm({
|
|
|
5084
5173
|
}
|
|
5085
5174
|
});
|
|
5086
5175
|
|
|
5176
|
+
// src/core/managed-stop-baseline.ts
|
|
5177
|
+
function record2(value, keys2) {
|
|
5178
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).length !== keys2.length || Object.keys(value).some((key) => !keys2.includes(key))) {
|
|
5179
|
+
throw new Error("Invalid managed stop baseline fields.");
|
|
5180
|
+
}
|
|
5181
|
+
return value;
|
|
5182
|
+
}
|
|
5183
|
+
function text(value, empty = false) {
|
|
5184
|
+
if (typeof value !== "string" || value.length > 4096 || !empty && !value.length || value.trim() !== value || [...value].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127)) {
|
|
5185
|
+
throw new Error("Invalid managed stop baseline identity.");
|
|
5186
|
+
}
|
|
5187
|
+
return value;
|
|
5188
|
+
}
|
|
5189
|
+
function absolute(value) {
|
|
5190
|
+
const result = text(value);
|
|
5191
|
+
if (!import_node_path16.default.isAbsolute(result) || import_node_path16.default.normalize(result) !== result) {
|
|
5192
|
+
throw new Error("Invalid managed stop baseline path.");
|
|
5193
|
+
}
|
|
5194
|
+
return result;
|
|
5195
|
+
}
|
|
5196
|
+
function list(value, parse2 = text) {
|
|
5197
|
+
if (!Array.isArray(value) || !value.length || value.length > 256) {
|
|
5198
|
+
throw new Error("Invalid managed stop baseline population.");
|
|
5199
|
+
}
|
|
5200
|
+
const result = value.map((item) => parse2(item));
|
|
5201
|
+
if (new Set(result).size !== result.length) throw new Error("Duplicate stop baseline identity.");
|
|
5202
|
+
return result;
|
|
5203
|
+
}
|
|
5204
|
+
function id(value) {
|
|
5205
|
+
const result = text(value);
|
|
5206
|
+
if (!/^[a-f0-9]{64}$/.test(result)) throw new Error("Invalid stop baseline container ID.");
|
|
5207
|
+
return result;
|
|
5208
|
+
}
|
|
5209
|
+
function validateManagedStopBaseline(value, state) {
|
|
5210
|
+
const v = record2(value, [
|
|
5211
|
+
"version",
|
|
5212
|
+
"provider",
|
|
5213
|
+
"context",
|
|
5214
|
+
"providerId",
|
|
5215
|
+
"uid",
|
|
5216
|
+
"sourcePath",
|
|
5217
|
+
"sourceContainer",
|
|
5218
|
+
"endpoint",
|
|
5219
|
+
"daemonId",
|
|
5220
|
+
"sourceConfigSha256",
|
|
5221
|
+
"effectiveConfigSha256",
|
|
5222
|
+
"project",
|
|
5223
|
+
"primaryService",
|
|
5224
|
+
"requiredServices",
|
|
5225
|
+
"allowedServices",
|
|
5226
|
+
"composeDirectory",
|
|
5227
|
+
"composeFiles",
|
|
5228
|
+
"featureDirectory",
|
|
5229
|
+
"containers"
|
|
5230
|
+
]);
|
|
5231
|
+
if (v.version !== 1 || v.provider !== "devsy" || Buffer.byteLength(JSON.stringify(value)) > 1024 * 1024) {
|
|
5232
|
+
throw new Error("Unsupported or oversized managed stop baseline.");
|
|
5233
|
+
}
|
|
5234
|
+
const endpoint = text(v.endpoint);
|
|
5235
|
+
if (!endpoint.startsWith("unix://"))
|
|
5236
|
+
throw new Error("Stop baseline requires a pinned local Docker endpoint.");
|
|
5237
|
+
absolute(endpoint.slice(7));
|
|
5238
|
+
const result = {
|
|
5239
|
+
version: 1,
|
|
5240
|
+
provider: "devsy",
|
|
5241
|
+
context: text(v.context),
|
|
5242
|
+
providerId: text(v.providerId),
|
|
5243
|
+
uid: text(v.uid, true),
|
|
5244
|
+
sourcePath: absolute(v.sourcePath),
|
|
5245
|
+
sourceContainer: text(v.sourceContainer, true),
|
|
5246
|
+
endpoint,
|
|
5247
|
+
daemonId: text(v.daemonId),
|
|
5248
|
+
sourceConfigSha256: id(v.sourceConfigSha256),
|
|
5249
|
+
effectiveConfigSha256: id(v.effectiveConfigSha256),
|
|
5250
|
+
project: text(v.project),
|
|
5251
|
+
primaryService: text(v.primaryService),
|
|
5252
|
+
requiredServices: list(v.requiredServices),
|
|
5253
|
+
allowedServices: list(v.allowedServices),
|
|
5254
|
+
composeDirectory: absolute(v.composeDirectory),
|
|
5255
|
+
composeFiles: list(v.composeFiles, absolute),
|
|
5256
|
+
featureDirectory: absolute(v.featureDirectory),
|
|
5257
|
+
containers: []
|
|
5258
|
+
};
|
|
5259
|
+
if (result.providerId !== state.devpodId || result.sourcePath !== state.repoPath || result.project !== state.composeProject || result.sourceConfigSha256 !== state.sourceConfigSha256 || result.effectiveConfigSha256 !== state.effectiveConfigSha256 || !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(result.context) || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(result.providerId) || !/^[a-z0-9][a-z0-9_-]*$/.test(result.project) || !result.requiredServices.includes(result.primaryService) || result.requiredServices.some((service) => !result.allowedServices.includes(service))) {
|
|
5260
|
+
throw new Error("Managed stop baseline does not bind the retained generation.");
|
|
5261
|
+
}
|
|
5262
|
+
if (!Array.isArray(v.containers) || !v.containers.length || v.containers.length > 256) {
|
|
5263
|
+
throw new Error("Invalid managed stop baseline population.");
|
|
5264
|
+
}
|
|
5265
|
+
result.containers = v.containers.map((entry) => {
|
|
5266
|
+
const c = record2(entry, ["id", "service", "configFiles", "mounts"]);
|
|
5267
|
+
const configFiles = list(c.configFiles, absolute);
|
|
5268
|
+
if (configFiles.length < result.composeFiles.length || result.composeFiles.some((file, index) => configFiles[index] !== file) || configFiles.slice(result.composeFiles.length).some(
|
|
5269
|
+
(file) => import_node_path16.default.dirname(file) !== result.featureDirectory || !/^docker-compose\.devcontainer\.containerFeatures-[a-zA-Z0-9_-]+\.yml$/.test(
|
|
5270
|
+
import_node_path16.default.basename(file)
|
|
5271
|
+
)
|
|
5272
|
+
)) {
|
|
5273
|
+
throw new Error("Invalid stop baseline Compose provenance.");
|
|
5274
|
+
}
|
|
5275
|
+
if (!Array.isArray(c.mounts) || c.mounts.length > 256)
|
|
5276
|
+
throw new Error("Invalid baseline mounts.");
|
|
5277
|
+
return {
|
|
5278
|
+
id: id(c.id),
|
|
5279
|
+
service: text(c.service),
|
|
5280
|
+
configFiles,
|
|
5281
|
+
mounts: c.mounts.map((item) => {
|
|
5282
|
+
const mount = record2(item, ["Type", "Source", "Destination"]);
|
|
5283
|
+
return {
|
|
5284
|
+
Type: text(mount.Type),
|
|
5285
|
+
Source: mount.Type === "tmpfs" && mount.Source === "" ? "" : absolute(mount.Source),
|
|
5286
|
+
Destination: absolute(mount.Destination)
|
|
5287
|
+
};
|
|
5288
|
+
})
|
|
5289
|
+
};
|
|
5290
|
+
});
|
|
5291
|
+
const services = result.containers.map((container) => container.service);
|
|
5292
|
+
if (new Set(services).size !== services.length || new Set(result.containers.map((container) => container.id)).size !== result.containers.length || services.some((service) => !result.allowedServices.includes(service)) || result.requiredServices.some((service) => !services.includes(service))) {
|
|
5293
|
+
throw new Error("Incomplete or duplicate stop baseline population.");
|
|
5294
|
+
}
|
|
5295
|
+
const primary = result.containers.find(
|
|
5296
|
+
(container) => container.service === result.primaryService
|
|
5297
|
+
);
|
|
5298
|
+
if (primary?.mounts.filter((mount) => mount.Type === "bind" && mount.Source === state.repoPath).length !== 1 || result.sourceContainer && result.sourceContainer !== primary.id) {
|
|
5299
|
+
throw new Error("Stop baseline has no exact primary source binding.");
|
|
5300
|
+
}
|
|
5301
|
+
return result;
|
|
5302
|
+
}
|
|
5303
|
+
var import_node_path16;
|
|
5304
|
+
var init_managed_stop_baseline = __esm({
|
|
5305
|
+
"src/core/managed-stop-baseline.ts"() {
|
|
5306
|
+
"use strict";
|
|
5307
|
+
import_node_path16 = __toESM(require("path"));
|
|
5308
|
+
}
|
|
5309
|
+
});
|
|
5310
|
+
|
|
5087
5311
|
// src/core/managed-runtime-state.ts
|
|
5088
5312
|
function stateKey(repoPath, workspace) {
|
|
5089
5313
|
return (0, import_node_crypto10.createHash)("sha256").update(`${repoPath}\0${workspace ?? ""}`, "utf-8").digest("hex");
|
|
5090
5314
|
}
|
|
5091
5315
|
function managedRuntimeStatePath(repoPath, workspace) {
|
|
5092
|
-
return
|
|
5316
|
+
return import_node_path17.default.join(DEVROUTER_HOME, "managed-runtime", `${stateKey(repoPath, workspace)}.json`);
|
|
5093
5317
|
}
|
|
5094
5318
|
function isStringArray(value) {
|
|
5095
5319
|
return Array.isArray(value) && value.every((item) => typeof item === "string") && new Set(value).size === value.length;
|
|
@@ -5131,7 +5355,7 @@ function validateState(value, repoPath, workspace) {
|
|
|
5131
5355
|
if (typeof candidate.updatedAt !== "string" || !Number.isFinite(Date.parse(candidate.updatedAt))) {
|
|
5132
5356
|
throw new Error("Managed runtime state has an invalid timestamp.");
|
|
5133
5357
|
}
|
|
5134
|
-
|
|
5358
|
+
const result = {
|
|
5135
5359
|
version: 1,
|
|
5136
5360
|
repoPath,
|
|
5137
5361
|
...workspace !== void 0 ? { workspace } : {},
|
|
@@ -5149,6 +5373,10 @@ function validateState(value, repoPath, workspace) {
|
|
|
5149
5373
|
...candidate.transitionPhase ? { transitionPhase: candidate.transitionPhase } : {},
|
|
5150
5374
|
updatedAt: candidate.updatedAt
|
|
5151
5375
|
};
|
|
5376
|
+
if (Object.hasOwn(candidate, "stopBaseline")) {
|
|
5377
|
+
result.stopBaseline = validateManagedStopBaseline(candidate.stopBaseline, result);
|
|
5378
|
+
}
|
|
5379
|
+
return result;
|
|
5152
5380
|
}
|
|
5153
5381
|
function readManagedRuntimeState(repoPath, workspace, read = (file) => import_node_fs13.default.readFileSync(file, "utf-8")) {
|
|
5154
5382
|
const statePath = managedRuntimeStatePath(repoPath, workspace);
|
|
@@ -5161,14 +5389,15 @@ function readManagedRuntimeState(repoPath, workspace, read = (file) => import_no
|
|
|
5161
5389
|
}
|
|
5162
5390
|
return validateState(parsed, repoPath, workspace);
|
|
5163
5391
|
}
|
|
5164
|
-
var import_node_crypto10, import_node_fs13,
|
|
5392
|
+
var import_node_crypto10, import_node_fs13, import_node_path17;
|
|
5165
5393
|
var init_managed_runtime_state = __esm({
|
|
5166
5394
|
"src/core/managed-runtime-state.ts"() {
|
|
5167
5395
|
"use strict";
|
|
5168
5396
|
import_node_crypto10 = require("crypto");
|
|
5169
5397
|
import_node_fs13 = __toESM(require("fs"));
|
|
5170
|
-
|
|
5398
|
+
import_node_path17 = __toESM(require("path"));
|
|
5171
5399
|
init_atomic_file();
|
|
5400
|
+
init_managed_stop_baseline();
|
|
5172
5401
|
init_router();
|
|
5173
5402
|
}
|
|
5174
5403
|
});
|
|
@@ -5197,7 +5426,7 @@ async function containers(project, signal) {
|
|
|
5197
5426
|
signal
|
|
5198
5427
|
);
|
|
5199
5428
|
const ids = listed.trim() ? listed.trim().split(/\r?\n/).sort() : [];
|
|
5200
|
-
if (ids.length > 128 || new Set(ids).size !== ids.length || ids.some((
|
|
5429
|
+
if (ids.length > 128 || new Set(ids).size !== ids.length || ids.some((id3) => !/^[a-f0-9]{64}$/.test(id3)))
|
|
5201
5430
|
throw new Error("Observation container population is invalid.");
|
|
5202
5431
|
if (!ids.length) return [];
|
|
5203
5432
|
const output2 = await runControllerProbe(
|
|
@@ -5214,12 +5443,12 @@ async function containers(project, signal) {
|
|
|
5214
5443
|
}
|
|
5215
5444
|
return values.sort((a, b) => a.id.localeCompare(b.id));
|
|
5216
5445
|
}
|
|
5217
|
-
var import_node_crypto11,
|
|
5446
|
+
var import_node_crypto11, import_node_path18, OBSERVATION_INSPECT_TEMPLATE, collectControllerObservation;
|
|
5218
5447
|
var init_controller_observation = __esm({
|
|
5219
5448
|
"src/core/controller-observation.ts"() {
|
|
5220
5449
|
"use strict";
|
|
5221
5450
|
import_node_crypto11 = require("crypto");
|
|
5222
|
-
|
|
5451
|
+
import_node_path18 = __toESM(require("path"));
|
|
5223
5452
|
init_controller_binding();
|
|
5224
5453
|
init_controller_monitor();
|
|
5225
5454
|
init_controller_probe();
|
|
@@ -5262,14 +5491,14 @@ var init_controller_observation = __esm({
|
|
|
5262
5491
|
)).trim().split("\n");
|
|
5263
5492
|
if (metadata.length !== 2) throw new Error("Observation ownership paths unavailable.");
|
|
5264
5493
|
const [gitDir, commonDir] = metadata;
|
|
5265
|
-
const sourcePath =
|
|
5494
|
+
const sourcePath = import_node_path18.default.join(environment.repoPath, ".devcontainer/devcontainer.json");
|
|
5266
5495
|
const files = [
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5496
|
+
import_node_path18.default.join(commonDir, "devrouter/workspaces", `${environment.workspace}.json`),
|
|
5497
|
+
import_node_path18.default.join(environment.repoPath, ".devrouter.yml"),
|
|
5498
|
+
import_node_path18.default.join(gitDir, "devrouter-workspace"),
|
|
5270
5499
|
managedRuntimeStatePath(environment.repoPath, environment.workspace),
|
|
5271
5500
|
sourcePath,
|
|
5272
|
-
|
|
5501
|
+
import_node_path18.default.join(environment.repoPath, MANAGED_DEVCONTAINER_PATH)
|
|
5273
5502
|
];
|
|
5274
5503
|
const persisted = captureControllerEvidence(files);
|
|
5275
5504
|
if (controllerBindingFingerprint(persisted.contents[0], persisted.contents[1]) !== environment.fingerprint)
|
|
@@ -5285,16 +5514,16 @@ var init_controller_observation = __esm({
|
|
|
5285
5514
|
const source = parseDevcontainerConfig(persisted.contents[4], sourcePath);
|
|
5286
5515
|
if (!state || state.devpodId !== environment.providerId || state.profile !== environment.profile || typeof source.service !== "string" || !source.service || (0, import_node_crypto11.createHash)("sha256").update(persisted.contents[4]).digest("hex") !== state.sourceConfigSha256 || (0, import_node_crypto11.createHash)("sha256").update(persisted.contents[5]).digest("hex") !== state.effectiveConfigSha256)
|
|
5287
5516
|
throw new ControllerObservationBindingChanged("Observation retained configuration changed.");
|
|
5288
|
-
const
|
|
5517
|
+
const identity2 = {
|
|
5289
5518
|
repoPath: environment.repoPath,
|
|
5290
5519
|
workspace: environment.workspace,
|
|
5291
5520
|
provider: environment.provider
|
|
5292
5521
|
};
|
|
5293
|
-
const journal = readReliabilityOperation(
|
|
5522
|
+
const journal = readReliabilityOperation(identity2);
|
|
5294
5523
|
if (!journal) throw new Error("Observation journal is unavailable.");
|
|
5295
5524
|
const routes = readRoutes().filter((route) => route.repoPath === environment.repoPath);
|
|
5296
5525
|
const before = await containers(state.composeProject, signal);
|
|
5297
|
-
const composeDirectory =
|
|
5526
|
+
const composeDirectory = import_node_path18.default.join(environment.repoPath, ".devcontainer");
|
|
5298
5527
|
const references = typeof source.dockerComposeFile === "string" ? [source.dockerComposeFile] : source.dockerComposeFile;
|
|
5299
5528
|
if (!Array.isArray(references) || !references.length || references.some((value) => typeof value !== "string"))
|
|
5300
5529
|
throw new Error("Observation Compose source is unavailable.");
|
|
@@ -5311,7 +5540,7 @@ var init_controller_observation = __esm({
|
|
|
5311
5540
|
if (!recorded || container.labels["com.docker.compose.project.working_dir"] !== composeDirectory)
|
|
5312
5541
|
throw new Error("Observation Compose ownership is unavailable.");
|
|
5313
5542
|
const files2 = recorded.split(",").map((file) => file.trim());
|
|
5314
|
-
if (files2.some((file) => !
|
|
5543
|
+
if (files2.some((file) => !import_node_path18.default.isAbsolute(file)) || !hasExactComposeIdentity(container, {
|
|
5315
5544
|
repoPath: environment.repoPath,
|
|
5316
5545
|
service: container.labels["com.docker.compose.service"] ?? "",
|
|
5317
5546
|
composeProject: state.composeProject,
|
|
@@ -5440,7 +5669,7 @@ var init_controller_observation = __esm({
|
|
|
5440
5669
|
const routeFingerprint = digest2(routes);
|
|
5441
5670
|
return {
|
|
5442
5671
|
environment,
|
|
5443
|
-
identity,
|
|
5672
|
+
identity: identity2,
|
|
5444
5673
|
journal,
|
|
5445
5674
|
sampledAtMs: Math.floor(performance.now()),
|
|
5446
5675
|
runtimeFingerprint: digest2({ before, processes }),
|
|
@@ -5538,11 +5767,11 @@ var init_controller_sessions = __esm({
|
|
|
5538
5767
|
this.previousTime = { monotonic, wall };
|
|
5539
5768
|
return discontinuity;
|
|
5540
5769
|
}
|
|
5541
|
-
acquire(
|
|
5770
|
+
acquire(id3, environment, requirements, monotonic, wall) {
|
|
5542
5771
|
this.tick(monotonic, wall);
|
|
5543
5772
|
const next = this.read();
|
|
5544
5773
|
const required = [...requirements].sort();
|
|
5545
|
-
const existing = next.sessions.find((s) => s.id ===
|
|
5774
|
+
const existing = next.sessions.find((s) => s.id === id3);
|
|
5546
5775
|
const shared = next.environments.find((env) => env.repoPath === environment.repoPath);
|
|
5547
5776
|
if (shared && JSON.stringify(shared) !== JSON.stringify(environment))
|
|
5548
5777
|
throw new Error("Controller environment binding conflicts.");
|
|
@@ -5554,7 +5783,7 @@ var init_controller_sessions = __esm({
|
|
|
5554
5783
|
if (next.sessions.length >= 128 || !shared && next.environments.length >= 32)
|
|
5555
5784
|
throw new Error("Controller coordination capacity exhausted.");
|
|
5556
5785
|
const session = {
|
|
5557
|
-
id:
|
|
5786
|
+
id: id3,
|
|
5558
5787
|
environmentId: environment.id,
|
|
5559
5788
|
generation: (0, import_node_crypto12.randomUUID)(),
|
|
5560
5789
|
requirements: required,
|
|
@@ -5564,7 +5793,7 @@ var init_controller_sessions = __esm({
|
|
|
5564
5793
|
next.sessions.push(session);
|
|
5565
5794
|
this.event(next, session, "acquired");
|
|
5566
5795
|
this.commit(next);
|
|
5567
|
-
this.leases.set(
|
|
5796
|
+
this.leases.set(id3, monotonic + 3e4);
|
|
5568
5797
|
return this.binding(session);
|
|
5569
5798
|
}
|
|
5570
5799
|
publish(bindings, projections, monotonic, wall) {
|
|
@@ -5610,8 +5839,8 @@ var init_controller_sessions = __esm({
|
|
|
5610
5839
|
if (changed) {
|
|
5611
5840
|
this.commit(next);
|
|
5612
5841
|
if (bindingChanged) {
|
|
5613
|
-
for (const
|
|
5614
|
-
if (!next.sessions.some((s) => s.id ===
|
|
5842
|
+
for (const id3 of this.leases.keys())
|
|
5843
|
+
if (!next.sessions.some((s) => s.id === id3)) this.leases.delete(id3);
|
|
5615
5844
|
}
|
|
5616
5845
|
}
|
|
5617
5846
|
}
|
|
@@ -5677,11 +5906,11 @@ function fields(value, expected) {
|
|
|
5677
5906
|
const actual = Object.keys(value);
|
|
5678
5907
|
if (actual.length !== expected.length || actual.some((key) => !expected.includes(key))) fail2();
|
|
5679
5908
|
}
|
|
5680
|
-
function
|
|
5909
|
+
function text2(value, max = 256) {
|
|
5681
5910
|
return typeof value === "string" && value.length > 0 && value.length <= max && ![...value].some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127);
|
|
5682
5911
|
}
|
|
5683
|
-
function
|
|
5684
|
-
return
|
|
5912
|
+
function id2(value) {
|
|
5913
|
+
return text2(value, 128) && /^[a-zA-Z0-9_-]+$/.test(value);
|
|
5685
5914
|
}
|
|
5686
5915
|
function counter(value) {
|
|
5687
5916
|
return Number.isSafeInteger(value) && Number(value) >= 0;
|
|
@@ -5697,7 +5926,7 @@ function validateControllerSnapshot(value) {
|
|
|
5697
5926
|
"sessions",
|
|
5698
5927
|
"events"
|
|
5699
5928
|
]);
|
|
5700
|
-
if (value.version !== 1 || !
|
|
5929
|
+
if (value.version !== 1 || !id2(value.store) || !counter(value.epoch) || !counter(value.revision) || !counter(value.nextSequence) || value.nextSequence < 1 || !Array.isArray(value.environments) || value.environments.length > 32 || !Array.isArray(value.sessions) || value.sessions.length > 128 || !Array.isArray(value.events) || value.events.length > 256)
|
|
5701
5930
|
fail2();
|
|
5702
5931
|
const environments = /* @__PURE__ */ new Set();
|
|
5703
5932
|
const checkouts = /* @__PURE__ */ new Set();
|
|
@@ -5711,7 +5940,7 @@ function validateControllerSnapshot(value) {
|
|
|
5711
5940
|
"profile",
|
|
5712
5941
|
"fingerprint"
|
|
5713
5942
|
]);
|
|
5714
|
-
if (!
|
|
5943
|
+
if (!id2(env.id) || environments.has(env.id) || !text2(env.repoPath, 4096) || !import_node_path19.default.isAbsolute(env.repoPath) || import_node_path19.default.resolve(env.repoPath) !== env.repoPath || checkouts.has(env.repoPath) || !id2(env.workspace) || !id2(env.providerId) || !text2(env.profile) || !/^[a-z0-9-]+(?:,[a-z0-9-]+)*$/.test(env.profile) || !text2(env.fingerprint, 128) || !/^[a-f0-9]{64}$/.test(env.fingerprint) || !["devpod", "devsy"].includes(String(env.provider)))
|
|
5715
5944
|
fail2();
|
|
5716
5945
|
environments.add(env.id);
|
|
5717
5946
|
checkouts.add(env.repoPath);
|
|
@@ -5727,8 +5956,8 @@ function validateControllerSnapshot(value) {
|
|
|
5727
5956
|
"renewedAtMs",
|
|
5728
5957
|
...Object.hasOwn(session, "observation") ? ["observation"] : []
|
|
5729
5958
|
]);
|
|
5730
|
-
if (!
|
|
5731
|
-
(r) => !
|
|
5959
|
+
if (!id2(session.id) || sessions.has(session.id) || !id2(session.environmentId) || !environments.has(session.environmentId) || !id2(session.generation) || !counter(session.renewedAtMs) || !Array.isArray(session.requirements) || session.requirements.length < 1 || session.requirements.length > 16 || new Set(session.requirements).size !== session.requirements.length || session.requirements.some(
|
|
5960
|
+
(r) => !text2(r, 132) || !/^(runtime|app:[a-z0-9][a-z0-9-]*)$/.test(r)
|
|
5732
5961
|
))
|
|
5733
5962
|
fail2();
|
|
5734
5963
|
if (session.observation !== void 0) {
|
|
@@ -5750,7 +5979,7 @@ function validateControllerSnapshot(value) {
|
|
|
5750
5979
|
"WAITING_CAPACITY",
|
|
5751
5980
|
"PARKED_CAPACITY",
|
|
5752
5981
|
"UNKNOWN"
|
|
5753
|
-
].includes(String(observation.status)) || !counter(observation.sampledAtMs) || !counter(observation.validUntilMs) || observation.validUntilMs < observation.sampledAtMs || !counter(observation.journalRevision) || !
|
|
5982
|
+
].includes(String(observation.status)) || !counter(observation.sampledAtMs) || !counter(observation.validUntilMs) || observation.validUntilMs < observation.sampledAtMs || !counter(observation.journalRevision) || !text2(observation.runtimeFingerprint, 128) || !/^[a-f0-9]{64}$/.test(observation.runtimeFingerprint))
|
|
5754
5983
|
fail2();
|
|
5755
5984
|
}
|
|
5756
5985
|
sessions.add(session.id);
|
|
@@ -5760,7 +5989,7 @@ function validateControllerSnapshot(value) {
|
|
|
5760
5989
|
let previous = 0;
|
|
5761
5990
|
for (const event of value.events) {
|
|
5762
5991
|
fields(event, ["sequence", "session", "generation", "kind"]);
|
|
5763
|
-
if (!counter(event.sequence) || event.sequence <= previous || event.sequence >= value.nextSequence || !
|
|
5992
|
+
if (!counter(event.sequence) || event.sequence <= previous || event.sequence >= value.nextSequence || !id2(event.session) || !id2(event.generation) || !["acquired", "renewed", "released", "expired", "invalidated", "observed"].includes(
|
|
5764
5993
|
String(event.kind)
|
|
5765
5994
|
))
|
|
5766
5995
|
fail2();
|
|
@@ -5769,20 +5998,20 @@ function validateControllerSnapshot(value) {
|
|
|
5769
5998
|
if (Buffer.byteLength(JSON.stringify(value.events)) > 262144 || Buffer.byteLength(JSON.stringify(value)) + 1 > CONTROLLER_SNAPSHOT_BYTES)
|
|
5770
5999
|
fail2();
|
|
5771
6000
|
}
|
|
5772
|
-
var import_node_crypto13, import_node_fs14,
|
|
6001
|
+
var import_node_crypto13, import_node_fs14, import_node_path19, CONTROLLER_SNAPSHOT_BYTES, ControllerStore;
|
|
5773
6002
|
var init_controller_store = __esm({
|
|
5774
6003
|
"src/core/controller-store.ts"() {
|
|
5775
6004
|
"use strict";
|
|
5776
6005
|
import_node_crypto13 = require("crypto");
|
|
5777
6006
|
import_node_fs14 = __toESM(require("fs"));
|
|
5778
|
-
|
|
6007
|
+
import_node_path19 = __toESM(require("path"));
|
|
5779
6008
|
init_atomic_file();
|
|
5780
6009
|
CONTROLLER_SNAPSHOT_BYTES = 1048576;
|
|
5781
6010
|
ControllerStore = class {
|
|
5782
6011
|
constructor(directory, write = writeFileAtomically) {
|
|
5783
6012
|
this.directory = directory;
|
|
5784
6013
|
this.write = write;
|
|
5785
|
-
this.file =
|
|
6014
|
+
this.file = import_node_path19.default.join(directory, "snapshot.json");
|
|
5786
6015
|
}
|
|
5787
6016
|
file;
|
|
5788
6017
|
read() {
|
|
@@ -5862,10 +6091,10 @@ var init_controller_store = __esm({
|
|
|
5862
6091
|
|
|
5863
6092
|
// src/core/controller-server.ts
|
|
5864
6093
|
function privateDirectory(directory) {
|
|
5865
|
-
const
|
|
5866
|
-
let current =
|
|
5867
|
-
for (const part of
|
|
5868
|
-
current =
|
|
6094
|
+
const absolute2 = import_node_path20.default.resolve(directory);
|
|
6095
|
+
let current = import_node_path20.default.parse(absolute2).root;
|
|
6096
|
+
for (const part of absolute2.slice(current.length).split(import_node_path20.default.sep)) {
|
|
6097
|
+
current = import_node_path20.default.join(current, part);
|
|
5869
6098
|
try {
|
|
5870
6099
|
const stat2 = import_node_fs15.default.lstatSync(current);
|
|
5871
6100
|
if (!stat2.isDirectory() || stat2.isSymbolicLink())
|
|
@@ -5875,7 +6104,7 @@ function privateDirectory(directory) {
|
|
|
5875
6104
|
import_node_fs15.default.mkdirSync(current, { mode: 448 });
|
|
5876
6105
|
}
|
|
5877
6106
|
}
|
|
5878
|
-
const stat = import_node_fs15.default.lstatSync(
|
|
6107
|
+
const stat = import_node_fs15.default.lstatSync(absolute2);
|
|
5879
6108
|
if (stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0)
|
|
5880
6109
|
throw new Error("Controller directory is not private.");
|
|
5881
6110
|
}
|
|
@@ -5891,10 +6120,10 @@ function validateOwnedFile(file, socket) {
|
|
|
5891
6120
|
}
|
|
5892
6121
|
}
|
|
5893
6122
|
async function runController(options) {
|
|
5894
|
-
const socketPath =
|
|
6123
|
+
const socketPath = import_node_path20.default.join(options.directory, "control.sock");
|
|
5895
6124
|
if (Buffer.byteLength(socketPath) > 103) throw new Error("Controller socket path is too long.");
|
|
5896
6125
|
privateDirectory(options.directory);
|
|
5897
|
-
const lockPath =
|
|
6126
|
+
const lockPath = import_node_path20.default.join(options.directory, "owner.lock");
|
|
5898
6127
|
try {
|
|
5899
6128
|
const stat = import_node_fs15.default.lstatSync(lockPath);
|
|
5900
6129
|
if (!stat.isFile() || stat.uid !== process.getuid?.())
|
|
@@ -6197,13 +6426,13 @@ async function runController(options) {
|
|
|
6197
6426
|
}
|
|
6198
6427
|
});
|
|
6199
6428
|
}
|
|
6200
|
-
var import_node_fs15, import_node_net2,
|
|
6429
|
+
var import_node_fs15, import_node_net2, import_node_path20, FRAME_BYTES;
|
|
6201
6430
|
var init_controller_server = __esm({
|
|
6202
6431
|
"src/core/controller-server.ts"() {
|
|
6203
6432
|
"use strict";
|
|
6204
6433
|
import_node_fs15 = __toESM(require("fs"));
|
|
6205
6434
|
import_node_net2 = __toESM(require("net"));
|
|
6206
|
-
|
|
6435
|
+
import_node_path20 = __toESM(require("path"));
|
|
6207
6436
|
init_controller_monitor();
|
|
6208
6437
|
init_controller_protocol();
|
|
6209
6438
|
init_controller_sessions();
|
|
@@ -6219,7 +6448,7 @@ __export(controller_exports, {
|
|
|
6219
6448
|
runControllerCommand: () => runControllerCommand
|
|
6220
6449
|
});
|
|
6221
6450
|
async function runControllerCommand(method, options, repo) {
|
|
6222
|
-
const directory =
|
|
6451
|
+
const directory = import_node_path21.default.join(DEVROUTER_HOME, "controller");
|
|
6223
6452
|
try {
|
|
6224
6453
|
if (method === "run") {
|
|
6225
6454
|
const controller = new AbortController();
|
|
@@ -6244,7 +6473,7 @@ async function runControllerCommand(method, options, repo) {
|
|
|
6244
6473
|
if (fields2.timeout !== void 0) fields2.timeout = Number(fields2.timeout);
|
|
6245
6474
|
await controllerRequest(
|
|
6246
6475
|
directory,
|
|
6247
|
-
{ method, ...fields2, ...repo ? { path:
|
|
6476
|
+
{ method, ...fields2, ...repo ? { path: import_node_path21.default.resolve(repo) } : {} },
|
|
6248
6477
|
(value) => process.stdout.write(`${JSON.stringify(value)}
|
|
6249
6478
|
`)
|
|
6250
6479
|
);
|
|
@@ -6256,11 +6485,11 @@ async function runControllerCommand(method, options, repo) {
|
|
|
6256
6485
|
process.exitCode = 1;
|
|
6257
6486
|
}
|
|
6258
6487
|
}
|
|
6259
|
-
var
|
|
6488
|
+
var import_node_path21;
|
|
6260
6489
|
var init_controller = __esm({
|
|
6261
6490
|
"src/commands/controller.ts"() {
|
|
6262
6491
|
"use strict";
|
|
6263
|
-
|
|
6492
|
+
import_node_path21 = __toESM(require("path"));
|
|
6264
6493
|
init_controller_binding();
|
|
6265
6494
|
init_controller_client();
|
|
6266
6495
|
init_controller_observation();
|
|
@@ -6308,13 +6537,13 @@ function replaceDevrouterSection(content) {
|
|
|
6308
6537
|
return `${content.slice(0, start)}${buildDevrouterSection()}${content.slice(end)}`;
|
|
6309
6538
|
}
|
|
6310
6539
|
function writeRepoFile(repoPath, relPath, content) {
|
|
6311
|
-
const absolutePath = (0,
|
|
6312
|
-
(0, import_node_fs16.mkdirSync)((0,
|
|
6540
|
+
const absolutePath = (0, import_node_path22.join)(repoPath, relPath);
|
|
6541
|
+
(0, import_node_fs16.mkdirSync)((0, import_node_path22.dirname)(absolutePath), { recursive: true });
|
|
6313
6542
|
(0, import_node_fs16.writeFileSync)(absolutePath, content, "utf-8");
|
|
6314
6543
|
return absolutePath;
|
|
6315
6544
|
}
|
|
6316
6545
|
function ensureAgentsMdSection(repoPath) {
|
|
6317
|
-
const filePath = (0,
|
|
6546
|
+
const filePath = (0, import_node_path22.join)(repoPath, AGENTS_MD);
|
|
6318
6547
|
if ((0, import_node_fs16.existsSync)(filePath)) {
|
|
6319
6548
|
const content = (0, import_node_fs16.readFileSync)(filePath, "utf-8");
|
|
6320
6549
|
if (content.includes(DEVROUTER_SENTINEL)) {
|
|
@@ -6339,12 +6568,12 @@ function ensureSkillFile(repoPath) {
|
|
|
6339
6568
|
const filePath = writeRepoFile(repoPath, DEVROUTER_SKILL_REL_PATH, DEVROUTER_SKILL_CONTENT);
|
|
6340
6569
|
return { path: filePath, written: true };
|
|
6341
6570
|
}
|
|
6342
|
-
var import_node_fs16,
|
|
6571
|
+
var import_node_fs16, import_node_path22, DEVROUTER_SENTINEL, DEVROUTER_END_SENTINEL, LEGACY_DEVROUTER_SECTION_END, AGENTS_MD, DEVROUTER_SKILL_REL_PATH, DEVROUTER_SKILL_CONTENT;
|
|
6343
6572
|
var init_agents_md = __esm({
|
|
6344
6573
|
"src/core/agents-md.ts"() {
|
|
6345
6574
|
"use strict";
|
|
6346
6575
|
import_node_fs16 = require("fs");
|
|
6347
|
-
|
|
6576
|
+
import_node_path22 = require("path");
|
|
6348
6577
|
DEVROUTER_SENTINEL = "<!-- devrouter -->";
|
|
6349
6578
|
DEVROUTER_END_SENTINEL = "<!-- /devrouter -->";
|
|
6350
6579
|
LEGACY_DEVROUTER_SECTION_END = "- `devrouter ls`";
|
|
@@ -6883,7 +7112,7 @@ function buildOnboardingPrompt(options = {}) {
|
|
|
6883
7112
|
"- Managed process reuse fingerprints command argv, workspace identity, and the exact adapter snapshot. If a non-secret runtime value also affects reuse, set `DEVROUTER_PROCESS_FINGERPRINT_ENV` to its comma-separated environment names; secret-like names are rejected and raw values are never persisted.",
|
|
6884
7113
|
"- Managed adapters may use devrouter-process ensure --prepare-command <command> for synchronous dependency preparation under the process lock before app launch. Unchanged owned processes skip preparation; it participates in default fingerprints and must not daemonize or detach.",
|
|
6885
7114
|
"- Host-generated Compose inputs may use managedRuntime.devcontainer.prepareCommand as literal argv. Ensure runs it once from the checkout root before Compose inspection, with a 60-second bound and no implicit shell. Keep .devrouter.yml unchanged. Diagnostics never run it; changed warm mounts require separate verification.",
|
|
6886
|
-
"-
|
|
7115
|
+
"- Managed Devsy startup on supported local Unix Docker endpoints captures exact stop ownership before application readiness. With that baseline, canonical stop tolerates changed or missing repository configuration and preserves containers and volumes. Older records without a baseline retain configuration-dependent stop; installing a new CLI does not establish historical ownership. Invalid or changed ownership never falls back. Do not replay destructive bootstrap hooks to obtain a baseline.",
|
|
6887
7116
|
"- Owner status is `present`, `missing`, `locked`, or `conflict`. Dirty or locked full down fails before side effects. `workspace gc` is a dry run; only `--yes` deletes exact eligible missing resources and records, never Git worktrees, branches, or prune state.",
|
|
6888
7117
|
"- `devrouter workspace cleanup --repo . --inactive-for 30d --check-merged --json` is report-only and never mutates the workspace runtime, routes, ownership, Git, Docker, applications, worktrees, or branches. It reports ownership, workspace runtime registration, runtime state (`running|stopped|busy|not-found|absent|unknown`), checkout, advisory activity, route, and integration evidence. Local DevPod/Devsy list/status checks always run; `--check-merged` alone enables read-only origin and matching GitHub/GitLab checks. Treat `not-found` as stale runtime after Docker pruning; busy, unavailable, or conflicting evidence suppresses destructive suggestions. Explicit `gc`/`down` can remove exact stale registration only after expected-ID `NotFound` proof and ownership revalidation. Runtime `lastUsed` remains advisory.",
|
|
6889
7118
|
'- `--measure-size` adds per-workspace storage consumption and stays read-only, but walks each worktree and runs `docker ps` / `docker inspect --size`, so leave it off for a quick report. Each row reports `worktree` and `containerWritable` bytes, which are reclaimable; `imageShared` bytes, which are not, because those image layers are shared with other containers and overlap across rows; and `reclaimable`, the sum of the reclaimable fields. Never add `imageShared` across rows. Any figure that cannot be trusted reports `{"status": "unknown", "reason": ...}` rather than zero, and a workspace with no container reports a measured `0`.',
|
|
@@ -7618,7 +7847,7 @@ function readPromptDirectory(promptDirPath) {
|
|
|
7618
7847
|
continue;
|
|
7619
7848
|
}
|
|
7620
7849
|
const version = normalizeVersion(basename);
|
|
7621
|
-
const promptPath =
|
|
7850
|
+
const promptPath = import_node_path23.default.join(promptDirPath, file.name);
|
|
7622
7851
|
const prompt = import_node_fs17.default.readFileSync(promptPath, "utf-8").trim();
|
|
7623
7852
|
releases.push({ version, prompt, promptPath });
|
|
7624
7853
|
}
|
|
@@ -7640,17 +7869,17 @@ function listAvailableUpgradeTargets(currentVersion, releases) {
|
|
|
7640
7869
|
}
|
|
7641
7870
|
function resolvePromptDirectory(explicitPath) {
|
|
7642
7871
|
if (explicitPath) {
|
|
7643
|
-
return
|
|
7872
|
+
return import_node_path23.default.resolve(explicitPath);
|
|
7644
7873
|
}
|
|
7645
|
-
const entryFile = process.argv[1] ?
|
|
7646
|
-
const entryDir =
|
|
7647
|
-
const resolvedEntryDir = import_node_fs17.default.existsSync(entryFile) ?
|
|
7874
|
+
const entryFile = process.argv[1] ? import_node_path23.default.resolve(process.argv[1]) : __filename;
|
|
7875
|
+
const entryDir = import_node_path23.default.dirname(entryFile);
|
|
7876
|
+
const resolvedEntryDir = import_node_fs17.default.existsSync(entryFile) ? import_node_path23.default.dirname(import_node_fs17.default.realpathSync(entryFile)) : entryDir;
|
|
7648
7877
|
const candidates = [
|
|
7649
|
-
|
|
7650
|
-
|
|
7651
|
-
|
|
7652
|
-
|
|
7653
|
-
|
|
7878
|
+
import_node_path23.default.resolve(resolvedEntryDir, "..", UPGRADE_PROMPTS_DIR),
|
|
7879
|
+
import_node_path23.default.resolve(entryDir, "..", UPGRADE_PROMPTS_DIR),
|
|
7880
|
+
import_node_path23.default.resolve(__dirname, "..", UPGRADE_PROMPTS_DIR),
|
|
7881
|
+
import_node_path23.default.resolve(__dirname, "..", "..", UPGRADE_PROMPTS_DIR),
|
|
7882
|
+
import_node_path23.default.resolve(process.cwd(), UPGRADE_PROMPTS_DIR)
|
|
7654
7883
|
];
|
|
7655
7884
|
const existing = candidates.find((candidate) => import_node_fs17.default.existsSync(candidate));
|
|
7656
7885
|
return existing ?? candidates[0];
|
|
@@ -7668,12 +7897,12 @@ function loadUpgradeCatalog(options = {}) {
|
|
|
7668
7897
|
releases
|
|
7669
7898
|
};
|
|
7670
7899
|
}
|
|
7671
|
-
var import_node_fs17,
|
|
7900
|
+
var import_node_fs17, import_node_path23, SEMVER_RE, UPGRADE_PROMPTS_DIR;
|
|
7672
7901
|
var init_upgrade = __esm({
|
|
7673
7902
|
"src/core/upgrade.ts"() {
|
|
7674
7903
|
"use strict";
|
|
7675
7904
|
import_node_fs17 = __toESM(require("fs"));
|
|
7676
|
-
|
|
7905
|
+
import_node_path23 = __toESM(require("path"));
|
|
7677
7906
|
init_repo_config();
|
|
7678
7907
|
SEMVER_RE = /^v?(\d+)\.(\d+)\.(\d+)$/;
|
|
7679
7908
|
UPGRADE_PROMPTS_DIR = "upgrade-prompts";
|
|
@@ -7786,7 +8015,7 @@ function nativeAssetName(platform, arch) {
|
|
|
7786
8015
|
return void 0;
|
|
7787
8016
|
}
|
|
7788
8017
|
function managedBinaryPath(cacheRoot, asset) {
|
|
7789
|
-
return
|
|
8018
|
+
return import_node_path24.default.join(cacheRoot, `v${SUPPORTED_DEVSY_VERSION}`, asset.name);
|
|
7790
8019
|
}
|
|
7791
8020
|
function hashOpenFile(file) {
|
|
7792
8021
|
const digest3 = (0, import_node_crypto14.createHash)("sha256");
|
|
@@ -7828,7 +8057,7 @@ function resolvedOptions(options) {
|
|
|
7828
8057
|
platform: options.platform ?? process.platform,
|
|
7829
8058
|
arch: options.arch ?? process.arch,
|
|
7830
8059
|
versionOutput: options.versionOutput ?? installedVersionOutput(),
|
|
7831
|
-
cacheRoot: options.cacheRoot ??
|
|
8060
|
+
cacheRoot: options.cacheRoot ?? import_node_path24.default.join(CACHE_DIR, "devsy", "agents"),
|
|
7832
8061
|
lockPath: options.lockPath ?? DEVSY_AGENT_CACHE_LOCK,
|
|
7833
8062
|
assets: options.assets ?? DEVSY_AGENT_ASSETS,
|
|
7834
8063
|
nativeAssetName: options.nativeAssetName
|
|
@@ -7953,11 +8182,11 @@ function fsyncDirectory2(directory) {
|
|
|
7953
8182
|
}
|
|
7954
8183
|
}
|
|
7955
8184
|
async function downloadAndPublish(asset, binaryPath, fetcher, githubCliDownloader) {
|
|
7956
|
-
const directory =
|
|
8185
|
+
const directory = import_node_path24.default.dirname(binaryPath);
|
|
7957
8186
|
import_node_fs18.default.mkdirSync(directory, { recursive: true, mode: 448 });
|
|
7958
|
-
const temporaryPath =
|
|
8187
|
+
const temporaryPath = import_node_path24.default.join(
|
|
7959
8188
|
directory,
|
|
7960
|
-
`.${
|
|
8189
|
+
`.${import_node_path24.default.basename(binaryPath)}.${process.pid}.${(0, import_node_crypto14.randomUUID)()}.tmp`
|
|
7961
8190
|
);
|
|
7962
8191
|
let handle;
|
|
7963
8192
|
try {
|
|
@@ -8041,7 +8270,7 @@ async function prepareDevsyAgent(options = {}) {
|
|
|
8041
8270
|
throw new DevsyAgentReadinessError(before);
|
|
8042
8271
|
}
|
|
8043
8272
|
const lock = options.withLock ?? ((operation) => {
|
|
8044
|
-
import_node_fs18.default.mkdirSync(
|
|
8273
|
+
import_node_fs18.default.mkdirSync(import_node_path24.default.dirname(resolved.lockPath), { recursive: true });
|
|
8045
8274
|
return withFileLock(
|
|
8046
8275
|
resolved.lockPath,
|
|
8047
8276
|
{
|
|
@@ -8086,14 +8315,14 @@ async function prepareDevsyAgent(options = {}) {
|
|
|
8086
8315
|
};
|
|
8087
8316
|
});
|
|
8088
8317
|
}
|
|
8089
|
-
var import_node_child_process10, import_node_crypto14, import_node_fs18,
|
|
8318
|
+
var import_node_child_process10, import_node_crypto14, import_node_fs18, import_node_path24, SUPPORTED_DEVSY_VERSION, DEVSY_AGENT_SETUP_COMMAND, DEVSY_AGENT_ASSETS, DevsyAgentReadinessError, DEVSY_AGENT_CACHE_LOCK, DEVSY_AGENT_LOCK_WAIT_MS, GITHUB_CLI_STDERR_TAIL_BYTES;
|
|
8090
8319
|
var init_devsy_agent = __esm({
|
|
8091
8320
|
"src/core/devsy-agent.ts"() {
|
|
8092
8321
|
"use strict";
|
|
8093
8322
|
import_node_child_process10 = require("child_process");
|
|
8094
8323
|
import_node_crypto14 = require("crypto");
|
|
8095
8324
|
import_node_fs18 = __toESM(require("fs"));
|
|
8096
|
-
|
|
8325
|
+
import_node_path24 = __toESM(require("path"));
|
|
8097
8326
|
init_file_lock();
|
|
8098
8327
|
init_router();
|
|
8099
8328
|
SUPPORTED_DEVSY_VERSION = "1.16.2";
|
|
@@ -8121,7 +8350,7 @@ var init_devsy_agent = __esm({
|
|
|
8121
8350
|
this.name = "DevsyAgentReadinessError";
|
|
8122
8351
|
}
|
|
8123
8352
|
};
|
|
8124
|
-
DEVSY_AGENT_CACHE_LOCK =
|
|
8353
|
+
DEVSY_AGENT_CACHE_LOCK = import_node_path24.default.join(DEVROUTER_HOME, "devsy-agent-cache.lock");
|
|
8125
8354
|
DEVSY_AGENT_LOCK_WAIT_MS = 18e5;
|
|
8126
8355
|
GITHUB_CLI_STDERR_TAIL_BYTES = 4096;
|
|
8127
8356
|
}
|
|
@@ -8139,11 +8368,11 @@ function adapterFingerprint(adapter) {
|
|
|
8139
8368
|
return (0, import_node_crypto15.createHash)("sha256").update(adapter).digest("hex");
|
|
8140
8369
|
}
|
|
8141
8370
|
function resolveManagedPostStartPlan(repoPath) {
|
|
8142
|
-
const adapterPath =
|
|
8371
|
+
const adapterPath = import_node_path25.default.join(repoPath, MANAGED_ADAPTER_PATH);
|
|
8143
8372
|
const adapterBytes = readRegularFileBytes(adapterPath);
|
|
8144
8373
|
const adapterText = adapterBytes?.toString("utf-8");
|
|
8145
|
-
const dockerfilePath =
|
|
8146
|
-
const devcontainerPath =
|
|
8374
|
+
const dockerfilePath = import_node_path25.default.join(repoPath, ".devcontainer", "Dockerfile");
|
|
8375
|
+
const devcontainerPath = import_node_path25.default.join(repoPath, ".devcontainer", "devcontainer.json");
|
|
8147
8376
|
const dockerfile = readRegularFile(dockerfilePath) ?? "";
|
|
8148
8377
|
const devcontainer = readRegularFile(devcontainerPath) ?? "";
|
|
8149
8378
|
const devrouterPattern = [adapterText, dockerfile, devcontainer].filter((value) => value !== void 0).some(
|
|
@@ -8225,14 +8454,14 @@ function runManagedProcessAction(options) {
|
|
|
8225
8454
|
}
|
|
8226
8455
|
return "drifted";
|
|
8227
8456
|
}
|
|
8228
|
-
var import_node_child_process11, import_node_crypto15, import_node_fs19,
|
|
8457
|
+
var import_node_child_process11, import_node_crypto15, import_node_fs19, import_node_path25, MANAGED_MARKER, MANAGED_ADAPTER_PATH, RUNTIME_HELPER_PATH;
|
|
8229
8458
|
var init_managed_post_start = __esm({
|
|
8230
8459
|
"src/core/managed-post-start.ts"() {
|
|
8231
8460
|
"use strict";
|
|
8232
8461
|
import_node_child_process11 = require("child_process");
|
|
8233
8462
|
import_node_crypto15 = require("crypto");
|
|
8234
8463
|
import_node_fs19 = __toESM(require("fs"));
|
|
8235
|
-
|
|
8464
|
+
import_node_path25 = __toESM(require("path"));
|
|
8236
8465
|
init_devcontainer_config();
|
|
8237
8466
|
MANAGED_MARKER = "devrouter:managed devcontainer";
|
|
8238
8467
|
MANAGED_ADAPTER_PATH = ".devcontainer/post-start.sh";
|
|
@@ -8272,19 +8501,19 @@ function formatPublishedPort(serviceName, value) {
|
|
|
8272
8501
|
if (typeof value === "string") {
|
|
8273
8502
|
return `${serviceName}: ${value}`;
|
|
8274
8503
|
}
|
|
8275
|
-
const
|
|
8276
|
-
if (!
|
|
8504
|
+
const record4 = asRecord(value);
|
|
8505
|
+
if (!record4) {
|
|
8277
8506
|
return void 0;
|
|
8278
8507
|
}
|
|
8279
|
-
const target =
|
|
8280
|
-
const published =
|
|
8508
|
+
const target = record4.target;
|
|
8509
|
+
const published = record4.published;
|
|
8281
8510
|
if (published !== void 0 && target !== void 0) {
|
|
8282
8511
|
return `${serviceName}: ${String(published)}:${String(target)}`;
|
|
8283
8512
|
}
|
|
8284
8513
|
if (target !== void 0) {
|
|
8285
8514
|
return `${serviceName}: target ${String(target)}`;
|
|
8286
8515
|
}
|
|
8287
|
-
return `${serviceName}: ${JSON.stringify(
|
|
8516
|
+
return `${serviceName}: ${JSON.stringify(record4)}`;
|
|
8288
8517
|
}
|
|
8289
8518
|
function upstreamHost(upstream) {
|
|
8290
8519
|
const separator = upstream.lastIndexOf(":");
|
|
@@ -8360,11 +8589,11 @@ function routedProxyApps(config) {
|
|
|
8360
8589
|
);
|
|
8361
8590
|
}
|
|
8362
8591
|
function buildDevcontainerChecks(repoPath, config, workspace) {
|
|
8363
|
-
const devcontainerDir =
|
|
8592
|
+
const devcontainerDir = import_node_path26.default.join(repoPath, ".devcontainer");
|
|
8364
8593
|
if (!import_node_fs20.default.existsSync(devcontainerDir)) {
|
|
8365
8594
|
return [];
|
|
8366
8595
|
}
|
|
8367
|
-
const compose = inspectCompose(
|
|
8596
|
+
const compose = inspectCompose(import_node_path26.default.join(devcontainerDir, "docker-compose.yml"), workspace);
|
|
8368
8597
|
const checks = [];
|
|
8369
8598
|
if (compose.parseError) {
|
|
8370
8599
|
checks.push({
|
|
@@ -8391,7 +8620,7 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
|
|
|
8391
8620
|
details: compose.publishedPorts.length > 0 ? compose.publishedPorts.join(", ") : void 0,
|
|
8392
8621
|
suggestion: compose.parseError || compose.publishedPorts.length > 0 ? "Remove published ports and route services through devnet aliases with devrouter proxy apps." : void 0
|
|
8393
8622
|
});
|
|
8394
|
-
const dockerfilePath =
|
|
8623
|
+
const dockerfilePath = import_node_path26.default.join(devcontainerDir, "Dockerfile");
|
|
8395
8624
|
const dockerfileExists = import_node_fs20.default.existsSync(dockerfilePath);
|
|
8396
8625
|
const dockerfile = dockerfileExists ? import_node_fs20.default.readFileSync(dockerfilePath, "utf-8") : "";
|
|
8397
8626
|
const devrouterArtifacts = ["@devrouter/cli", "devrouter-process"].filter(
|
|
@@ -8448,12 +8677,12 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
|
|
|
8448
8677
|
});
|
|
8449
8678
|
return checks;
|
|
8450
8679
|
}
|
|
8451
|
-
var import_node_fs20,
|
|
8680
|
+
var import_node_fs20, import_node_path26, import_yaml4;
|
|
8452
8681
|
var init_devcontainer_diagnostics = __esm({
|
|
8453
8682
|
"src/core/devcontainer-diagnostics.ts"() {
|
|
8454
8683
|
"use strict";
|
|
8455
8684
|
import_node_fs20 = __toESM(require("fs"));
|
|
8456
|
-
|
|
8685
|
+
import_node_path26 = __toESM(require("path"));
|
|
8457
8686
|
import_yaml4 = __toESM(require("yaml"));
|
|
8458
8687
|
init_managed_post_start();
|
|
8459
8688
|
}
|
|
@@ -9393,9 +9622,16 @@ function listDevsyWorkspaces() {
|
|
|
9393
9622
|
if (typeof candidate.id !== "string" || !source || typeof source.localFolder !== "string") {
|
|
9394
9623
|
throw new Error("devsy workspace list returned a workspace without id/source.localFolder.");
|
|
9395
9624
|
}
|
|
9625
|
+
if (candidate.uid !== void 0 && typeof candidate.uid !== "string" || source.container !== void 0 && typeof source.container !== "string") {
|
|
9626
|
+
throw new Error("Devsy workspace list returned invalid provider identity.");
|
|
9627
|
+
}
|
|
9396
9628
|
const workspace = {
|
|
9397
9629
|
id: candidate.id,
|
|
9398
|
-
source: {
|
|
9630
|
+
source: {
|
|
9631
|
+
localFolder: source.localFolder,
|
|
9632
|
+
...typeof source.container === "string" ? { container: source.container } : {}
|
|
9633
|
+
},
|
|
9634
|
+
...typeof candidate.uid === "string" ? { uid: candidate.uid } : {},
|
|
9399
9635
|
...typeof candidate.context === "string" ? { context: candidate.context } : {}
|
|
9400
9636
|
};
|
|
9401
9637
|
if ("lastUsed" in candidate) {
|
|
@@ -9662,20 +9898,20 @@ function resetWorkspaceRuntimeCaches() {
|
|
|
9662
9898
|
cachedRuntime = void 0;
|
|
9663
9899
|
cachedSnapshots = void 0;
|
|
9664
9900
|
}
|
|
9665
|
-
var import_node_child_process14, import_node_fs23,
|
|
9901
|
+
var import_node_child_process14, import_node_fs23, import_node_path27, SUPPORTED_RUNTIMES2, RUNTIME_CONFIG_FILE, INACTIVITY_TIMEOUT_PATTERN, cachedRuntime, cachedSnapshots, UnsupportedWorkspaceRuntimeError, WorkspaceRuntimeOwnershipError;
|
|
9666
9902
|
var init_workspace_runtime = __esm({
|
|
9667
9903
|
"src/core/workspace-runtime.ts"() {
|
|
9668
9904
|
"use strict";
|
|
9669
9905
|
import_node_child_process14 = require("child_process");
|
|
9670
9906
|
import_node_fs23 = __toESM(require("fs"));
|
|
9671
|
-
|
|
9907
|
+
import_node_path27 = __toESM(require("path"));
|
|
9672
9908
|
init_atomic_file();
|
|
9673
9909
|
init_devpod_registry();
|
|
9674
9910
|
init_devsy_workspaces();
|
|
9675
9911
|
init_router();
|
|
9676
9912
|
init_workspace();
|
|
9677
9913
|
SUPPORTED_RUNTIMES2 = ["devpod", "devsy"];
|
|
9678
|
-
RUNTIME_CONFIG_FILE =
|
|
9914
|
+
RUNTIME_CONFIG_FILE = import_node_path27.default.join(DEVROUTER_HOME, "workspace-runtime.json");
|
|
9679
9915
|
INACTIVITY_TIMEOUT_PATTERN = /^(?:\d+(?:ms|s|m|h))+$/;
|
|
9680
9916
|
UnsupportedWorkspaceRuntimeError = class extends Error {
|
|
9681
9917
|
};
|
|
@@ -9753,7 +9989,7 @@ function parseMinimumNodeMajor(value) {
|
|
|
9753
9989
|
return Number(match[1]);
|
|
9754
9990
|
}
|
|
9755
9991
|
function readPackageJson(repoPath) {
|
|
9756
|
-
const packagePath =
|
|
9992
|
+
const packagePath = import_node_path28.default.join(repoPath, "package.json");
|
|
9757
9993
|
if (!import_node_fs24.default.existsSync(packagePath)) {
|
|
9758
9994
|
return void 0;
|
|
9759
9995
|
}
|
|
@@ -9942,13 +10178,13 @@ function buildGlobalToolChecksWithoutTelemetry(repoPath) {
|
|
|
9942
10178
|
checks.push(nodeToolchainCheck(repoPath));
|
|
9943
10179
|
return checks;
|
|
9944
10180
|
}
|
|
9945
|
-
var import_node_child_process15, import_node_fs24,
|
|
10181
|
+
var import_node_child_process15, import_node_fs24, import_node_path28, DEVSY_DISABLE_TELEMETRY;
|
|
9946
10182
|
var init_tool_diagnostics = __esm({
|
|
9947
10183
|
"src/core/tool-diagnostics.ts"() {
|
|
9948
10184
|
"use strict";
|
|
9949
10185
|
import_node_child_process15 = require("child_process");
|
|
9950
10186
|
import_node_fs24 = __toESM(require("fs"));
|
|
9951
|
-
|
|
10187
|
+
import_node_path28 = __toESM(require("path"));
|
|
9952
10188
|
init_devsy_agent();
|
|
9953
10189
|
init_workspace_runtime();
|
|
9954
10190
|
DEVSY_DISABLE_TELEMETRY = "DEVSY_DISABLE_TELEMETRY";
|
|
@@ -10054,6 +10290,54 @@ var init_devpod_workspaces = __esm({
|
|
|
10054
10290
|
}
|
|
10055
10291
|
});
|
|
10056
10292
|
|
|
10293
|
+
// src/core/managed-compose-population.ts
|
|
10294
|
+
function proveManagedComposePopulation(options) {
|
|
10295
|
+
const { plan, repoPath, composeProject, featureDirectory, devsyRoot, containers: containers2 } = options;
|
|
10296
|
+
const services = /* @__PURE__ */ new Set();
|
|
10297
|
+
for (const container of containers2) {
|
|
10298
|
+
const service = container.labels["com.docker.compose.service"] ?? "";
|
|
10299
|
+
if (!plan.nativeRunServices.includes(service) || services.has(service) || container.labels["com.docker.compose.project"] !== composeProject || !sameWorkspacePath(
|
|
10300
|
+
container.labels["com.docker.compose.project.working_dir"] ?? "",
|
|
10301
|
+
plan.composeDirectory
|
|
10302
|
+
)) {
|
|
10303
|
+
throw new Error("Managed stop found unexpected or duplicate project membership.");
|
|
10304
|
+
}
|
|
10305
|
+
services.add(service);
|
|
10306
|
+
const files = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").map((file) => file.trim());
|
|
10307
|
+
if (files.some((file) => !file || !import_node_path29.default.isAbsolute(file)) || new Set(files.map((file) => import_node_path29.default.resolve(file))).size !== files.length || files.length < plan.composeFiles.length || plan.composeFiles.some((file, index) => !sameWorkspacePath(file, files[index]))) {
|
|
10308
|
+
throw new Error("Managed stop Compose file identity changed.");
|
|
10309
|
+
}
|
|
10310
|
+
for (const file of files.slice(plan.composeFiles.length)) {
|
|
10311
|
+
if (import_node_path29.default.dirname(file) !== featureDirectory || !/^docker-compose\.devcontainer\.containerFeatures-[a-zA-Z0-9_-]+\.yml$/.test(
|
|
10312
|
+
import_node_path29.default.basename(file)
|
|
10313
|
+
) || import_node_fs25.default.realpathSync(file) !== import_node_path29.default.join(import_node_fs25.default.realpathSync(devsyRoot), import_node_path29.default.relative(devsyRoot, file))) {
|
|
10314
|
+
throw new Error("Managed stop refuses a foreign or escaped provider Compose file.");
|
|
10315
|
+
}
|
|
10316
|
+
}
|
|
10317
|
+
}
|
|
10318
|
+
if (plan.desiredServices.some((service) => !services.has(service))) {
|
|
10319
|
+
throw new Error("Managed stop cannot prove the complete retained service population.");
|
|
10320
|
+
}
|
|
10321
|
+
const primary = containers2.find(
|
|
10322
|
+
(c) => c.labels["com.docker.compose.service"] === plan.primaryService
|
|
10323
|
+
);
|
|
10324
|
+
if (primary?.mounts.filter(
|
|
10325
|
+
(mount) => mount.Type === "bind" && sameWorkspacePath(mount.Source, repoPath)
|
|
10326
|
+
).length !== 1) {
|
|
10327
|
+
throw new Error("Managed stop cannot prove the exact primary workspace mount.");
|
|
10328
|
+
}
|
|
10329
|
+
return primary;
|
|
10330
|
+
}
|
|
10331
|
+
var import_node_fs25, import_node_path29;
|
|
10332
|
+
var init_managed_compose_population = __esm({
|
|
10333
|
+
"src/core/managed-compose-population.ts"() {
|
|
10334
|
+
"use strict";
|
|
10335
|
+
import_node_fs25 = __toESM(require("fs"));
|
|
10336
|
+
import_node_path29 = __toESM(require("path"));
|
|
10337
|
+
init_workspace();
|
|
10338
|
+
}
|
|
10339
|
+
});
|
|
10340
|
+
|
|
10057
10341
|
// src/core/reliability-context.ts
|
|
10058
10342
|
function claimLifecycleEffect() {
|
|
10059
10343
|
claim?.();
|
|
@@ -10065,81 +10349,6 @@ var init_reliability_context = __esm({
|
|
|
10065
10349
|
}
|
|
10066
10350
|
});
|
|
10067
10351
|
|
|
10068
|
-
// src/core/route-publication.ts
|
|
10069
|
-
function routedAppsFromConfig(config) {
|
|
10070
|
-
return config.apps.filter((app) => app.kind !== "dependency");
|
|
10071
|
-
}
|
|
10072
|
-
function configuredProxyAppsFromConfig(config) {
|
|
10073
|
-
return routedAppsFromConfig(config).filter(
|
|
10074
|
-
(app) => app.runtime === "proxy"
|
|
10075
|
-
);
|
|
10076
|
-
}
|
|
10077
|
-
function proxyAppsFromConfig(config) {
|
|
10078
|
-
const routedApps = routedAppsFromConfig(config);
|
|
10079
|
-
const proxyApps = configuredProxyAppsFromConfig(config);
|
|
10080
|
-
const unsupported = routedApps.filter((app) => app.runtime !== "proxy");
|
|
10081
|
-
if (unsupported.length > 0) {
|
|
10082
|
-
throw new Error(
|
|
10083
|
-
`Environment reconciliation supports proxy runtime apps only; unsupported: ${unsupported.map((app) => `${app.name} (${app.runtime})`).join(", ")}`
|
|
10084
|
-
);
|
|
10085
|
-
}
|
|
10086
|
-
return proxyApps;
|
|
10087
|
-
}
|
|
10088
|
-
async function ensureRouteInfrastructure(apps, options = {}) {
|
|
10089
|
-
claimLifecycleEffect();
|
|
10090
|
-
ensureRouterFiles();
|
|
10091
|
-
await ensureNetwork(DEVNET_NAME);
|
|
10092
|
-
claimLifecycleEffect();
|
|
10093
|
-
const tlsCoverage = await ensureTLSHostsCovered(
|
|
10094
|
-
apps.map((app) => app.host),
|
|
10095
|
-
options
|
|
10096
|
-
);
|
|
10097
|
-
for (const app of apps) {
|
|
10098
|
-
if (app.protocol === "tcp") {
|
|
10099
|
-
if (!isTLSEnabled()) {
|
|
10100
|
-
throw new Error(
|
|
10101
|
-
`TCP route '${app.name}' requires TLS. Run: ${tlsSetupCommand(options.repoPath)}`
|
|
10102
|
-
);
|
|
10103
|
-
}
|
|
10104
|
-
activateTcpProtocol(app.tcpProtocol);
|
|
10105
|
-
}
|
|
10106
|
-
}
|
|
10107
|
-
claimLifecycleEffect();
|
|
10108
|
-
startRouterStack();
|
|
10109
|
-
return tlsCoverage;
|
|
10110
|
-
}
|
|
10111
|
-
async function replacePublishedProxyRoutes(repoPath, config, workspace, options = {}) {
|
|
10112
|
-
const apps = proxyAppsFromConfig(config);
|
|
10113
|
-
const tlsCoverage = options.prepareInfrastructure === false ? { refreshed: false } : await ensureRouteInfrastructure(apps, { repoPath });
|
|
10114
|
-
const routes = apps.map((app) => {
|
|
10115
|
-
const upstream = parseUpstream(app.upstream);
|
|
10116
|
-
return {
|
|
10117
|
-
name: app.name,
|
|
10118
|
-
host: app.host,
|
|
10119
|
-
protocol: app.protocol,
|
|
10120
|
-
tcpProtocol: app.protocol === "tcp" ? app.tcpProtocol : void 0,
|
|
10121
|
-
repoPath,
|
|
10122
|
-
port: upstream.port,
|
|
10123
|
-
mode: "proxy",
|
|
10124
|
-
upstreamHost: upstream.upstreamHost,
|
|
10125
|
-
workspace
|
|
10126
|
-
};
|
|
10127
|
-
});
|
|
10128
|
-
claimLifecycleEffect();
|
|
10129
|
-
replaceHostRoutesForRepo(repoPath, routes);
|
|
10130
|
-
return { routes, tlsRefreshed: tlsCoverage.refreshed };
|
|
10131
|
-
}
|
|
10132
|
-
var init_route_publication = __esm({
|
|
10133
|
-
"src/core/route-publication.ts"() {
|
|
10134
|
-
"use strict";
|
|
10135
|
-
init_docker();
|
|
10136
|
-
init_host_routes();
|
|
10137
|
-
init_reliability_context();
|
|
10138
|
-
init_router();
|
|
10139
|
-
init_tls();
|
|
10140
|
-
}
|
|
10141
|
-
});
|
|
10142
|
-
|
|
10143
10352
|
// src/core/workspace-ownership.ts
|
|
10144
10353
|
function commandError(command, repoPath, stderr) {
|
|
10145
10354
|
return new Error(
|
|
@@ -10155,7 +10364,7 @@ function resolveGitCommonDir(repoPath) {
|
|
|
10155
10364
|
if (result.status !== 0 || !output2) {
|
|
10156
10365
|
throw commandError("Could not resolve the Git common directory", repoPath, result.stderr);
|
|
10157
10366
|
}
|
|
10158
|
-
return comparableWorkspacePath(
|
|
10367
|
+
return comparableWorkspacePath(import_node_path30.default.isAbsolute(output2) ? output2 : import_node_path30.default.resolve(repoPath, output2));
|
|
10159
10368
|
}
|
|
10160
10369
|
function resolveGitTopLevel(repoPath) {
|
|
10161
10370
|
const result = (0, import_node_child_process17.spawnSync)("git", ["-C", repoPath, "rev-parse", "--show-toplevel"], {
|
|
@@ -10206,7 +10415,7 @@ function listGitWorktrees(repoPath) {
|
|
|
10206
10415
|
return worktrees;
|
|
10207
10416
|
}
|
|
10208
10417
|
function ownershipDirectory(repoPath) {
|
|
10209
|
-
return
|
|
10418
|
+
return import_node_path30.default.join(resolveGitCommonDir(repoPath), OWNERSHIP_DIR);
|
|
10210
10419
|
}
|
|
10211
10420
|
function validateWorkspace(value, label) {
|
|
10212
10421
|
if (typeof value !== "string" || wsFromBranch(value) !== value) {
|
|
@@ -10234,7 +10443,7 @@ function validateRecord(value, expectedWorkspace) {
|
|
|
10234
10443
|
`workspace ownership file '${expectedWorkspace}' contains identity '${workspace}'`
|
|
10235
10444
|
);
|
|
10236
10445
|
}
|
|
10237
|
-
if (typeof candidate.worktreePath !== "string" || !
|
|
10446
|
+
if (typeof candidate.worktreePath !== "string" || !import_node_path30.default.isAbsolute(candidate.worktreePath)) {
|
|
10238
10447
|
throw new Error("invalid workspace ownership worktreePath");
|
|
10239
10448
|
}
|
|
10240
10449
|
if (candidate.branch !== null && typeof candidate.branch !== "string") {
|
|
@@ -10252,7 +10461,7 @@ function validateRecord(value, expectedWorkspace) {
|
|
|
10252
10461
|
};
|
|
10253
10462
|
}
|
|
10254
10463
|
function recordPath(repoPath, workspace) {
|
|
10255
|
-
return
|
|
10464
|
+
return import_node_path30.default.join(
|
|
10256
10465
|
ownershipDirectory(repoPath),
|
|
10257
10466
|
`${validateWorkspace(workspace, "workspace")}.json`
|
|
10258
10467
|
);
|
|
@@ -10260,7 +10469,7 @@ function recordPath(repoPath, workspace) {
|
|
|
10260
10469
|
function readRecordFile(filePath, expectedWorkspace) {
|
|
10261
10470
|
let parsed;
|
|
10262
10471
|
try {
|
|
10263
|
-
parsed = JSON.parse(
|
|
10472
|
+
parsed = JSON.parse(import_node_fs26.default.readFileSync(filePath, "utf-8"));
|
|
10264
10473
|
} catch (error) {
|
|
10265
10474
|
if (error instanceof SyntaxError) {
|
|
10266
10475
|
throw new Error(`invalid workspace ownership JSON at '${filePath}'`);
|
|
@@ -10285,7 +10494,7 @@ function listWorkspaceOwnership(repoPath) {
|
|
|
10285
10494
|
function listWorkspaceOwnershipInDirectory(directory) {
|
|
10286
10495
|
let entries;
|
|
10287
10496
|
try {
|
|
10288
|
-
entries =
|
|
10497
|
+
entries = import_node_fs26.default.readdirSync(directory, { withFileTypes: true });
|
|
10289
10498
|
} catch (error) {
|
|
10290
10499
|
if (error.code === "ENOENT") return [];
|
|
10291
10500
|
throw error;
|
|
@@ -10293,24 +10502,24 @@ function listWorkspaceOwnershipInDirectory(directory) {
|
|
|
10293
10502
|
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).sort((left, right) => left.name.localeCompare(right.name)).map((entry) => {
|
|
10294
10503
|
const workspace = entry.name.slice(0, -".json".length);
|
|
10295
10504
|
validateWorkspace(workspace, "filename");
|
|
10296
|
-
return readRecordFile(
|
|
10505
|
+
return readRecordFile(import_node_path30.default.join(directory, entry.name), workspace);
|
|
10297
10506
|
});
|
|
10298
10507
|
}
|
|
10299
10508
|
function writeWorkspaceOwnershipInDirectory(directory, input2) {
|
|
10300
10509
|
const workspace = validateWorkspace(input2.workspace, "workspace");
|
|
10301
10510
|
const devpodId = validateWorkspace(input2.devpodId, "devpodId");
|
|
10302
10511
|
const worktreePath = comparableWorkspacePath(input2.worktreePath);
|
|
10303
|
-
const filePath =
|
|
10512
|
+
const filePath = import_node_path30.default.join(directory, `${workspace}.json`);
|
|
10304
10513
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
10305
10514
|
const records = listWorkspaceOwnershipInDirectory(directory);
|
|
10306
|
-
const existing = records.find((
|
|
10515
|
+
const existing = records.find((record5) => record5.workspace === workspace);
|
|
10307
10516
|
if (existing && !sameWorkspacePath(existing.worktreePath, worktreePath)) {
|
|
10308
10517
|
throw new Error(
|
|
10309
10518
|
`Workspace '${workspace}' already belongs to '${existing.worktreePath}', refusing '${worktreePath}'.`
|
|
10310
10519
|
);
|
|
10311
10520
|
}
|
|
10312
10521
|
const pathOwner = records.find(
|
|
10313
|
-
(
|
|
10522
|
+
(record5) => record5.workspace !== workspace && sameWorkspacePath(record5.worktreePath, worktreePath)
|
|
10314
10523
|
);
|
|
10315
10524
|
if (pathOwner) {
|
|
10316
10525
|
throw new Error(
|
|
@@ -10322,7 +10531,7 @@ function writeWorkspaceOwnershipInDirectory(directory, input2) {
|
|
|
10322
10531
|
`Workspace '${workspace}' already owns DevPod '${existing.devpodId}', refusing '${devpodId}'.`
|
|
10323
10532
|
);
|
|
10324
10533
|
}
|
|
10325
|
-
const
|
|
10534
|
+
const record4 = {
|
|
10326
10535
|
version: OWNERSHIP_VERSION,
|
|
10327
10536
|
workspace,
|
|
10328
10537
|
worktreePath,
|
|
@@ -10331,14 +10540,14 @@ function writeWorkspaceOwnershipInDirectory(directory, input2) {
|
|
|
10331
10540
|
createdAt: existing?.createdAt ?? validateTimestamp(now, "createdAt"),
|
|
10332
10541
|
updatedAt: validateTimestamp(now, "updatedAt")
|
|
10333
10542
|
};
|
|
10334
|
-
writeFileAtomically(filePath, `${JSON.stringify(
|
|
10543
|
+
writeFileAtomically(filePath, `${JSON.stringify(record4, null, 2)}
|
|
10335
10544
|
`);
|
|
10336
|
-
return
|
|
10545
|
+
return record4;
|
|
10337
10546
|
}
|
|
10338
10547
|
function removeWorkspaceOwnershipInDirectory(directory, workspace) {
|
|
10339
|
-
const filePath =
|
|
10548
|
+
const filePath = import_node_path30.default.join(directory, `${validateWorkspace(workspace, "workspace")}.json`);
|
|
10340
10549
|
try {
|
|
10341
|
-
|
|
10550
|
+
import_node_fs26.default.rmSync(filePath);
|
|
10342
10551
|
return true;
|
|
10343
10552
|
} catch (error) {
|
|
10344
10553
|
if (error.code === "ENOENT") return false;
|
|
@@ -10349,7 +10558,7 @@ function sameOwnershipRecord(left, right) {
|
|
|
10349
10558
|
return left.version === right.version && left.workspace === right.workspace && sameWorkspacePath(left.worktreePath, right.worktreePath) && left.branch === right.branch && left.devpodId === right.devpodId && left.createdAt === right.createdAt && left.updatedAt === right.updatedAt;
|
|
10350
10559
|
}
|
|
10351
10560
|
function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
|
|
10352
|
-
const filePath =
|
|
10561
|
+
const filePath = import_node_path30.default.join(
|
|
10353
10562
|
directory,
|
|
10354
10563
|
`${validateWorkspace(expected.workspace, "workspace")}.json`
|
|
10355
10564
|
);
|
|
@@ -10361,14 +10570,14 @@ function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
|
|
|
10361
10570
|
throw error;
|
|
10362
10571
|
}
|
|
10363
10572
|
if (!sameOwnershipRecord(current, expected)) return "changed";
|
|
10364
|
-
|
|
10573
|
+
import_node_fs26.default.rmSync(filePath);
|
|
10365
10574
|
return "removed";
|
|
10366
10575
|
}
|
|
10367
10576
|
function withWorkspaceOwnershipTransaction(repoPath, operation, options = {}) {
|
|
10368
10577
|
const directory = ownershipDirectory(repoPath);
|
|
10369
|
-
|
|
10578
|
+
import_node_fs26.default.mkdirSync(directory, { recursive: true });
|
|
10370
10579
|
return withFileLockSync(
|
|
10371
|
-
|
|
10580
|
+
import_node_path30.default.join(directory, ".lock"),
|
|
10372
10581
|
{
|
|
10373
10582
|
activity: "workspace ownership transaction",
|
|
10374
10583
|
target: `'${repoPath}'`,
|
|
@@ -10401,7 +10610,7 @@ function persistedWorkspaceOwners(repoPath, worktreePath) {
|
|
|
10401
10610
|
try {
|
|
10402
10611
|
workspace = readPersistedWorkspace(worktree.path);
|
|
10403
10612
|
} catch (error) {
|
|
10404
|
-
if (!
|
|
10613
|
+
if (!import_node_fs26.default.existsSync(worktree.path)) continue;
|
|
10405
10614
|
throw error;
|
|
10406
10615
|
}
|
|
10407
10616
|
if (!workspace) continue;
|
|
@@ -10415,7 +10624,7 @@ function persistedWorkspaceOwners(repoPath, worktreePath) {
|
|
|
10415
10624
|
}
|
|
10416
10625
|
function claimConflict(workspace, devpodId, worktreePath, records, providerWorkspaces, persistedOwners, exactRecord) {
|
|
10417
10626
|
const recordOwner = records.find(
|
|
10418
|
-
(
|
|
10627
|
+
(record4) => record4 !== exactRecord && (record4.workspace === workspace || record4.devpodId === devpodId)
|
|
10419
10628
|
);
|
|
10420
10629
|
if (recordOwner) {
|
|
10421
10630
|
return `workspace owner record '${recordOwner.workspace}' for '${recordOwner.worktreePath}'`;
|
|
@@ -10438,7 +10647,7 @@ function claimWorkspaceIdentity(repoPath, input2) {
|
|
|
10438
10647
|
return withWorkspaceOwnershipTransaction(repoPath, (transaction) => {
|
|
10439
10648
|
const records = transaction.list();
|
|
10440
10649
|
const exactRecords = records.filter(
|
|
10441
|
-
(
|
|
10650
|
+
(record4) => sameWorkspacePath(record4.worktreePath, worktreePath)
|
|
10442
10651
|
);
|
|
10443
10652
|
if (exactRecords.length > 1) {
|
|
10444
10653
|
throw new Error(
|
|
@@ -10552,11 +10761,11 @@ function removeWorkspaceOwnership(repoPath, workspace) {
|
|
|
10552
10761
|
(transaction) => transaction.remove(workspace)
|
|
10553
10762
|
);
|
|
10554
10763
|
}
|
|
10555
|
-
function inspectWorkspaceOwnership(
|
|
10764
|
+
function inspectWorkspaceOwnership(record4, worktrees, devpods) {
|
|
10556
10765
|
const worktree = worktrees.find(
|
|
10557
|
-
(candidate) => sameWorkspacePath(candidate.path,
|
|
10766
|
+
(candidate) => sameWorkspacePath(candidate.path, record4.worktreePath)
|
|
10558
10767
|
);
|
|
10559
|
-
const devpodOwnership = devpods ? inspectDevpodWorkspaceOwnership(devpods,
|
|
10768
|
+
const devpodOwnership = devpods ? inspectDevpodWorkspaceOwnership(devpods, record4.devpodId, record4.worktreePath) : void 0;
|
|
10560
10769
|
const devpodStatus = devpodOwnership?.status ?? "unknown";
|
|
10561
10770
|
if (devpodStatus === "conflict") {
|
|
10562
10771
|
return { ownerStatus: "conflict", devpodStatus, worktree };
|
|
@@ -10564,7 +10773,7 @@ function inspectWorkspaceOwnership(record3, worktrees, devpods) {
|
|
|
10564
10773
|
if (worktree?.locked) {
|
|
10565
10774
|
return { ownerStatus: "locked", devpodStatus, worktree };
|
|
10566
10775
|
}
|
|
10567
|
-
if ((!worktree || worktree.prunable) &&
|
|
10776
|
+
if ((!worktree || worktree.prunable) && import_node_fs26.default.existsSync(record4.worktreePath)) {
|
|
10568
10777
|
return { ownerStatus: "conflict", devpodStatus, worktree };
|
|
10569
10778
|
}
|
|
10570
10779
|
if (!worktree || worktree.prunable) {
|
|
@@ -10577,7 +10786,7 @@ function inspectWorkspaceOwnership(record3, worktrees, devpods) {
|
|
|
10577
10786
|
return { ownerStatus: "conflict", devpodStatus, worktree };
|
|
10578
10787
|
}
|
|
10579
10788
|
return {
|
|
10580
|
-
ownerStatus: persisted ===
|
|
10789
|
+
ownerStatus: persisted === record4.workspace ? "present" : "conflict",
|
|
10581
10790
|
devpodStatus,
|
|
10582
10791
|
worktree
|
|
10583
10792
|
};
|
|
@@ -10585,23 +10794,231 @@ function inspectWorkspaceOwnership(record3, worktrees, devpods) {
|
|
|
10585
10794
|
function listMissingWorkspaceOwnership(repoPath) {
|
|
10586
10795
|
const worktrees = listGitWorktrees(repoPath);
|
|
10587
10796
|
return listWorkspaceOwnership(repoPath).filter(
|
|
10588
|
-
(
|
|
10797
|
+
(record4) => inspectWorkspaceOwnership(record4, worktrees, void 0).ownerStatus === "missing"
|
|
10589
10798
|
);
|
|
10590
10799
|
}
|
|
10591
|
-
var import_node_child_process17,
|
|
10800
|
+
var import_node_child_process17, import_node_fs26, import_node_path30, READ_ONLY_GIT_ENV, OWNERSHIP_VERSION, OWNERSHIP_DIR;
|
|
10592
10801
|
var init_workspace_ownership = __esm({
|
|
10593
10802
|
"src/core/workspace-ownership.ts"() {
|
|
10594
10803
|
"use strict";
|
|
10595
10804
|
import_node_child_process17 = require("child_process");
|
|
10596
|
-
|
|
10597
|
-
|
|
10805
|
+
import_node_fs26 = __toESM(require("fs"));
|
|
10806
|
+
import_node_path30 = __toESM(require("path"));
|
|
10598
10807
|
init_atomic_file();
|
|
10599
10808
|
init_devpod_workspaces();
|
|
10600
10809
|
init_file_lock();
|
|
10601
10810
|
init_workspace();
|
|
10602
10811
|
READ_ONLY_GIT_ENV = { ...process.env, GIT_OPTIONAL_LOCKS: "0" };
|
|
10603
10812
|
OWNERSHIP_VERSION = 1;
|
|
10604
|
-
OWNERSHIP_DIR =
|
|
10813
|
+
OWNERSHIP_DIR = import_node_path30.default.join("devrouter", "workspaces");
|
|
10814
|
+
}
|
|
10815
|
+
});
|
|
10816
|
+
|
|
10817
|
+
// src/core/managed-stop-recovery.ts
|
|
10818
|
+
function registration(state) {
|
|
10819
|
+
resetWorkspaceRuntimeCaches();
|
|
10820
|
+
if (resolveWorkspaceRuntimeOrDefault(state.repoPath) !== "devsy")
|
|
10821
|
+
throw new Error("Stop provider changed.");
|
|
10822
|
+
const owner = inspectDevsyWorkspaceOwnership(
|
|
10823
|
+
listDevsyWorkspaces(),
|
|
10824
|
+
state.devpodId,
|
|
10825
|
+
state.repoPath
|
|
10826
|
+
);
|
|
10827
|
+
if (owner.status !== "owned") throw new Error("Stop requires one exact provider registration.");
|
|
10828
|
+
if (isLinkedWorktree(state.repoPath)) {
|
|
10829
|
+
const workspace = resolveWorktreeWorkspace(state.repoPath);
|
|
10830
|
+
const record4 = workspace ? readWorkspaceOwnership(state.repoPath, workspace) : void 0;
|
|
10831
|
+
if (!workspace || workspace !== state.workspace || !record4 || record4.devpodId !== state.devpodId || !sameWorkspacePath(record4.worktreePath, state.repoPath))
|
|
10832
|
+
throw new Error("Stop workspace identity changed.");
|
|
10833
|
+
resolveGitCommonDir(state.repoPath);
|
|
10834
|
+
} else if (state.workspace !== void 0) throw new Error("Stop checkout identity changed.");
|
|
10835
|
+
const value = owner.workspace;
|
|
10836
|
+
return {
|
|
10837
|
+
context: value.context ?? "",
|
|
10838
|
+
providerId: value.id,
|
|
10839
|
+
uid: value.uid ?? "",
|
|
10840
|
+
sourcePath: value.source.localFolder,
|
|
10841
|
+
sourceContainer: value.source.container ?? ""
|
|
10842
|
+
};
|
|
10843
|
+
}
|
|
10844
|
+
function compareText(left, right) {
|
|
10845
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
10846
|
+
}
|
|
10847
|
+
function orderedMounts(mounts) {
|
|
10848
|
+
return mounts.map((mount) => ({ ...mount })).sort((a, b) => compareText(JSON.stringify(a), JSON.stringify(b)));
|
|
10849
|
+
}
|
|
10850
|
+
function identity(container) {
|
|
10851
|
+
return {
|
|
10852
|
+
id: container.id,
|
|
10853
|
+
service: container.labels["com.docker.compose.service"] ?? "",
|
|
10854
|
+
configFiles: (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").map((file) => file.trim()),
|
|
10855
|
+
mounts: orderedMounts(container.mounts)
|
|
10856
|
+
};
|
|
10857
|
+
}
|
|
10858
|
+
function prove(state, baseline) {
|
|
10859
|
+
const owner = registration(state);
|
|
10860
|
+
if (Object.entries(owner).some(
|
|
10861
|
+
([key, value]) => baseline[key] !== value
|
|
10862
|
+
)) {
|
|
10863
|
+
throw new Error("Retained provider identity changed.");
|
|
10864
|
+
}
|
|
10865
|
+
if (inspectManagedStopDaemon(baseline.endpoint) !== baseline.daemonId)
|
|
10866
|
+
throw new Error("Stop daemon changed.");
|
|
10867
|
+
const containers2 = inspectManagedStopContainers(baseline.project, baseline.endpoint);
|
|
10868
|
+
const expected = baseline.containers.map((container) => ({ ...container, mounts: orderedMounts(container.mounts) })).sort((a, b) => compareText(a.id, b.id));
|
|
10869
|
+
const observed = containers2.map(identity).sort((a, b) => compareText(a.id, b.id));
|
|
10870
|
+
if (!(0, import_node_util2.isDeepStrictEqual)(expected, observed) || containers2.some(
|
|
10871
|
+
(container) => container.labels["com.docker.compose.project.working_dir"] !== baseline.composeDirectory
|
|
10872
|
+
)) {
|
|
10873
|
+
throw new Error("Retained container population or immutable identity changed.");
|
|
10874
|
+
}
|
|
10875
|
+
const workspaceIds = inspectManagedStopWorkspaceIds(
|
|
10876
|
+
baseline.endpoint,
|
|
10877
|
+
baseline.composeDirectory
|
|
10878
|
+
).sort();
|
|
10879
|
+
if (!(0, import_node_util2.isDeepStrictEqual)(workspaceIds, expected.map((container) => container.id).sort())) {
|
|
10880
|
+
throw new Error("Workspace contains an unrecorded container population.");
|
|
10881
|
+
}
|
|
10882
|
+
const primary = baseline.containers.find(
|
|
10883
|
+
(container) => container.service === baseline.primaryService
|
|
10884
|
+
);
|
|
10885
|
+
if (!primary) throw new Error("Retained primary is missing.");
|
|
10886
|
+
const uidBytes = Buffer.byteLength(baseline.uid);
|
|
10887
|
+
const runnerId = uidBytes === 16 || uidBytes === 40 ? baseline.uid : baseline.providerId;
|
|
10888
|
+
if (!baseline.sourceContainer && inspectManagedStopRunnerId(baseline.endpoint, primary.id) !== runnerId) {
|
|
10889
|
+
throw new Error("Provider-to-container binding changed.");
|
|
10890
|
+
}
|
|
10891
|
+
if (!(0, import_node_util2.isDeepStrictEqual)(registration(state), owner) || inspectManagedStopDaemon(baseline.endpoint) !== baseline.daemonId) {
|
|
10892
|
+
throw new Error("Stop authority changed during inspection.");
|
|
10893
|
+
}
|
|
10894
|
+
return containers2;
|
|
10895
|
+
}
|
|
10896
|
+
function proveRetainedManagedStop(state) {
|
|
10897
|
+
const baseline = validateManagedStopBaseline(state.stopBaseline, state);
|
|
10898
|
+
if (!(0, import_node_util2.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state)) {
|
|
10899
|
+
throw new Error("Retained stop generation changed.");
|
|
10900
|
+
}
|
|
10901
|
+
const result = prove(state, baseline);
|
|
10902
|
+
if (!(0, import_node_util2.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state)) {
|
|
10903
|
+
throw new Error("Retained stop generation changed during inspection.");
|
|
10904
|
+
}
|
|
10905
|
+
return result;
|
|
10906
|
+
}
|
|
10907
|
+
function stopFromManagedBaseline(state) {
|
|
10908
|
+
const baseline = validateManagedStopBaseline(state.stopBaseline, state);
|
|
10909
|
+
const stopped = /* @__PURE__ */ new Set();
|
|
10910
|
+
for (const initial of proveRetainedManagedStop(state))
|
|
10911
|
+
if (!initial.state.Running) stopped.add(initial.id);
|
|
10912
|
+
const inspect = () => {
|
|
10913
|
+
const current = proveRetainedManagedStop(state);
|
|
10914
|
+
if (current.some((container) => stopped.has(container.id) && container.state.Running)) {
|
|
10915
|
+
throw new Error("Retained container restarted during stop.");
|
|
10916
|
+
}
|
|
10917
|
+
for (const container of current) if (!container.state.Running) stopped.add(container.id);
|
|
10918
|
+
return current;
|
|
10919
|
+
};
|
|
10920
|
+
for (const retained of baseline.containers) {
|
|
10921
|
+
if (inspect().find((container) => container.id === retained.id)?.state.Running) {
|
|
10922
|
+
claimLifecycleEffect();
|
|
10923
|
+
stopPinnedManagedContainer(baseline.endpoint, retained.id);
|
|
10924
|
+
if (inspect().find((container) => container.id === retained.id)?.state.Running)
|
|
10925
|
+
throw new Error("Container cessation is not proven.");
|
|
10926
|
+
}
|
|
10927
|
+
}
|
|
10928
|
+
if (inspect().some((container) => container.state.Running))
|
|
10929
|
+
throw new Error("Retained workloads remain running.");
|
|
10930
|
+
}
|
|
10931
|
+
var import_node_os2, import_node_path31, import_node_util2;
|
|
10932
|
+
var init_managed_stop_recovery = __esm({
|
|
10933
|
+
"src/core/managed-stop-recovery.ts"() {
|
|
10934
|
+
"use strict";
|
|
10935
|
+
import_node_os2 = __toESM(require("os"));
|
|
10936
|
+
import_node_path31 = __toESM(require("path"));
|
|
10937
|
+
import_node_util2 = require("util");
|
|
10938
|
+
init_devpod_environment();
|
|
10939
|
+
init_devsy_workspaces();
|
|
10940
|
+
init_managed_compose_population();
|
|
10941
|
+
init_managed_runtime_state();
|
|
10942
|
+
init_managed_stop_baseline();
|
|
10943
|
+
init_reliability_context();
|
|
10944
|
+
init_workspace();
|
|
10945
|
+
init_workspace_ownership();
|
|
10946
|
+
init_workspace_runtime();
|
|
10947
|
+
}
|
|
10948
|
+
});
|
|
10949
|
+
|
|
10950
|
+
// src/core/route-publication.ts
|
|
10951
|
+
function routedAppsFromConfig(config) {
|
|
10952
|
+
return config.apps.filter((app) => app.kind !== "dependency");
|
|
10953
|
+
}
|
|
10954
|
+
function configuredProxyAppsFromConfig(config) {
|
|
10955
|
+
return routedAppsFromConfig(config).filter(
|
|
10956
|
+
(app) => app.runtime === "proxy"
|
|
10957
|
+
);
|
|
10958
|
+
}
|
|
10959
|
+
function proxyAppsFromConfig(config) {
|
|
10960
|
+
const routedApps = routedAppsFromConfig(config);
|
|
10961
|
+
const proxyApps = configuredProxyAppsFromConfig(config);
|
|
10962
|
+
const unsupported = routedApps.filter((app) => app.runtime !== "proxy");
|
|
10963
|
+
if (unsupported.length > 0) {
|
|
10964
|
+
throw new Error(
|
|
10965
|
+
`Environment reconciliation supports proxy runtime apps only; unsupported: ${unsupported.map((app) => `${app.name} (${app.runtime})`).join(", ")}`
|
|
10966
|
+
);
|
|
10967
|
+
}
|
|
10968
|
+
return proxyApps;
|
|
10969
|
+
}
|
|
10970
|
+
async function ensureRouteInfrastructure(apps, options = {}) {
|
|
10971
|
+
claimLifecycleEffect();
|
|
10972
|
+
ensureRouterFiles();
|
|
10973
|
+
await ensureNetwork(DEVNET_NAME);
|
|
10974
|
+
claimLifecycleEffect();
|
|
10975
|
+
const tlsCoverage = await ensureTLSHostsCovered(
|
|
10976
|
+
apps.map((app) => app.host),
|
|
10977
|
+
options
|
|
10978
|
+
);
|
|
10979
|
+
for (const app of apps) {
|
|
10980
|
+
if (app.protocol === "tcp") {
|
|
10981
|
+
if (!isTLSEnabled()) {
|
|
10982
|
+
throw new Error(
|
|
10983
|
+
`TCP route '${app.name}' requires TLS. Run: ${tlsSetupCommand(options.repoPath)}`
|
|
10984
|
+
);
|
|
10985
|
+
}
|
|
10986
|
+
activateTcpProtocol(app.tcpProtocol);
|
|
10987
|
+
}
|
|
10988
|
+
}
|
|
10989
|
+
claimLifecycleEffect();
|
|
10990
|
+
startRouterStack();
|
|
10991
|
+
return tlsCoverage;
|
|
10992
|
+
}
|
|
10993
|
+
async function replacePublishedProxyRoutes(repoPath, config, workspace, options = {}) {
|
|
10994
|
+
const apps = proxyAppsFromConfig(config);
|
|
10995
|
+
const tlsCoverage = options.prepareInfrastructure === false ? { refreshed: false } : await ensureRouteInfrastructure(apps, { repoPath });
|
|
10996
|
+
const routes = apps.map((app) => {
|
|
10997
|
+
const upstream = parseUpstream(app.upstream);
|
|
10998
|
+
return {
|
|
10999
|
+
name: app.name,
|
|
11000
|
+
host: app.host,
|
|
11001
|
+
protocol: app.protocol,
|
|
11002
|
+
tcpProtocol: app.protocol === "tcp" ? app.tcpProtocol : void 0,
|
|
11003
|
+
repoPath,
|
|
11004
|
+
port: upstream.port,
|
|
11005
|
+
mode: "proxy",
|
|
11006
|
+
upstreamHost: upstream.upstreamHost,
|
|
11007
|
+
workspace
|
|
11008
|
+
};
|
|
11009
|
+
});
|
|
11010
|
+
claimLifecycleEffect();
|
|
11011
|
+
replaceHostRoutesForRepo(repoPath, routes);
|
|
11012
|
+
return { routes, tlsRefreshed: tlsCoverage.refreshed };
|
|
11013
|
+
}
|
|
11014
|
+
var init_route_publication = __esm({
|
|
11015
|
+
"src/core/route-publication.ts"() {
|
|
11016
|
+
"use strict";
|
|
11017
|
+
init_docker();
|
|
11018
|
+
init_host_routes();
|
|
11019
|
+
init_reliability_context();
|
|
11020
|
+
init_router();
|
|
11021
|
+
init_tls();
|
|
10605
11022
|
}
|
|
10606
11023
|
});
|
|
10607
11024
|
|
|
@@ -10627,8 +11044,12 @@ function stopRetainedManagedDevsyWorkspace(options) {
|
|
|
10627
11044
|
if (state.devpodId !== devsyId || linked && !workspace) {
|
|
10628
11045
|
throw new Error("Managed stop requires the exact retained workspace identity.");
|
|
10629
11046
|
}
|
|
11047
|
+
if (state.stopBaseline) {
|
|
11048
|
+
stopFromManagedBaseline(state);
|
|
11049
|
+
return true;
|
|
11050
|
+
}
|
|
10630
11051
|
const workspaceEnv = workspace ? { token: workspace, gitCommonDir: resolveGitCommonDir(repoPath) } : void 0;
|
|
10631
|
-
function
|
|
11052
|
+
function registration2() {
|
|
10632
11053
|
resetWorkspaceRuntimeCaches();
|
|
10633
11054
|
if (resolveWorkspaceRuntimeOrDefault(repoPath) !== "devsy") {
|
|
10634
11055
|
throw new Error("Managed stop provider selection changed.");
|
|
@@ -10638,19 +11059,19 @@ function stopRetainedManagedDevsyWorkspace(options) {
|
|
|
10638
11059
|
throw new Error("Managed stop requires one exact retained Devsy registration.");
|
|
10639
11060
|
}
|
|
10640
11061
|
if (workspace) {
|
|
10641
|
-
const
|
|
10642
|
-
if (!
|
|
11062
|
+
const record4 = readWorkspaceOwnership(repoPath, workspace);
|
|
11063
|
+
if (!record4 || record4.devpodId !== devsyId || !sameWorkspacePath(record4.worktreePath, repoPath) || resolveWorktreeWorkspace(repoPath) !== workspace || resolveGitCommonDir(repoPath) !== workspaceEnv?.gitCommonDir) {
|
|
10643
11064
|
throw new Error("Managed stop workspace ownership changed.");
|
|
10644
11065
|
}
|
|
10645
11066
|
}
|
|
10646
11067
|
return owner.workspace;
|
|
10647
11068
|
}
|
|
10648
|
-
const context =
|
|
11069
|
+
const context = registration2().context;
|
|
10649
11070
|
if (!context || !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(context) || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(devsyId)) {
|
|
10650
11071
|
throw new Error("Managed stop requires a valid exact Devsy context and workspace ID.");
|
|
10651
11072
|
}
|
|
10652
|
-
const devsyRoot =
|
|
10653
|
-
const featureDirectory =
|
|
11073
|
+
const devsyRoot = import_node_path32.default.resolve(process.env.DEVSY_HOME || import_node_path32.default.join(import_node_os3.default.homedir(), ".devsy"));
|
|
11074
|
+
const featureDirectory = import_node_path32.default.join(
|
|
10654
11075
|
devsyRoot,
|
|
10655
11076
|
"contexts",
|
|
10656
11077
|
context,
|
|
@@ -10659,8 +11080,8 @@ function stopRetainedManagedDevsyWorkspace(options) {
|
|
|
10659
11080
|
"agent",
|
|
10660
11081
|
".docker-compose"
|
|
10661
11082
|
);
|
|
10662
|
-
function
|
|
10663
|
-
if (
|
|
11083
|
+
function prove2(previous) {
|
|
11084
|
+
if (registration2().context !== context || JSON.stringify(readManagedRuntimeState(repoPath, workspace)) !== JSON.stringify(state)) {
|
|
10664
11085
|
throw new Error("Managed stop retained context or runtime record changed.");
|
|
10665
11086
|
}
|
|
10666
11087
|
const runtime = loadRuntimeConfig(repoPath, workspace ?? "", state.profile);
|
|
@@ -10683,49 +11104,24 @@ function stopRetainedManagedDevsyWorkspace(options) {
|
|
|
10683
11104
|
throw new Error("Managed stop requires unchanged recorded resources and configuration.");
|
|
10684
11105
|
}
|
|
10685
11106
|
const containers2 = inspectManagedStopContainers(state.composeProject);
|
|
10686
|
-
const
|
|
10687
|
-
|
|
10688
|
-
|
|
10689
|
-
|
|
10690
|
-
|
|
10691
|
-
|
|
10692
|
-
|
|
10693
|
-
|
|
10694
|
-
|
|
10695
|
-
services.add(service);
|
|
10696
|
-
const files = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").map((file) => file.trim());
|
|
10697
|
-
if (files.some((file) => !file || !import_node_path29.default.isAbsolute(file)) || new Set(files.map((file) => import_node_path29.default.resolve(file))).size !== files.length || files.length < plan.composeFiles.length || plan.composeFiles.some((file, index) => !sameWorkspacePath(file, files[index]))) {
|
|
10698
|
-
throw new Error("Managed stop Compose file identity changed.");
|
|
10699
|
-
}
|
|
10700
|
-
for (const file of files.slice(plan.composeFiles.length)) {
|
|
10701
|
-
if (import_node_path29.default.dirname(file) !== featureDirectory || !/^docker-compose\.devcontainer\.containerFeatures-[a-zA-Z0-9_-]+\.yml$/.test(
|
|
10702
|
-
import_node_path29.default.basename(file)
|
|
10703
|
-
) || import_node_fs26.default.realpathSync(file) !== import_node_path29.default.join(import_node_fs26.default.realpathSync(devsyRoot), import_node_path29.default.relative(devsyRoot, file))) {
|
|
10704
|
-
throw new Error("Managed stop refuses a foreign or escaped provider Compose file.");
|
|
10705
|
-
}
|
|
10706
|
-
}
|
|
10707
|
-
if (previous) {
|
|
10708
|
-
const retained = previous.find((entry) => entry.id === container.id);
|
|
10709
|
-
if (!retained || containerIdentity(retained) !== containerIdentity(container) || !retained.state.Running && container.state.Running) {
|
|
10710
|
-
throw new Error("Managed stop container identity or quiescent state changed.");
|
|
10711
|
-
}
|
|
10712
|
-
}
|
|
10713
|
-
}
|
|
10714
|
-
if (plan.desiredServices.some((service) => !services.has(service)) || previous && !sameSet(
|
|
11107
|
+
const primary = proveManagedComposePopulation({
|
|
11108
|
+
plan,
|
|
11109
|
+
repoPath,
|
|
11110
|
+
composeProject: state.composeProject,
|
|
11111
|
+
featureDirectory,
|
|
11112
|
+
devsyRoot,
|
|
11113
|
+
containers: containers2
|
|
11114
|
+
});
|
|
11115
|
+
if (previous && (!sameSet(
|
|
10715
11116
|
previous.map((c) => c.id),
|
|
10716
11117
|
containers2.map((c) => c.id)
|
|
10717
|
-
)) {
|
|
10718
|
-
|
|
10719
|
-
|
|
10720
|
-
|
|
10721
|
-
|
|
10722
|
-
);
|
|
10723
|
-
if (primary?.mounts.filter(
|
|
10724
|
-
(mount) => mount.Type === "bind" && sameWorkspacePath(mount.Source, repoPath)
|
|
10725
|
-
).length !== 1) {
|
|
10726
|
-
throw new Error("Managed stop cannot prove the exact primary workspace mount.");
|
|
11118
|
+
) || containers2.some((container) => {
|
|
11119
|
+
const retained = previous.find((entry) => entry.id === container.id);
|
|
11120
|
+
return !retained || containerIdentity(retained) !== containerIdentity(container) || !retained.state.Running && container.state.Running;
|
|
11121
|
+
}))) {
|
|
11122
|
+
throw new Error("Managed stop container identity or quiescent state changed.");
|
|
10727
11123
|
}
|
|
10728
|
-
if (
|
|
11124
|
+
if (registration2().context !== context) {
|
|
10729
11125
|
throw new Error("Managed stop provider context changed during inspection.");
|
|
10730
11126
|
}
|
|
10731
11127
|
const status = inspectDevsyRuntimeStatus(devsyId);
|
|
@@ -10734,7 +11130,7 @@ function stopRetainedManagedDevsyWorkspace(options) {
|
|
|
10734
11130
|
}
|
|
10735
11131
|
return { containers: containers2, primary, status };
|
|
10736
11132
|
}
|
|
10737
|
-
const initial =
|
|
11133
|
+
const initial = prove2();
|
|
10738
11134
|
let providerError;
|
|
10739
11135
|
let providerFailed = false;
|
|
10740
11136
|
if (initial.status === "running") {
|
|
@@ -10747,7 +11143,7 @@ function stopRetainedManagedDevsyWorkspace(options) {
|
|
|
10747
11143
|
}
|
|
10748
11144
|
try {
|
|
10749
11145
|
const stopped = () => {
|
|
10750
|
-
const current =
|
|
11146
|
+
const current = prove2(initial.containers);
|
|
10751
11147
|
if (current.status !== "stopped" || current.primary.state.Running) {
|
|
10752
11148
|
throw new Error("Managed stop has not stopped the exact primary container.");
|
|
10753
11149
|
}
|
|
@@ -10779,17 +11175,18 @@ function stopRetainedManagedDevsyWorkspace(options) {
|
|
|
10779
11175
|
if (providerFailed) throw providerError;
|
|
10780
11176
|
return true;
|
|
10781
11177
|
}
|
|
10782
|
-
var
|
|
11178
|
+
var import_node_os3, import_node_path32;
|
|
10783
11179
|
var init_managed_devsy_stop = __esm({
|
|
10784
11180
|
"src/core/managed-devsy-stop.ts"() {
|
|
10785
11181
|
"use strict";
|
|
10786
|
-
|
|
10787
|
-
|
|
10788
|
-
import_node_path29 = __toESM(require("path"));
|
|
11182
|
+
import_node_os3 = __toESM(require("os"));
|
|
11183
|
+
import_node_path32 = __toESM(require("path"));
|
|
10789
11184
|
init_devcontainer_profile();
|
|
10790
11185
|
init_devpod_environment();
|
|
10791
11186
|
init_devsy_workspaces();
|
|
11187
|
+
init_managed_compose_population();
|
|
10792
11188
|
init_managed_runtime_state();
|
|
11189
|
+
init_managed_stop_recovery();
|
|
10793
11190
|
init_repo_config();
|
|
10794
11191
|
init_route_publication();
|
|
10795
11192
|
init_workspace();
|
|
@@ -10875,20 +11272,20 @@ function stopOwnedDevsyWorkspace(devsyId, worktreePath) {
|
|
|
10875
11272
|
function deleteOwnedDevsyWorkspace(devsyId, worktreePath) {
|
|
10876
11273
|
return mutateOwnedDevsyWorkspace("delete", devsyId, worktreePath);
|
|
10877
11274
|
}
|
|
10878
|
-
var import_node_child_process18, import_node_fs27,
|
|
11275
|
+
var import_node_child_process18, import_node_fs27, import_node_path33, DEVSY_MUTATION_LOCK_FILE, DEVSY_MUTATION_WAIT_MS;
|
|
10879
11276
|
var init_devsy_mutation = __esm({
|
|
10880
11277
|
"src/core/devsy-mutation.ts"() {
|
|
10881
11278
|
"use strict";
|
|
10882
11279
|
import_node_child_process18 = require("child_process");
|
|
10883
11280
|
import_node_fs27 = __toESM(require("fs"));
|
|
10884
|
-
|
|
11281
|
+
import_node_path33 = __toESM(require("path"));
|
|
10885
11282
|
init_devsy_agent();
|
|
10886
11283
|
init_devsy_workspaces();
|
|
10887
11284
|
init_file_lock();
|
|
10888
11285
|
init_managed_devsy_stop();
|
|
10889
11286
|
init_reliability_context();
|
|
10890
11287
|
init_router();
|
|
10891
|
-
DEVSY_MUTATION_LOCK_FILE =
|
|
11288
|
+
DEVSY_MUTATION_LOCK_FILE = import_node_path33.default.join(DEVROUTER_HOME, "devsy-mutation.lock");
|
|
10892
11289
|
DEVSY_MUTATION_WAIT_MS = 18e5;
|
|
10893
11290
|
}
|
|
10894
11291
|
});
|
|
@@ -10981,20 +11378,20 @@ function deleteOwnedDevpodWorkspace(devpodId, worktreePath) {
|
|
|
10981
11378
|
resetWorkspaceRuntimeCaches();
|
|
10982
11379
|
return result;
|
|
10983
11380
|
}
|
|
10984
|
-
var import_node_child_process19, import_node_fs28,
|
|
11381
|
+
var import_node_child_process19, import_node_fs28, import_node_path34, DEVPOD_MUTATION_LOCK_FILE, DEVPOD_MUTATION_WAIT_MS;
|
|
10985
11382
|
var init_devpod_mutation = __esm({
|
|
10986
11383
|
"src/core/devpod-mutation.ts"() {
|
|
10987
11384
|
"use strict";
|
|
10988
11385
|
import_node_child_process19 = require("child_process");
|
|
10989
11386
|
import_node_fs28 = __toESM(require("fs"));
|
|
10990
|
-
|
|
11387
|
+
import_node_path34 = __toESM(require("path"));
|
|
10991
11388
|
init_devpod_workspaces();
|
|
10992
11389
|
init_devsy_mutation();
|
|
10993
11390
|
init_file_lock();
|
|
10994
11391
|
init_reliability_context();
|
|
10995
11392
|
init_router();
|
|
10996
11393
|
init_workspace_runtime();
|
|
10997
|
-
DEVPOD_MUTATION_LOCK_FILE =
|
|
11394
|
+
DEVPOD_MUTATION_LOCK_FILE = import_node_path34.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
|
|
10998
11395
|
DEVPOD_MUTATION_WAIT_MS = 18e5;
|
|
10999
11396
|
}
|
|
11000
11397
|
});
|
|
@@ -11009,10 +11406,10 @@ function inRepositoryWorkspaceScope(repoPath, worktreePath, livePaths) {
|
|
|
11009
11406
|
if (livePaths.some((candidate) => sameWorkspacePath(candidate, worktreePath))) return true;
|
|
11010
11407
|
const comparableRepo = comparableWorkspacePath(repoPath);
|
|
11011
11408
|
const comparableWorktree = comparableWorkspacePath(worktreePath);
|
|
11012
|
-
const localRoot =
|
|
11409
|
+
const localRoot = import_node_path35.default.join(comparableRepo, "trees") + import_node_path35.default.sep;
|
|
11013
11410
|
if (comparableWorktree.startsWith(localRoot)) return true;
|
|
11014
|
-
const legacyPrefix = `${
|
|
11015
|
-
return
|
|
11411
|
+
const legacyPrefix = `${import_node_path35.default.basename(comparableRepo)}-`;
|
|
11412
|
+
return import_node_path35.default.dirname(comparableWorktree) === import_node_path35.default.dirname(comparableRepo) && import_node_path35.default.basename(comparableWorktree).startsWith(legacyPrefix);
|
|
11016
11413
|
}
|
|
11017
11414
|
function previewActions(devpodStatus, routeCount, includeRecord) {
|
|
11018
11415
|
const actions = [
|
|
@@ -11041,15 +11438,15 @@ function ownedReason(ownerStatus) {
|
|
|
11041
11438
|
return "Ownership conflicts with live Git, persisted token, or DevPod source evidence.";
|
|
11042
11439
|
}
|
|
11043
11440
|
}
|
|
11044
|
-
function ownedCandidate(
|
|
11045
|
-
const status = inspectWorkspaceOwnership(
|
|
11046
|
-
const routeCount = routesForOwner(routes,
|
|
11441
|
+
function ownedCandidate(record4, worktrees, devpods, routes) {
|
|
11442
|
+
const status = inspectWorkspaceOwnership(record4, worktrees, devpods);
|
|
11443
|
+
const routeCount = routesForOwner(routes, record4.workspace, record4.worktreePath).length;
|
|
11047
11444
|
const eligible = status.ownerStatus === "missing" && status.devpodStatus !== "conflict";
|
|
11048
11445
|
return {
|
|
11049
11446
|
kind: "owned",
|
|
11050
|
-
workspace:
|
|
11051
|
-
devpodId:
|
|
11052
|
-
worktreePath:
|
|
11447
|
+
workspace: record4.workspace,
|
|
11448
|
+
devpodId: record4.devpodId,
|
|
11449
|
+
worktreePath: record4.worktreePath,
|
|
11053
11450
|
ownerStatus: status.ownerStatus,
|
|
11054
11451
|
devpodStatus: status.devpodStatus,
|
|
11055
11452
|
routeCount,
|
|
@@ -11067,10 +11464,10 @@ function legacyCandidates(repoPath, records, worktrees, devpods, routes) {
|
|
|
11067
11464
|
const primaryPath = worktrees[0]?.path;
|
|
11068
11465
|
const evidence = /* @__PURE__ */ new Map();
|
|
11069
11466
|
const isRecordedRoute = (workspace, worktreePath) => records.some(
|
|
11070
|
-
(
|
|
11467
|
+
(record4) => record4.workspace === workspace && sameWorkspacePath(record4.worktreePath, worktreePath)
|
|
11071
11468
|
);
|
|
11072
11469
|
const isRecordedDevpod = (devpodId, worktreePath) => records.some(
|
|
11073
|
-
(
|
|
11470
|
+
(record4) => record4.devpodId === devpodId && sameWorkspacePath(record4.worktreePath, worktreePath)
|
|
11074
11471
|
);
|
|
11075
11472
|
const key = (workspace, worktreePath) => `${workspace}\0${comparableWorkspacePath(worktreePath)}`;
|
|
11076
11473
|
for (const devpod of devpods) {
|
|
@@ -11109,8 +11506,8 @@ function legacyCandidates(repoPath, records, worktrees, devpods, routes) {
|
|
|
11109
11506
|
}));
|
|
11110
11507
|
}
|
|
11111
11508
|
function revalidateCandidate(candidate, repoPath, transaction) {
|
|
11112
|
-
const
|
|
11113
|
-
if (!
|
|
11509
|
+
const record4 = transaction.list().find((entry) => entry.workspace === candidate.workspace);
|
|
11510
|
+
if (!record4 || !sameWorkspacePath(record4.worktreePath, candidate.worktreePath) || record4.devpodId !== candidate.devpodId) {
|
|
11114
11511
|
return {
|
|
11115
11512
|
status: "blocked",
|
|
11116
11513
|
candidate: {
|
|
@@ -11126,12 +11523,12 @@ function revalidateCandidate(candidate, repoPath, transaction) {
|
|
|
11126
11523
|
};
|
|
11127
11524
|
}
|
|
11128
11525
|
const fresh2 = ownedCandidate(
|
|
11129
|
-
|
|
11526
|
+
record4,
|
|
11130
11527
|
listGitWorktrees(repoPath),
|
|
11131
|
-
listDevpodWorkspaces(
|
|
11528
|
+
listDevpodWorkspaces(record4.worktreePath),
|
|
11132
11529
|
listHostRouteState()
|
|
11133
11530
|
);
|
|
11134
|
-
if (fresh2.eligible) return { status: "ready", candidate: fresh2, record:
|
|
11531
|
+
if (fresh2.eligible) return { status: "ready", candidate: fresh2, record: record4 };
|
|
11135
11532
|
return {
|
|
11136
11533
|
status: "blocked",
|
|
11137
11534
|
candidate: {
|
|
@@ -11165,11 +11562,11 @@ function applyCandidate(candidate, repoPath) {
|
|
|
11165
11562
|
};
|
|
11166
11563
|
}
|
|
11167
11564
|
if (revalidated.status === "blocked") return revalidated.candidate;
|
|
11168
|
-
const { candidate: fresh2, record:
|
|
11565
|
+
const { candidate: fresh2, record: record4 } = revalidated;
|
|
11169
11566
|
const actions = [];
|
|
11170
11567
|
let devpodStatus;
|
|
11171
11568
|
try {
|
|
11172
|
-
const mutation = deleteOwnedDevpodWorkspace(
|
|
11569
|
+
const mutation = deleteOwnedDevpodWorkspace(record4.devpodId, record4.worktreePath);
|
|
11173
11570
|
devpodStatus = mutation.status === "changed" ? "owned" : "absent";
|
|
11174
11571
|
} catch (error) {
|
|
11175
11572
|
return { ...fresh2, actions: [failedAction("devpod", error)] };
|
|
@@ -11189,7 +11586,7 @@ function applyCandidate(candidate, repoPath) {
|
|
|
11189
11586
|
return { ...fresh2, actions: [...actions, failedAction("routes", error)] };
|
|
11190
11587
|
}
|
|
11191
11588
|
try {
|
|
11192
|
-
const removal = transaction.removeIfMatches(
|
|
11589
|
+
const removal = transaction.removeIfMatches(record4);
|
|
11193
11590
|
if (removal === "changed") {
|
|
11194
11591
|
throw new Error("Ownership record changed during cleanup; record was retained.");
|
|
11195
11592
|
}
|
|
@@ -11235,10 +11632,10 @@ function inspectWorkspaceGc(repo) {
|
|
|
11235
11632
|
// that owns each record (snapshot reads: the apply path revalidates
|
|
11236
11633
|
// live); legacy detection scans both registries.
|
|
11237
11634
|
...records.map(
|
|
11238
|
-
(
|
|
11239
|
-
|
|
11635
|
+
(record4) => ownedCandidate(
|
|
11636
|
+
record4,
|
|
11240
11637
|
worktrees,
|
|
11241
|
-
listDevpodWorkspacesFromSnapshots(
|
|
11638
|
+
listDevpodWorkspacesFromSnapshots(record4.worktreePath),
|
|
11242
11639
|
routes
|
|
11243
11640
|
)
|
|
11244
11641
|
),
|
|
@@ -11264,11 +11661,11 @@ function applyWorkspaceGc(plan) {
|
|
|
11264
11661
|
candidates
|
|
11265
11662
|
};
|
|
11266
11663
|
}
|
|
11267
|
-
var
|
|
11664
|
+
var import_node_path35;
|
|
11268
11665
|
var init_workspace_gc = __esm({
|
|
11269
11666
|
"src/core/workspace-gc.ts"() {
|
|
11270
11667
|
"use strict";
|
|
11271
|
-
|
|
11668
|
+
import_node_path35 = __toESM(require("path"));
|
|
11272
11669
|
init_devpod_mutation();
|
|
11273
11670
|
init_devpod_workspaces();
|
|
11274
11671
|
init_host_routes();
|
|
@@ -11621,7 +12018,7 @@ async function buildDoctorReport(options = {}) {
|
|
|
11621
12018
|
const config = runtimeConfig.config;
|
|
11622
12019
|
loadedConfig = config;
|
|
11623
12020
|
loadedWorkspace = runtimeConfig.workspace;
|
|
11624
|
-
const cliVersion = true ? "0.0.
|
|
12021
|
+
const cliVersion = true ? "0.0.61" : "0.0.0-dev";
|
|
11625
12022
|
const configVersion = config.devrouter?.version;
|
|
11626
12023
|
if (configVersion && cliVersion !== "0.0.0-dev" && compareSemver(configVersion, cliVersion) > 0) {
|
|
11627
12024
|
addCheck(checks, {
|
|
@@ -11651,7 +12048,7 @@ async function buildDoctorReport(options = {}) {
|
|
|
11651
12048
|
(app) => app.docker.composeFiles.map((filePath) => ({
|
|
11652
12049
|
app: app.name,
|
|
11653
12050
|
filePath,
|
|
11654
|
-
absolutePath:
|
|
12051
|
+
absolutePath: import_node_path36.default.resolve(repo.path, filePath)
|
|
11655
12052
|
}))
|
|
11656
12053
|
).filter((entry) => !import_node_fs29.default.existsSync(entry.absolutePath));
|
|
11657
12054
|
addCheck(checks, {
|
|
@@ -11696,7 +12093,7 @@ async function buildDoctorReport(options = {}) {
|
|
|
11696
12093
|
const missingHostCwds = config.apps.filter((app) => app.runtime === "host").map((app) => ({
|
|
11697
12094
|
app: app.name,
|
|
11698
12095
|
cwd: app.hostRun.cwd,
|
|
11699
|
-
absolutePath:
|
|
12096
|
+
absolutePath: import_node_path36.default.resolve(repo.path, app.hostRun.cwd)
|
|
11700
12097
|
})).filter((entry) => !import_node_fs29.default.existsSync(entry.absolutePath));
|
|
11701
12098
|
addCheck(checks, {
|
|
11702
12099
|
id: "repo.host-cwd",
|
|
@@ -11841,12 +12238,12 @@ async function buildDoctorReport(options = {}) {
|
|
|
11841
12238
|
nextSteps
|
|
11842
12239
|
};
|
|
11843
12240
|
}
|
|
11844
|
-
var import_node_fs29,
|
|
12241
|
+
var import_node_fs29, import_node_path36, import_yaml5, POSTGRES_DEFAULTS;
|
|
11845
12242
|
var init_doctor = __esm({
|
|
11846
12243
|
"src/core/doctor.ts"() {
|
|
11847
12244
|
"use strict";
|
|
11848
12245
|
import_node_fs29 = __toESM(require("fs"));
|
|
11849
|
-
|
|
12246
|
+
import_node_path36 = __toESM(require("path"));
|
|
11850
12247
|
import_yaml5 = __toESM(require("yaml"));
|
|
11851
12248
|
init_docker();
|
|
11852
12249
|
init_host_routes();
|
|
@@ -12255,7 +12652,7 @@ var init_up = __esm({
|
|
|
12255
12652
|
});
|
|
12256
12653
|
|
|
12257
12654
|
// src/core/reliability-model.ts
|
|
12258
|
-
function
|
|
12655
|
+
function record3(value) {
|
|
12259
12656
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12260
12657
|
}
|
|
12261
12658
|
function oneOf2(value, values) {
|
|
@@ -12265,16 +12662,16 @@ function uniqueIds2(values) {
|
|
|
12265
12662
|
return new Set(values).size === values.length && values.every(isReliabilityId);
|
|
12266
12663
|
}
|
|
12267
12664
|
function isFence(value) {
|
|
12268
|
-
return
|
|
12665
|
+
return record3(value) && isReliabilityId(value.environmentId) && isReliabilityCounter(value.intentRevision) && isReliabilityCounter(value.runtimeGeneration) && isReliabilityCounter(value.controllerEpoch);
|
|
12269
12666
|
}
|
|
12270
12667
|
function isConsumer(value) {
|
|
12271
|
-
return
|
|
12668
|
+
return record3(value) && isReliabilityId(value.id) && typeof value.pinned === "boolean" && Array.isArray(value.requiredCapabilities) && value.requiredCapabilities.length <= 128 && uniqueIds2(value.requiredCapabilities);
|
|
12272
12669
|
}
|
|
12273
12670
|
function isObservation(value) {
|
|
12274
|
-
return
|
|
12671
|
+
return record3(value) && isReliabilityId(value.capability) && oneOf2(value.infrastructure, INFRASTRUCTURE_VALUES) && oneOf2(value.application, APPLICATION_VALUES) && isReliabilityCounter(value.observedAtMs) && isReliabilityCounter(value.validForMs);
|
|
12275
12672
|
}
|
|
12276
12673
|
function assertReliabilityEvent(value) {
|
|
12277
|
-
if (!
|
|
12674
|
+
if (!record3(value) || !isFence(value) || typeof value.type !== "string") {
|
|
12278
12675
|
throw new Error("Invalid reliability event.");
|
|
12279
12676
|
}
|
|
12280
12677
|
let valid = false;
|
|
@@ -12476,7 +12873,6 @@ function handleOperationRequest(state, event) {
|
|
|
12476
12873
|
);
|
|
12477
12874
|
if (state.operationHistory.some((entry) => entry.id === event.operationId))
|
|
12478
12875
|
return unchanged(state, "conflict");
|
|
12479
|
-
if (state.operationHistory.length >= RELIABILITY_MAX_ITEMS) return unchanged(state, "blocked");
|
|
12480
12876
|
const fullyStopped = state.stopProof.workloadsStopped && state.stopProof.routesRemoved;
|
|
12481
12877
|
const reconcileEnsure = event.kind === "ensure" && state.operation?.kind === "ensure" && ["NOT_STARTED", "INTERRUPTED"].includes(state.operation.status);
|
|
12482
12878
|
if (state.operation && (!state.operation.drained || !["COMPLETED", "NOT_LAUNCHED", "NOT_STARTED"].includes(state.operation.status) && !fullyStopped && !reconcileEnsure))
|
|
@@ -12485,11 +12881,22 @@ function handleOperationRequest(state, event) {
|
|
|
12485
12881
|
return unchanged(state, "blocked");
|
|
12486
12882
|
if (event.kind === "exec" && (!event.runtimeRunning || state.desired !== "running" && state.intentRevision !== 0))
|
|
12487
12883
|
return unchanged(state, "blocked");
|
|
12488
|
-
|
|
12884
|
+
const rollover = state.operationHistory.length >= RELIABILITY_MAX_ITEMS;
|
|
12885
|
+
let retired = -1;
|
|
12886
|
+
if (rollover) {
|
|
12887
|
+
if (state.operation && (!state.operation.drained || !["COMPLETED", "NOT_LAUNCHED"].includes(state.operation.status)))
|
|
12888
|
+
return unchanged(state, "blocked");
|
|
12889
|
+
retired = state.operationHistory.findIndex(
|
|
12890
|
+
(entry) => entry.id !== state.operation?.id && entry.drained && ["COMPLETED", "NOT_LAUNCHED"].includes(entry.status)
|
|
12891
|
+
);
|
|
12892
|
+
if (retired === -1) return unchanged(state, "blocked");
|
|
12893
|
+
}
|
|
12894
|
+
if (state.desired !== "running" || rollover) {
|
|
12489
12895
|
const revision = advance(state.intentRevision);
|
|
12490
12896
|
if (revision === null) return unchanged(state, "blocked");
|
|
12491
12897
|
state.intentRevision = revision;
|
|
12492
12898
|
}
|
|
12899
|
+
if (rollover) state.operationHistory.splice(retired, 1);
|
|
12493
12900
|
state.desired = "running";
|
|
12494
12901
|
state.phase = "queued";
|
|
12495
12902
|
state.profile = event.profile;
|
|
@@ -12858,8 +13265,8 @@ var init_reliability_model = __esm({
|
|
|
12858
13265
|
});
|
|
12859
13266
|
|
|
12860
13267
|
// src/core/reliability-worker.ts
|
|
12861
|
-
function sameFence2(
|
|
12862
|
-
return Object.entries(reliabilityFence(
|
|
13268
|
+
function sameFence2(record4, fence) {
|
|
13269
|
+
return Object.entries(reliabilityFence(record4.state)).every(
|
|
12863
13270
|
([key, value]) => value === fence[key]
|
|
12864
13271
|
);
|
|
12865
13272
|
}
|
|
@@ -12882,7 +13289,7 @@ function signalOwnedGroup(child, signal) {
|
|
|
12882
13289
|
async function runLifecycleWorker(request) {
|
|
12883
13290
|
if (process.platform === "win32")
|
|
12884
13291
|
throw new Error("Lifecycle workers require POSIX process-group ownership.");
|
|
12885
|
-
const workerPath =
|
|
13292
|
+
const workerPath = import_node_path37.default.join(__dirname, "devrouter-lifecycle-worker.js");
|
|
12886
13293
|
if (!import_node_fs30.default.existsSync(workerPath))
|
|
12887
13294
|
throw new Error("The packaged lifecycle worker is missing; rebuild the CLI.");
|
|
12888
13295
|
const child = (0, import_node_child_process21.fork)(workerPath, [], {
|
|
@@ -12930,28 +13337,28 @@ async function runLifecycleWorker(request) {
|
|
|
12930
13337
|
if (request.kind !== "stop") {
|
|
12931
13338
|
const birth = processBirthIdentity(child.pid);
|
|
12932
13339
|
if (!birth) throw new Error("Could not prove lifecycle worker incarnation.");
|
|
12933
|
-
updateReliabilityOperation(request.identity, (
|
|
12934
|
-
if (!sameFence2(
|
|
13340
|
+
updateReliabilityOperation(request.identity, (record4) => {
|
|
13341
|
+
if (!sameFence2(record4, request.fence) || record4.worker || record4.state.operation?.id !== request.operationId) {
|
|
12935
13342
|
throw new Error("Lifecycle intent changed before dispatch.");
|
|
12936
13343
|
}
|
|
12937
|
-
|
|
13344
|
+
record4.worker = {
|
|
12938
13345
|
id: request.workerId,
|
|
12939
13346
|
operationId: request.operationId,
|
|
12940
13347
|
pid: child.pid,
|
|
12941
13348
|
birth
|
|
12942
13349
|
};
|
|
12943
13350
|
const transition2 = stepReliability(
|
|
12944
|
-
|
|
13351
|
+
record4.state,
|
|
12945
13352
|
{ ...request.fence, type: "dispatch" },
|
|
12946
13353
|
Date.now()
|
|
12947
13354
|
);
|
|
12948
13355
|
if (transition2.outcome !== "accepted")
|
|
12949
13356
|
throw new Error("Lifecycle dispatch is blocked.");
|
|
12950
|
-
|
|
13357
|
+
record4.state = transition2.state;
|
|
12951
13358
|
});
|
|
12952
|
-
updateReliabilityOperation(request.identity, (
|
|
13359
|
+
updateReliabilityOperation(request.identity, (record4) => {
|
|
12953
13360
|
const transition2 = stepReliability(
|
|
12954
|
-
|
|
13361
|
+
record4.state,
|
|
12955
13362
|
{
|
|
12956
13363
|
...request.fence,
|
|
12957
13364
|
type: "dispatch-persisted",
|
|
@@ -12959,7 +13366,7 @@ async function runLifecycleWorker(request) {
|
|
|
12959
13366
|
},
|
|
12960
13367
|
Date.now()
|
|
12961
13368
|
);
|
|
12962
|
-
|
|
13369
|
+
record4.state = transition2.state;
|
|
12963
13370
|
if (!transition2.effects.some((effect2) => effect2.kind === "launch")) {
|
|
12964
13371
|
throw new Error("Lifecycle dispatch persistence was not acknowledged.");
|
|
12965
13372
|
}
|
|
@@ -12973,8 +13380,8 @@ async function runLifecycleWorker(request) {
|
|
|
12973
13380
|
});
|
|
12974
13381
|
monitor = setInterval(() => {
|
|
12975
13382
|
try {
|
|
12976
|
-
const
|
|
12977
|
-
if (!
|
|
13383
|
+
const record4 = readReliabilityOperation(request.identity);
|
|
13384
|
+
if (!record4 || !sameFence2(record4, request.fence)) cancel();
|
|
12978
13385
|
} catch (error) {
|
|
12979
13386
|
failure = error instanceof Error ? error : new Error(String(error));
|
|
12980
13387
|
cancel();
|
|
@@ -12991,13 +13398,13 @@ async function runLifecycleWorker(request) {
|
|
|
12991
13398
|
child.once("close", () => {
|
|
12992
13399
|
try {
|
|
12993
13400
|
if (request.kind !== "stop") {
|
|
12994
|
-
updateReliabilityOperation(request.identity, (
|
|
12995
|
-
if (
|
|
13401
|
+
updateReliabilityOperation(request.identity, (record4) => {
|
|
13402
|
+
if (record4.worker?.id !== request.workerId || record4.worker.pid !== child.pid) return;
|
|
12996
13403
|
if (!result) {
|
|
12997
|
-
|
|
12998
|
-
|
|
13404
|
+
record4.state = stepReliability(
|
|
13405
|
+
record4.state,
|
|
12999
13406
|
{
|
|
13000
|
-
...reliabilityFence(
|
|
13407
|
+
...reliabilityFence(record4.state),
|
|
13001
13408
|
type: "interrupted",
|
|
13002
13409
|
operationId: request.operationId
|
|
13003
13410
|
},
|
|
@@ -13005,16 +13412,16 @@ async function runLifecycleWorker(request) {
|
|
|
13005
13412
|
).state;
|
|
13006
13413
|
}
|
|
13007
13414
|
if (child.pid && workerGroupAbsent(child.pid)) {
|
|
13008
|
-
|
|
13009
|
-
|
|
13415
|
+
record4.state = stepReliability(
|
|
13416
|
+
record4.state,
|
|
13010
13417
|
{
|
|
13011
|
-
...reliabilityFence(
|
|
13418
|
+
...reliabilityFence(record4.state),
|
|
13012
13419
|
type: "drained",
|
|
13013
13420
|
operationId: request.operationId
|
|
13014
13421
|
},
|
|
13015
13422
|
Date.now()
|
|
13016
13423
|
).state;
|
|
13017
|
-
|
|
13424
|
+
record4.worker = null;
|
|
13018
13425
|
}
|
|
13019
13426
|
});
|
|
13020
13427
|
}
|
|
@@ -13041,14 +13448,14 @@ async function runLifecycleWorker(request) {
|
|
|
13041
13448
|
function newLifecycleIds() {
|
|
13042
13449
|
return { requestId: (0, import_node_crypto16.randomUUID)(), operationId: (0, import_node_crypto16.randomUUID)(), workerId: (0, import_node_crypto16.randomUUID)() };
|
|
13043
13450
|
}
|
|
13044
|
-
var import_node_child_process21, import_node_crypto16, import_node_fs30,
|
|
13451
|
+
var import_node_child_process21, import_node_crypto16, import_node_fs30, import_node_path37;
|
|
13045
13452
|
var init_reliability_worker = __esm({
|
|
13046
13453
|
"src/core/reliability-worker.ts"() {
|
|
13047
13454
|
"use strict";
|
|
13048
13455
|
import_node_child_process21 = require("child_process");
|
|
13049
13456
|
import_node_crypto16 = require("crypto");
|
|
13050
13457
|
import_node_fs30 = __toESM(require("fs"));
|
|
13051
|
-
|
|
13458
|
+
import_node_path37 = __toESM(require("path"));
|
|
13052
13459
|
init_file_lock();
|
|
13053
13460
|
init_reliability_contract();
|
|
13054
13461
|
init_reliability_model();
|
|
@@ -13202,19 +13609,19 @@ async function ensureTraefikRouteExpectation(routes, expectation, options) {
|
|
|
13202
13609
|
async function ensureTraefikRoutesRemoved(routes, options = {}) {
|
|
13203
13610
|
return ensureTraefikRouteExpectation(routes, "removed", options);
|
|
13204
13611
|
}
|
|
13205
|
-
var import_node_child_process23, import_node_fs32,
|
|
13612
|
+
var import_node_child_process23, import_node_fs32, import_node_path38, ROUTER_API_BASE, ROUTER_RELOAD_LOCK_FILE, ROUTER_API_REQUEST_TIMEOUT_SECONDS, DEFAULT_INITIAL_TIMEOUT_MS, DEFAULT_RECOVERY_TIMEOUT_MS, DEFAULT_POLL_INTERVAL_MS, ROUTER_API_PAGE_SIZE;
|
|
13206
13613
|
var init_traefik_route_health = __esm({
|
|
13207
13614
|
"src/core/traefik-route-health.ts"() {
|
|
13208
13615
|
"use strict";
|
|
13209
13616
|
import_node_child_process23 = require("child_process");
|
|
13210
13617
|
import_node_fs32 = __toESM(require("fs"));
|
|
13211
|
-
|
|
13618
|
+
import_node_path38 = __toESM(require("path"));
|
|
13212
13619
|
init_file_lock();
|
|
13213
13620
|
init_host_routes();
|
|
13214
13621
|
init_reliability_context();
|
|
13215
13622
|
init_router();
|
|
13216
13623
|
ROUTER_API_BASE = "http://127.0.0.1:8080/api";
|
|
13217
|
-
ROUTER_RELOAD_LOCK_FILE =
|
|
13624
|
+
ROUTER_RELOAD_LOCK_FILE = import_node_path38.default.join(DEVROUTER_HOME, "router-reload.lock");
|
|
13218
13625
|
ROUTER_API_REQUEST_TIMEOUT_SECONDS = "2";
|
|
13219
13626
|
DEFAULT_INITIAL_TIMEOUT_MS = 3e3;
|
|
13220
13627
|
DEFAULT_RECOVERY_TIMEOUT_MS = 1e4;
|
|
@@ -13253,17 +13660,18 @@ function resolveLinkedTarget(repoPath) {
|
|
|
13253
13660
|
gitCommonDir: resolveGitCommonDir(repoPath)
|
|
13254
13661
|
};
|
|
13255
13662
|
}
|
|
13256
|
-
var import_node_child_process24, import_node_fs33,
|
|
13663
|
+
var import_node_child_process24, import_node_fs33, import_node_path39;
|
|
13257
13664
|
var init_workspace_ensure = __esm({
|
|
13258
13665
|
"src/core/workspace-ensure.ts"() {
|
|
13259
13666
|
"use strict";
|
|
13260
13667
|
import_node_child_process24 = require("child_process");
|
|
13261
13668
|
import_node_fs33 = __toESM(require("fs"));
|
|
13262
|
-
|
|
13669
|
+
import_node_path39 = __toESM(require("path"));
|
|
13263
13670
|
init_devcontainer_profile();
|
|
13264
13671
|
init_devpod_environment();
|
|
13265
13672
|
init_devpod_mutation();
|
|
13266
13673
|
init_devpod_workspaces();
|
|
13674
|
+
init_devsy_mutation();
|
|
13267
13675
|
init_file_lock();
|
|
13268
13676
|
init_host_routes();
|
|
13269
13677
|
init_http_route_probe();
|
|
@@ -13271,6 +13679,7 @@ var init_workspace_ensure = __esm({
|
|
|
13271
13679
|
init_managed_post_start();
|
|
13272
13680
|
init_managed_runtime_state();
|
|
13273
13681
|
init_managed_runtime_status();
|
|
13682
|
+
init_managed_stop_recovery();
|
|
13274
13683
|
init_reliability_lifecycle();
|
|
13275
13684
|
init_repo_config();
|
|
13276
13685
|
init_route_publication();
|
|
@@ -13292,50 +13701,51 @@ async function withLifecycleOperationLock(repoPath, operation) {
|
|
|
13292
13701
|
}
|
|
13293
13702
|
return withWorkspaceLifecycleLock(repoPath, operation);
|
|
13294
13703
|
}
|
|
13295
|
-
function stepRecord(
|
|
13296
|
-
const transition2 = stepReliability(
|
|
13704
|
+
function stepRecord(record4, event) {
|
|
13705
|
+
const transition2 = stepReliability(record4.state, event, Date.now());
|
|
13297
13706
|
if (!["accepted", "joined"].includes(transition2.outcome))
|
|
13298
13707
|
throw new Error(`Lifecycle transition is ${transition2.outcome}.`);
|
|
13299
|
-
|
|
13708
|
+
record4.state = transition2.state;
|
|
13300
13709
|
}
|
|
13301
|
-
function reconcileDrained(
|
|
13302
|
-
const worker =
|
|
13710
|
+
function reconcileDrained(record4) {
|
|
13711
|
+
const worker = record4.worker;
|
|
13303
13712
|
if (!worker) {
|
|
13304
|
-
if (
|
|
13305
|
-
stepRecord(
|
|
13306
|
-
...reliabilityFence(
|
|
13713
|
+
if (record4.state.operation?.status === "NOT_STARTED" && !record4.state.operation.drained) {
|
|
13714
|
+
stepRecord(record4, {
|
|
13715
|
+
...reliabilityFence(record4.state),
|
|
13307
13716
|
type: "drained",
|
|
13308
|
-
operationId:
|
|
13717
|
+
operationId: record4.state.operation.id
|
|
13309
13718
|
});
|
|
13310
13719
|
}
|
|
13311
13720
|
return;
|
|
13312
13721
|
}
|
|
13313
13722
|
if (processBirthIdentity(worker.pid) === worker.birth || !workerGroupAbsent(worker.pid)) return;
|
|
13314
|
-
if (
|
|
13723
|
+
if (record4.state.operation) {
|
|
13315
13724
|
const interrupted = stepReliability(
|
|
13316
|
-
|
|
13725
|
+
record4.state,
|
|
13317
13726
|
{
|
|
13318
|
-
...reliabilityFence(
|
|
13727
|
+
...reliabilityFence(record4.state),
|
|
13319
13728
|
type: "interrupted",
|
|
13320
13729
|
operationId: worker.operationId
|
|
13321
13730
|
},
|
|
13322
13731
|
Date.now()
|
|
13323
13732
|
);
|
|
13324
|
-
|
|
13325
|
-
stepRecord(
|
|
13326
|
-
...reliabilityFence(
|
|
13733
|
+
record4.state = interrupted.state;
|
|
13734
|
+
stepRecord(record4, {
|
|
13735
|
+
...reliabilityFence(record4.state),
|
|
13327
13736
|
type: "drained",
|
|
13328
13737
|
operationId: worker.operationId
|
|
13329
13738
|
});
|
|
13330
|
-
|
|
13739
|
+
record4.worker = null;
|
|
13331
13740
|
}
|
|
13332
13741
|
}
|
|
13333
13742
|
async function superviseLifecycle(kind, repoPath, options = {}, command) {
|
|
13334
13743
|
repoPath = comparableWorkspacePath(repoPath);
|
|
13335
13744
|
if (isLinkedWorktree(repoPath) && !readPersistedWorkspace(repoPath)) {
|
|
13745
|
+
if (kind === "stop") throw new Error("Stop requires the existing linked workspace identity.");
|
|
13336
13746
|
resolveLinkedTarget(repoPath);
|
|
13337
13747
|
}
|
|
13338
|
-
const
|
|
13748
|
+
const identity2 = {
|
|
13339
13749
|
repoPath,
|
|
13340
13750
|
workspace: resolveWorktreeWorkspace(repoPath) ?? null,
|
|
13341
13751
|
provider: resolveWorkspaceRuntimeOrDefault(repoPath)
|
|
@@ -13343,48 +13753,50 @@ async function superviseLifecycle(kind, repoPath, options = {}, command) {
|
|
|
13343
13753
|
const ids = newLifecycleIds();
|
|
13344
13754
|
let fence;
|
|
13345
13755
|
const runtimeRunning = kind === "exec" ? Boolean(resolveRunningWorkspaceContainer(repoPath)) : false;
|
|
13346
|
-
updateReliabilityOperation(
|
|
13347
|
-
reconcileDrained(
|
|
13756
|
+
updateReliabilityOperation(identity2, (record4) => {
|
|
13757
|
+
reconcileDrained(record4);
|
|
13348
13758
|
if (kind === "stop") {
|
|
13349
|
-
stepRecord(
|
|
13759
|
+
stepRecord(record4, { ...reliabilityFence(record4.state), type: "stop" });
|
|
13350
13760
|
} else {
|
|
13351
|
-
if (
|
|
13761
|
+
if (record4.worker)
|
|
13352
13762
|
throw new Error(
|
|
13353
13763
|
"An earlier lifecycle worker may still be active; use explicit stop to reconcile it."
|
|
13354
13764
|
);
|
|
13355
|
-
|
|
13356
|
-
stepRecord(
|
|
13357
|
-
...reliabilityFence(
|
|
13765
|
+
record4.outcome = null;
|
|
13766
|
+
stepRecord(record4, {
|
|
13767
|
+
...reliabilityFence(record4.state),
|
|
13358
13768
|
type: "operation-request",
|
|
13359
13769
|
kind,
|
|
13360
13770
|
key: ids.requestId,
|
|
13361
13771
|
operationId: ids.operationId,
|
|
13362
|
-
profile: options.profile ??
|
|
13772
|
+
profile: options.profile ?? record4.state.profile ?? "full",
|
|
13363
13773
|
consumer: { id: "manual-cli", requiredCapabilities: [], pinned: false },
|
|
13364
13774
|
runtimeRunning
|
|
13365
13775
|
});
|
|
13366
13776
|
}
|
|
13367
|
-
fence = reliabilityFence(
|
|
13777
|
+
fence = reliabilityFence(record4.state);
|
|
13368
13778
|
});
|
|
13369
13779
|
return runLifecycleWorker({
|
|
13370
13780
|
kind,
|
|
13371
13781
|
repoPath,
|
|
13372
|
-
identity,
|
|
13782
|
+
identity: identity2,
|
|
13373
13783
|
...ids,
|
|
13374
13784
|
fence,
|
|
13375
13785
|
options,
|
|
13376
13786
|
...command ? { command } : {}
|
|
13377
13787
|
});
|
|
13378
13788
|
}
|
|
13379
|
-
var
|
|
13789
|
+
var import_node_path40, activeWorker, lockHeld;
|
|
13380
13790
|
var init_reliability_lifecycle = __esm({
|
|
13381
13791
|
"src/core/reliability-lifecycle.ts"() {
|
|
13382
13792
|
"use strict";
|
|
13383
|
-
|
|
13793
|
+
import_node_path40 = __toESM(require("path"));
|
|
13384
13794
|
init_devpod_environment();
|
|
13795
|
+
init_devsy_mutation();
|
|
13385
13796
|
init_file_lock();
|
|
13386
13797
|
init_host_routes();
|
|
13387
13798
|
init_managed_runtime_state();
|
|
13799
|
+
init_managed_stop_recovery();
|
|
13388
13800
|
init_reliability_context();
|
|
13389
13801
|
init_reliability_contract();
|
|
13390
13802
|
init_reliability_model();
|
|
@@ -13798,7 +14210,7 @@ function readJson(filePath) {
|
|
|
13798
14210
|
}
|
|
13799
14211
|
}
|
|
13800
14212
|
function relative(repoPath, filePath) {
|
|
13801
|
-
return
|
|
14213
|
+
return import_node_path41.default.relative(repoPath, filePath) || ".";
|
|
13802
14214
|
}
|
|
13803
14215
|
function redactEnvAssignments(value) {
|
|
13804
14216
|
return value.replace(
|
|
@@ -13839,7 +14251,7 @@ function inspectPackageManager(repoPath, pkg) {
|
|
|
13839
14251
|
["bun.lock", "bun"]
|
|
13840
14252
|
];
|
|
13841
14253
|
for (const [fileName, name] of lockfiles) {
|
|
13842
|
-
if (import_node_fs34.default.existsSync(
|
|
14254
|
+
if (import_node_fs34.default.existsSync(import_node_path41.default.join(repoPath, fileName))) {
|
|
13843
14255
|
return { name, source: fileName };
|
|
13844
14256
|
}
|
|
13845
14257
|
}
|
|
@@ -13854,7 +14266,7 @@ function inspectNode(repoPath, pkg) {
|
|
|
13854
14266
|
if (typeof engines?.node === "string") {
|
|
13855
14267
|
return { version: engines.node, source: "package.json:engines.node" };
|
|
13856
14268
|
}
|
|
13857
|
-
const nvmrc =
|
|
14269
|
+
const nvmrc = import_node_path41.default.join(repoPath, ".nvmrc");
|
|
13858
14270
|
if (import_node_fs34.default.existsSync(nvmrc)) {
|
|
13859
14271
|
const version = import_node_fs34.default.readFileSync(nvmrc, "utf-8").trim();
|
|
13860
14272
|
return { version, source: ".nvmrc" };
|
|
@@ -13917,7 +14329,7 @@ function configuredComposeFiles(repoPath) {
|
|
|
13917
14329
|
const files = config.apps.filter(
|
|
13918
14330
|
(app) => app.runtime === "docker"
|
|
13919
14331
|
).flatMap((app) => app.docker.composeFiles).filter(
|
|
13920
|
-
(fileName) => !
|
|
14332
|
+
(fileName) => !import_node_path41.default.isAbsolute(fileName) && !import_node_path41.default.normalize(fileName).startsWith("..")
|
|
13921
14333
|
);
|
|
13922
14334
|
return Array.from(new Set(files));
|
|
13923
14335
|
} catch {
|
|
@@ -13935,7 +14347,7 @@ function composeFiles(repoPath) {
|
|
|
13935
14347
|
...configuredComposeFiles(repoPath)
|
|
13936
14348
|
];
|
|
13937
14349
|
return Array.from(new Set(candidates)).filter(
|
|
13938
|
-
(fileName) => import_node_fs34.default.existsSync(
|
|
14350
|
+
(fileName) => import_node_fs34.default.existsSync(import_node_path41.default.join(repoPath, fileName))
|
|
13939
14351
|
);
|
|
13940
14352
|
}
|
|
13941
14353
|
function stringArray2(value) {
|
|
@@ -13948,8 +14360,8 @@ function envNames(value) {
|
|
|
13948
14360
|
if (Array.isArray(value)) {
|
|
13949
14361
|
return value.filter((entry) => typeof entry === "string").map((entry) => entry.split("=")[0]?.trim()).filter((entry) => Boolean(entry));
|
|
13950
14362
|
}
|
|
13951
|
-
const
|
|
13952
|
-
return
|
|
14363
|
+
const record4 = asRecord3(value);
|
|
14364
|
+
return record4 ? Object.keys(record4).sort() : [];
|
|
13953
14365
|
}
|
|
13954
14366
|
function inferServiceKind(name, image) {
|
|
13955
14367
|
const source = `${name} ${image ?? ""}`.toLowerCase();
|
|
@@ -13973,7 +14385,7 @@ function inspectServices(repoPath) {
|
|
|
13973
14385
|
const services = [];
|
|
13974
14386
|
for (const fileName of composeFiles(repoPath)) {
|
|
13975
14387
|
try {
|
|
13976
|
-
const parsed = import_yaml6.default.parse(import_node_fs34.default.readFileSync(
|
|
14388
|
+
const parsed = import_yaml6.default.parse(import_node_fs34.default.readFileSync(import_node_path41.default.join(repoPath, fileName), "utf-8"));
|
|
13977
14389
|
const serviceMap = asRecord3(asRecord3(parsed)?.services);
|
|
13978
14390
|
for (const [name, value] of Object.entries(serviceMap ?? {})) {
|
|
13979
14391
|
const service = asRecord3(value);
|
|
@@ -14009,7 +14421,7 @@ function inspectServices(repoPath) {
|
|
|
14009
14421
|
}
|
|
14010
14422
|
function inspectEnvFiles(repoPath) {
|
|
14011
14423
|
const files = import_node_fs34.default.readdirSync(repoPath).filter((fileName) => /^\.env(\.|$)/.test(fileName)).sort().map((fileName) => {
|
|
14012
|
-
const content = import_node_fs34.default.readFileSync(
|
|
14424
|
+
const content = import_node_fs34.default.readFileSync(import_node_path41.default.join(repoPath, fileName), "utf-8");
|
|
14013
14425
|
const names = content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#") && line.includes("=")).map((line) => line.split("=")[0]?.trim()).filter((name) => Boolean(name)).sort();
|
|
14014
14426
|
return { path: fileName, names };
|
|
14015
14427
|
});
|
|
@@ -14023,13 +14435,13 @@ function inspectEnvFiles(repoPath) {
|
|
|
14023
14435
|
};
|
|
14024
14436
|
}
|
|
14025
14437
|
function inspectDevcontainer(repoPath) {
|
|
14026
|
-
const dir =
|
|
14438
|
+
const dir = import_node_path41.default.join(repoPath, ".devcontainer");
|
|
14027
14439
|
if (!import_node_fs34.default.existsSync(dir)) {
|
|
14028
14440
|
return { exists: false, files: [] };
|
|
14029
14441
|
}
|
|
14030
14442
|
return {
|
|
14031
14443
|
exists: true,
|
|
14032
|
-
files: import_node_fs34.default.readdirSync(dir).filter((fileName) => import_node_fs34.default.statSync(
|
|
14444
|
+
files: import_node_fs34.default.readdirSync(dir).filter((fileName) => import_node_fs34.default.statSync(import_node_path41.default.join(dir, fileName)).isFile()).sort().map((fileName) => `.devcontainer/${fileName}`)
|
|
14033
14445
|
};
|
|
14034
14446
|
}
|
|
14035
14447
|
function inspectDevrouter(repoPath) {
|
|
@@ -14078,14 +14490,14 @@ function inspectAgentGuidance(repoPath) {
|
|
|
14078
14490
|
["AGENTS.md", "agents"],
|
|
14079
14491
|
["CLAUDE.md", "claude"]
|
|
14080
14492
|
]) {
|
|
14081
|
-
if (import_node_fs34.default.existsSync(
|
|
14493
|
+
if (import_node_fs34.default.existsSync(import_node_path41.default.join(repoPath, fileName))) {
|
|
14082
14494
|
results.push({ path: fileName, kind });
|
|
14083
14495
|
}
|
|
14084
14496
|
}
|
|
14085
|
-
const skillsDir =
|
|
14497
|
+
const skillsDir = import_node_path41.default.join(repoPath, ".agents", "skills");
|
|
14086
14498
|
if (import_node_fs34.default.existsSync(skillsDir)) {
|
|
14087
14499
|
for (const name of import_node_fs34.default.readdirSync(skillsDir).sort()) {
|
|
14088
|
-
const skillPath =
|
|
14500
|
+
const skillPath = import_node_path41.default.join(skillsDir, name, "SKILL.md");
|
|
14089
14501
|
if (import_node_fs34.default.existsSync(skillPath)) {
|
|
14090
14502
|
results.push({ path: relative(repoPath, skillPath), kind: "skill" });
|
|
14091
14503
|
}
|
|
@@ -14129,7 +14541,7 @@ function buildIssues(report) {
|
|
|
14129
14541
|
}
|
|
14130
14542
|
function inspectRepo(options = {}) {
|
|
14131
14543
|
const repoPath = resolveRepoPath(options.repo);
|
|
14132
|
-
const pkg = readJson(
|
|
14544
|
+
const pkg = readJson(import_node_path41.default.join(repoPath, "package.json"));
|
|
14133
14545
|
const scripts = inspectScripts(pkg);
|
|
14134
14546
|
const reportWithoutIssues = {
|
|
14135
14547
|
repoPath,
|
|
@@ -14148,12 +14560,12 @@ function inspectRepo(options = {}) {
|
|
|
14148
14560
|
issues: buildIssues(reportWithoutIssues)
|
|
14149
14561
|
};
|
|
14150
14562
|
}
|
|
14151
|
-
var import_node_fs34,
|
|
14563
|
+
var import_node_fs34, import_node_path41, import_yaml6;
|
|
14152
14564
|
var init_repo_inspect = __esm({
|
|
14153
14565
|
"src/core/repo-inspect.ts"() {
|
|
14154
14566
|
"use strict";
|
|
14155
14567
|
import_node_fs34 = __toESM(require("fs"));
|
|
14156
|
-
|
|
14568
|
+
import_node_path41 = __toESM(require("path"));
|
|
14157
14569
|
import_yaml6 = __toESM(require("yaml"));
|
|
14158
14570
|
init_repo_config();
|
|
14159
14571
|
}
|
|
@@ -14258,7 +14670,7 @@ function requiredFileChecks(repoPath) {
|
|
|
14258
14670
|
".devcontainer/docker-compose.yml",
|
|
14259
14671
|
".devrouter.yml"
|
|
14260
14672
|
];
|
|
14261
|
-
const missing = required.filter((fileName) => !import_node_fs35.default.existsSync(
|
|
14673
|
+
const missing = required.filter((fileName) => !import_node_fs35.default.existsSync(import_node_path42.default.join(repoPath, fileName)));
|
|
14262
14674
|
return {
|
|
14263
14675
|
id: "repo.devcontainer.verify-files",
|
|
14264
14676
|
level: missing.length === 0 ? "ok" : "error",
|
|
@@ -14483,12 +14895,12 @@ async function verifyDevcontainer(options = {}) {
|
|
|
14483
14895
|
nextSteps: collectNextSteps3(checks)
|
|
14484
14896
|
};
|
|
14485
14897
|
}
|
|
14486
|
-
var import_node_fs35,
|
|
14898
|
+
var import_node_fs35, import_node_path42;
|
|
14487
14899
|
var init_devcontainer_verify = __esm({
|
|
14488
14900
|
"src/core/devcontainer-verify.ts"() {
|
|
14489
14901
|
"use strict";
|
|
14490
14902
|
import_node_fs35 = __toESM(require("fs"));
|
|
14491
|
-
|
|
14903
|
+
import_node_path42 = __toESM(require("path"));
|
|
14492
14904
|
init_capabilities();
|
|
14493
14905
|
init_doctor();
|
|
14494
14906
|
init_host_routes();
|
|
@@ -14785,7 +15197,7 @@ function packageManagerIssues(repo) {
|
|
|
14785
15197
|
}
|
|
14786
15198
|
function plannedFiles(repoPath, version) {
|
|
14787
15199
|
const repo = inspectRepo({ repo: repoPath });
|
|
14788
|
-
const projectName = sanitizeProjectName(
|
|
15200
|
+
const projectName = sanitizeProjectName(import_node_path43.default.basename(repo.repoPath));
|
|
14789
15201
|
const nodeMajor = majorVersion(repo.node?.version, "24");
|
|
14790
15202
|
const pnpmVersion = repo.packageManager?.name === "pnpm" && repo.packageManager.version ? repo.packageManager.version : DEFAULT_PNPM_VERSION;
|
|
14791
15203
|
const port = inferPort2(repo);
|
|
@@ -14830,7 +15242,7 @@ function plannedFiles(repoPath, version) {
|
|
|
14830
15242
|
};
|
|
14831
15243
|
}
|
|
14832
15244
|
function classifyFile(repoPath, file) {
|
|
14833
|
-
const absolutePath =
|
|
15245
|
+
const absolutePath = import_node_path43.default.join(repoPath, file.relativePath);
|
|
14834
15246
|
if (!import_node_fs36.default.existsSync(absolutePath)) {
|
|
14835
15247
|
return {
|
|
14836
15248
|
path: file.relativePath,
|
|
@@ -14896,8 +15308,8 @@ function buildPlan(repoPath, dryRun, version) {
|
|
|
14896
15308
|
};
|
|
14897
15309
|
}
|
|
14898
15310
|
function writeFile(repoPath, file) {
|
|
14899
|
-
const absolutePath =
|
|
14900
|
-
import_node_fs36.default.mkdirSync(
|
|
15311
|
+
const absolutePath = import_node_path43.default.join(repoPath, file.relativePath);
|
|
15312
|
+
import_node_fs36.default.mkdirSync(import_node_path43.default.dirname(absolutePath), { recursive: true });
|
|
14901
15313
|
import_node_fs36.default.writeFileSync(absolutePath, file.content, "utf-8");
|
|
14902
15314
|
if (file.executable) {
|
|
14903
15315
|
import_node_fs36.default.chmodSync(absolutePath, 493);
|
|
@@ -14938,12 +15350,12 @@ function writeDevcontainer(options = {}) {
|
|
|
14938
15350
|
nextSteps: postWriteNextSteps(repoPath)
|
|
14939
15351
|
};
|
|
14940
15352
|
}
|
|
14941
|
-
var import_node_fs36,
|
|
15353
|
+
var import_node_fs36, import_node_path43, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
|
|
14942
15354
|
var init_devcontainer_write = __esm({
|
|
14943
15355
|
"src/core/devcontainer-write.ts"() {
|
|
14944
15356
|
"use strict";
|
|
14945
15357
|
import_node_fs36 = __toESM(require("fs"));
|
|
14946
|
-
|
|
15358
|
+
import_node_path43 = __toESM(require("path"));
|
|
14947
15359
|
init_repo_config();
|
|
14948
15360
|
init_repo_inspect();
|
|
14949
15361
|
MANAGED_MARKER2 = "devrouter:managed devcontainer";
|
|
@@ -15277,7 +15689,7 @@ function buildProfilePlanReport(options) {
|
|
|
15277
15689
|
return { ...report, contractPath, bindings };
|
|
15278
15690
|
}
|
|
15279
15691
|
function loadContract(repoPath, contractPath) {
|
|
15280
|
-
if (
|
|
15692
|
+
if (import_node_path44.default.isAbsolute(contractPath)) {
|
|
15281
15693
|
fail3("profile plan contract path must be relative to the repository.");
|
|
15282
15694
|
}
|
|
15283
15695
|
const absolutePath = assertPathWithinRepo(contractPath, repoPath, "Profile plan contract");
|
|
@@ -15302,7 +15714,7 @@ function loadContract(repoPath, contractPath) {
|
|
|
15302
15714
|
}
|
|
15303
15715
|
return {
|
|
15304
15716
|
contract: parseProfilePlanContract(source),
|
|
15305
|
-
contractPath:
|
|
15717
|
+
contractPath: import_node_path44.default.relative(repoPath, absolutePath).split(import_node_path44.default.sep).join("/")
|
|
15306
15718
|
};
|
|
15307
15719
|
}
|
|
15308
15720
|
function resolveProfilePlan(options) {
|
|
@@ -15316,12 +15728,12 @@ function resolveProfilePlan(options) {
|
|
|
15316
15728
|
contractPath: loaded.contractPath
|
|
15317
15729
|
});
|
|
15318
15730
|
}
|
|
15319
|
-
var import_node_fs37,
|
|
15731
|
+
var import_node_fs37, import_node_path44, import_yaml7, CONTRACT_VERSION, MAX_CONTRACT_BYTES, MAX_APP_MAPPINGS, MAX_BINDING_KEYS, MAX_BINDING_VALUES, MAX_LITERAL_LENGTH, BINDING_KEY;
|
|
15320
15732
|
var init_profile_plan = __esm({
|
|
15321
15733
|
"src/core/profile-plan.ts"() {
|
|
15322
15734
|
"use strict";
|
|
15323
15735
|
import_node_fs37 = __toESM(require("fs"));
|
|
15324
|
-
|
|
15736
|
+
import_node_path44 = __toESM(require("path"));
|
|
15325
15737
|
import_yaml7 = require("yaml");
|
|
15326
15738
|
init_paths();
|
|
15327
15739
|
init_profile_resolution();
|
|
@@ -15353,7 +15765,7 @@ async function runProfileResolveCommand(options) {
|
|
|
15353
15765
|
async function runProfilePlanCommand(options) {
|
|
15354
15766
|
const report = resolveProfilePlan(options);
|
|
15355
15767
|
if (options.output) {
|
|
15356
|
-
writeFileAtomically(
|
|
15768
|
+
writeFileAtomically(import_node_path45.default.resolve(options.output), `${JSON.stringify(report, null, 2)}
|
|
15357
15769
|
`);
|
|
15358
15770
|
}
|
|
15359
15771
|
if (options.json) {
|
|
@@ -15389,14 +15801,14 @@ function printProfilePlanSummary(report, output2) {
|
|
|
15389
15801
|
process.stdout.write(`Binding ${key}: ${renderValues(report.bindings[key] ?? [])}
|
|
15390
15802
|
`);
|
|
15391
15803
|
}
|
|
15392
|
-
if (output2) process.stdout.write(`Output: ${
|
|
15804
|
+
if (output2) process.stdout.write(`Output: ${import_node_path45.default.resolve(output2)}
|
|
15393
15805
|
`);
|
|
15394
15806
|
}
|
|
15395
|
-
var
|
|
15807
|
+
var import_node_path45;
|
|
15396
15808
|
var init_profile = __esm({
|
|
15397
15809
|
"src/commands/profile.ts"() {
|
|
15398
15810
|
"use strict";
|
|
15399
|
-
|
|
15811
|
+
import_node_path45 = __toESM(require("path"));
|
|
15400
15812
|
init_atomic_file();
|
|
15401
15813
|
init_output();
|
|
15402
15814
|
init_profile_plan();
|
|
@@ -15686,7 +16098,7 @@ function sanitizeRouterId(value) {
|
|
|
15686
16098
|
return value.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
15687
16099
|
}
|
|
15688
16100
|
function repoHash(repoPath) {
|
|
15689
|
-
return (0, import_node_crypto17.createHash)("sha1").update(
|
|
16101
|
+
return (0, import_node_crypto17.createHash)("sha1").update(import_node_path46.default.resolve(repoPath)).digest("hex").slice(0, 12);
|
|
15690
16102
|
}
|
|
15691
16103
|
function asDockerApp(app) {
|
|
15692
16104
|
return app.runtime === "docker";
|
|
@@ -15765,9 +16177,9 @@ function prepareDockerOverlay(repoPath, appName, apps, publishTcpPorts = false)
|
|
|
15765
16177
|
if (dockerApps.length === 0) {
|
|
15766
16178
|
throw new Error("No docker apps selected to prepare compose overlay.");
|
|
15767
16179
|
}
|
|
15768
|
-
const cachePath =
|
|
16180
|
+
const cachePath = import_node_path46.default.join(CACHE_DIR, repoHash(repoPath), sanitizeRouterId(appName));
|
|
15769
16181
|
import_node_fs38.default.mkdirSync(cachePath, { recursive: true });
|
|
15770
|
-
const overlayPath =
|
|
16182
|
+
const overlayPath = import_node_path46.default.join(cachePath, "compose.devrouter.yml");
|
|
15771
16183
|
const overlayDocument = buildOverlayDocument(dockerApps, publishTcpPorts);
|
|
15772
16184
|
import_node_fs38.default.writeFileSync(overlayPath, import_yaml8.default.stringify(overlayDocument, { lineWidth: 0 }), "utf-8");
|
|
15773
16185
|
return {
|
|
@@ -15889,14 +16301,14 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
|
|
|
15889
16301
|
const port = Number(match[1]);
|
|
15890
16302
|
return Number.isInteger(port) && port > 0 ? port : void 0;
|
|
15891
16303
|
}
|
|
15892
|
-
var import_node_child_process27, import_node_crypto17, import_node_fs38,
|
|
16304
|
+
var import_node_child_process27, import_node_crypto17, import_node_fs38, import_node_path46, import_yaml8;
|
|
15893
16305
|
var init_docker_run = __esm({
|
|
15894
16306
|
"src/core/docker-run.ts"() {
|
|
15895
16307
|
"use strict";
|
|
15896
16308
|
import_node_child_process27 = require("child_process");
|
|
15897
16309
|
import_node_crypto17 = require("crypto");
|
|
15898
16310
|
import_node_fs38 = __toESM(require("fs"));
|
|
15899
|
-
|
|
16311
|
+
import_node_path46 = __toESM(require("path"));
|
|
15900
16312
|
import_yaml8 = __toESM(require("yaml"));
|
|
15901
16313
|
init_docker_error_guidance();
|
|
15902
16314
|
init_paths();
|
|
@@ -16750,7 +17162,7 @@ function measureWorktreeConsumption(worktreePath, options) {
|
|
|
16750
17162
|
timedOut = true;
|
|
16751
17163
|
break;
|
|
16752
17164
|
}
|
|
16753
|
-
const entryPath =
|
|
17165
|
+
const entryPath = import_node_path47.default.join(dirPath, entry.name);
|
|
16754
17166
|
let entryStat;
|
|
16755
17167
|
try {
|
|
16756
17168
|
entryStat = import_node_fs39.default.lstatSync(entryPath);
|
|
@@ -16827,12 +17239,12 @@ function describeError(error, worktreePath) {
|
|
|
16827
17239
|
}
|
|
16828
17240
|
return `could not stat worktree path '${worktreePath}': ${error?.message ?? String(error)}`;
|
|
16829
17241
|
}
|
|
16830
|
-
var import_node_fs39,
|
|
17242
|
+
var import_node_fs39, import_node_path47, DEFAULT_DEADLINE_MS, BLOCK_SIZE_BYTES;
|
|
16831
17243
|
var init_workspace_consumption = __esm({
|
|
16832
17244
|
"src/core/workspace-consumption.ts"() {
|
|
16833
17245
|
"use strict";
|
|
16834
17246
|
import_node_fs39 = __toESM(require("fs"));
|
|
16835
|
-
|
|
17247
|
+
import_node_path47 = __toESM(require("path"));
|
|
16836
17248
|
init_devpod_environment();
|
|
16837
17249
|
DEFAULT_DEADLINE_MS = 1e4;
|
|
16838
17250
|
BLOCK_SIZE_BYTES = 512;
|
|
@@ -17082,8 +17494,8 @@ function inspectWorkspaceIntegration(repoPath, snapshot, branch, checkMerged, co
|
|
|
17082
17494
|
return { status: "unknown", reason: "Current HEAD or branch evidence is unavailable." };
|
|
17083
17495
|
}
|
|
17084
17496
|
const originUrl = gitOutput(repoPath, ["config", "--get", "remote.origin.url"], commandRunner);
|
|
17085
|
-
const
|
|
17086
|
-
if (!
|
|
17497
|
+
const identity2 = originUrl ? parseRemoteIdentity(originUrl) : void 0;
|
|
17498
|
+
if (!identity2) {
|
|
17087
17499
|
return { status: "unknown", reason: "The origin is missing or uses an unsupported forge." };
|
|
17088
17500
|
}
|
|
17089
17501
|
const localTargetBranch = parseTargetBranch(
|
|
@@ -17128,11 +17540,11 @@ function inspectWorkspaceIntegration(repoPath, snapshot, branch, checkMerged, co
|
|
|
17128
17540
|
};
|
|
17129
17541
|
}
|
|
17130
17542
|
const forgeChanges = [];
|
|
17131
|
-
const forgeCommand =
|
|
17543
|
+
const forgeCommand = identity2.provider === "github" ? [
|
|
17132
17544
|
"pr",
|
|
17133
17545
|
"list",
|
|
17134
17546
|
"--repo",
|
|
17135
|
-
|
|
17547
|
+
identity2.project,
|
|
17136
17548
|
"--state",
|
|
17137
17549
|
"all",
|
|
17138
17550
|
"--head",
|
|
@@ -17143,20 +17555,20 @@ function inspectWorkspaceIntegration(repoPath, snapshot, branch, checkMerged, co
|
|
|
17143
17555
|
"mr",
|
|
17144
17556
|
"list",
|
|
17145
17557
|
"--repo",
|
|
17146
|
-
|
|
17558
|
+
identity2.project,
|
|
17147
17559
|
"--all",
|
|
17148
17560
|
"--source-branch",
|
|
17149
17561
|
branch,
|
|
17150
17562
|
"--output",
|
|
17151
17563
|
"json"
|
|
17152
17564
|
];
|
|
17153
|
-
const forge = commandRunner(
|
|
17565
|
+
const forge = commandRunner(identity2.provider === "github" ? "gh" : "glab", forgeCommand);
|
|
17154
17566
|
if (forge.status !== 0 || forge.error) {
|
|
17155
17567
|
return { status: "unknown", reason: "The forge query was unavailable or unauthenticated." };
|
|
17156
17568
|
}
|
|
17157
17569
|
try {
|
|
17158
17570
|
const parsed = JSON.parse(forge.stdout);
|
|
17159
|
-
const changes =
|
|
17571
|
+
const changes = identity2.provider === "github" ? parseGitHubChanges(parsed, identity2.project, branch) : parseGitLabChanges(parsed, identity2.project, branch);
|
|
17160
17572
|
if (!changes) {
|
|
17161
17573
|
return { status: "unknown", reason: "The forge response was malformed." };
|
|
17162
17574
|
}
|
|
@@ -17222,31 +17634,31 @@ function inspectWorkspaceIntegration(repoPath, snapshot, branch, checkMerged, co
|
|
|
17222
17634
|
reason: "Fresh target and forge evidence did not prove integration."
|
|
17223
17635
|
};
|
|
17224
17636
|
}
|
|
17225
|
-
function routeStatus(
|
|
17637
|
+
function routeStatus(record4, routes) {
|
|
17226
17638
|
if (!routes) return "unknown";
|
|
17227
17639
|
const matchingPath = routes.filter(
|
|
17228
|
-
(route) => sameWorkspacePath(route.repoPath,
|
|
17640
|
+
(route) => sameWorkspacePath(route.repoPath, record4.worktreePath)
|
|
17229
17641
|
);
|
|
17230
17642
|
if (matchingPath.length === 0) return "absent";
|
|
17231
|
-
return matchingPath.every((route) => route.workspace ===
|
|
17643
|
+
return matchingPath.every((route) => route.workspace === record4.workspace) ? "owned" : "conflict";
|
|
17232
17644
|
}
|
|
17233
|
-
function buildActivityEvidence(
|
|
17645
|
+
function buildActivityEvidence(record4, providerStatus, provider, routeEntries, git) {
|
|
17234
17646
|
const providerEvidence = providerStatus === "unknown" || providerStatus === "conflict" ? { source: "devpod.lastUsed", status: "unavailable" } : providerStatus === "absent" ? { source: "devpod.lastUsed", status: "not-applicable" } : provider?.lastUsedMalformed ? { source: "devpod.lastUsed", status: "malformed" } : provider?.lastUsed ? validTimestamp(provider.lastUsed) ? { source: "devpod.lastUsed", status: "valid", timestamp: provider.lastUsed } : { source: "devpod.lastUsed", status: "malformed" } : { source: "devpod.lastUsed", status: "missing" };
|
|
17235
17647
|
const routeTimestamps = routeEntries?.map((route) => route.updatedAt) ?? [];
|
|
17236
17648
|
const malformedRoute = routeTimestamps.some((timestamp) => !validTimestamp(timestamp));
|
|
17237
17649
|
const newestRoute = routeTimestamps.filter(validTimestamp).sort((left, right) => (parseTimestamp(right) ?? 0) - (parseTimestamp(left) ?? 0))[0];
|
|
17238
17650
|
const routeEvidence = !routeEntries ? { source: "route.updatedAt", status: "unavailable" } : routeEntries.length === 0 ? { source: "route.updatedAt", status: "not-applicable" } : malformedRoute ? { source: "route.updatedAt", status: "malformed" } : newestRoute ? { source: "route.updatedAt", status: "valid", timestamp: newestRoute } : { source: "route.updatedAt", status: "malformed" };
|
|
17239
|
-
const ownerEvidence = validTimestamp(
|
|
17651
|
+
const ownerEvidence = validTimestamp(record4.updatedAt) ? { source: "ownership.updatedAt", status: "valid", timestamp: record4.updatedAt } : { source: "ownership.updatedAt", status: "malformed" };
|
|
17240
17652
|
const gitEvidence = git.checkout === "missing" ? { source: "git.headCommitterDate", status: "not-applicable" } : git.committerDate && validTimestamp(git.committerDate) ? { source: "git.headCommitterDate", status: "valid", timestamp: git.committerDate } : git.checkout === "unknown" ? { source: "git.headCommitterDate", status: "unavailable" } : { source: "git.headCommitterDate", status: "malformed" };
|
|
17241
17653
|
return [providerEvidence, routeEvidence, ownerEvidence, gitEvidence];
|
|
17242
17654
|
}
|
|
17243
|
-
function buildSuggestions(repoPath,
|
|
17655
|
+
function buildSuggestions(repoPath, record4, ownership, provider, runtime, checkout, route, activity, integration, worktree, checkMerged) {
|
|
17244
17656
|
const reasons = [];
|
|
17245
17657
|
const eligibleActions = [];
|
|
17246
17658
|
const suggestions = [];
|
|
17247
17659
|
const gcCommand = workspaceCommand(repoPath, ["gc"], [], ["--yes"]);
|
|
17248
|
-
const keepCommand = workspaceCommand(repoPath, ["down",
|
|
17249
|
-
const downCommand = workspaceCommand(repoPath, ["down",
|
|
17660
|
+
const keepCommand = workspaceCommand(repoPath, ["down", record4.workspace], ["--keep-worktree"]);
|
|
17661
|
+
const downCommand = workspaceCommand(repoPath, ["down", record4.workspace]);
|
|
17250
17662
|
const routeSafe = route === "owned" || route === "absent";
|
|
17251
17663
|
const checkoutSafe = checkout === "clean" && !worktree?.locked;
|
|
17252
17664
|
const providerSafe = provider === "owned";
|
|
@@ -17324,17 +17736,17 @@ function buildSuggestions(repoPath, record3, ownership, provider, runtime, check
|
|
|
17324
17736
|
}
|
|
17325
17737
|
return { eligibleActions, suggestions, reasons };
|
|
17326
17738
|
}
|
|
17327
|
-
function buildRow(repoPath,
|
|
17328
|
-
const ownershipEvidence = inspectOwnershipFn(
|
|
17739
|
+
function buildRow(repoPath, record4, worktrees, devpods, routes, snapshot, cutoff, checkMerged, commandRunner, inspectOwnershipFn, inspectIntegrationFn, inspectRuntimeFn) {
|
|
17740
|
+
const ownershipEvidence = inspectOwnershipFn(record4, worktrees, devpods);
|
|
17329
17741
|
const providerOwnership = devpods?.find(
|
|
17330
|
-
(devpod) => devpod.id ===
|
|
17742
|
+
(devpod) => devpod.id === record4.devpodId && sameWorkspacePath(devpod.source.localFolder, record4.worktreePath)
|
|
17331
17743
|
);
|
|
17332
17744
|
const matchingRoutes = routes?.filter(
|
|
17333
|
-
(route) => sameWorkspacePath(route.repoPath,
|
|
17745
|
+
(route) => sameWorkspacePath(route.repoPath, record4.worktreePath)
|
|
17334
17746
|
);
|
|
17335
17747
|
const activity = evaluateWorkspaceActivity(
|
|
17336
17748
|
buildActivityEvidence(
|
|
17337
|
-
|
|
17749
|
+
record4,
|
|
17338
17750
|
ownershipEvidence.devpodStatus,
|
|
17339
17751
|
providerOwnership,
|
|
17340
17752
|
matchingRoutes,
|
|
@@ -17342,23 +17754,23 @@ function buildRow(repoPath, record3, worktrees, devpods, routes, snapshot, cutof
|
|
|
17342
17754
|
),
|
|
17343
17755
|
cutoff
|
|
17344
17756
|
);
|
|
17345
|
-
const branch = snapshot.checkout === "detached" ? null : snapshot.worktree.branch ??
|
|
17757
|
+
const branch = snapshot.checkout === "detached" ? null : snapshot.worktree.branch ?? record4.branch;
|
|
17346
17758
|
const integration = inspectIntegrationFn?.(repoPath, snapshot, branch, checkMerged) ?? inspectWorkspaceIntegration(repoPath, snapshot, branch, checkMerged, commandRunner);
|
|
17347
17759
|
const runtime = ownershipEvidence.devpodStatus === "absent" ? "absent" : ownershipEvidence.devpodStatus === "owned" ? (() => {
|
|
17348
17760
|
try {
|
|
17349
|
-
return inspectRuntimeFn(
|
|
17761
|
+
return inspectRuntimeFn(record4.devpodId);
|
|
17350
17762
|
} catch {
|
|
17351
17763
|
return "unknown";
|
|
17352
17764
|
}
|
|
17353
17765
|
})() : "unknown";
|
|
17354
17766
|
const suggestions = buildSuggestions(
|
|
17355
17767
|
repoPath,
|
|
17356
|
-
|
|
17768
|
+
record4,
|
|
17357
17769
|
ownershipEvidence.ownerStatus,
|
|
17358
17770
|
ownershipEvidence.devpodStatus,
|
|
17359
17771
|
runtime,
|
|
17360
17772
|
snapshot.checkout,
|
|
17361
|
-
routeStatus(
|
|
17773
|
+
routeStatus(record4, routes),
|
|
17362
17774
|
activity.status,
|
|
17363
17775
|
integration,
|
|
17364
17776
|
snapshot.worktree,
|
|
@@ -17369,7 +17781,7 @@ function buildRow(repoPath, record3, worktrees, devpods, routes, snapshot, cutof
|
|
|
17369
17781
|
`provider=${ownershipEvidence.devpodStatus}`,
|
|
17370
17782
|
`runtime=${runtime}`,
|
|
17371
17783
|
`checkout=${snapshot.checkout}`,
|
|
17372
|
-
`route=${routeStatus(
|
|
17784
|
+
`route=${routeStatus(record4, routes)}`,
|
|
17373
17785
|
`activity=${activity.status}`,
|
|
17374
17786
|
`integration=${integration.status}`,
|
|
17375
17787
|
...integration.reason ? [integration.reason] : [],
|
|
@@ -17377,16 +17789,16 @@ function buildRow(repoPath, record3, worktrees, devpods, routes, snapshot, cutof
|
|
|
17377
17789
|
];
|
|
17378
17790
|
return {
|
|
17379
17791
|
schemaVersion: 2,
|
|
17380
|
-
workspace:
|
|
17792
|
+
workspace: record4.workspace,
|
|
17381
17793
|
branch,
|
|
17382
17794
|
repo: repoPath,
|
|
17383
|
-
worktreePath:
|
|
17384
|
-
devpodId:
|
|
17795
|
+
worktreePath: record4.worktreePath,
|
|
17796
|
+
devpodId: record4.devpodId,
|
|
17385
17797
|
ownership: ownershipEvidence.ownerStatus,
|
|
17386
17798
|
provider: ownershipEvidence.devpodStatus,
|
|
17387
17799
|
runtime,
|
|
17388
17800
|
checkout: snapshot.checkout,
|
|
17389
|
-
route: routeStatus(
|
|
17801
|
+
route: routeStatus(record4, routes),
|
|
17390
17802
|
activity: activity.status,
|
|
17391
17803
|
cutoff,
|
|
17392
17804
|
latestTimestamp: activity.latestTimestamp,
|
|
@@ -17453,30 +17865,30 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
|
|
|
17453
17865
|
const measureContainersFn = dependencies.measureContainers ?? measureContainerConsumption;
|
|
17454
17866
|
let containers2 = /* @__PURE__ */ new Map();
|
|
17455
17867
|
if (measureSize) {
|
|
17456
|
-
const worktreePaths = records.map((
|
|
17868
|
+
const worktreePaths = records.map((record4) => record4.worktreePath);
|
|
17457
17869
|
try {
|
|
17458
17870
|
containers2 = measureContainersFn(worktreePaths);
|
|
17459
17871
|
} catch (error) {
|
|
17460
17872
|
const reason = `container measurement failed: ${describeCause(error)}`;
|
|
17461
|
-
containers2 = new Map(worktreePaths.map((
|
|
17873
|
+
containers2 = new Map(worktreePaths.map((path48) => [path48, unknownContainers(reason)]));
|
|
17462
17874
|
}
|
|
17463
17875
|
}
|
|
17464
|
-
const rows = records.map((
|
|
17876
|
+
const rows = records.map((record4) => {
|
|
17465
17877
|
const worktree = worktrees.find(
|
|
17466
|
-
(candidate) => sameWorkspacePath(candidate.path,
|
|
17878
|
+
(candidate) => sameWorkspacePath(candidate.path, record4.worktreePath)
|
|
17467
17879
|
);
|
|
17468
17880
|
const snapshot = (dependencies.readGitSnapshot ?? ((candidate) => readGitSnapshot(candidate, commandRunner)))(
|
|
17469
17881
|
worktree ?? {
|
|
17470
|
-
path:
|
|
17471
|
-
branch:
|
|
17882
|
+
path: record4.worktreePath,
|
|
17883
|
+
branch: record4.branch ?? void 0,
|
|
17472
17884
|
locked: false,
|
|
17473
17885
|
prunable: true
|
|
17474
17886
|
}
|
|
17475
17887
|
);
|
|
17476
|
-
const devpods = dependencies.listDevpods ? dependencies.listDevpods() : listDevpodWorkspacesFromSnapshots(
|
|
17888
|
+
const devpods = dependencies.listDevpods ? dependencies.listDevpods() : listDevpodWorkspacesFromSnapshots(record4.worktreePath);
|
|
17477
17889
|
const row = buildRow(
|
|
17478
17890
|
repoPath,
|
|
17479
|
-
|
|
17891
|
+
record4,
|
|
17480
17892
|
worktrees,
|
|
17481
17893
|
devpods,
|
|
17482
17894
|
routes,
|
|
@@ -17489,11 +17901,11 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
|
|
|
17489
17901
|
return { ownerStatus: status.ownerStatus, devpodStatus: status.devpodStatus };
|
|
17490
17902
|
}),
|
|
17491
17903
|
dependencies.inspectIntegration,
|
|
17492
|
-
dependencies.inspectDevpodRuntime ?? ((devpodId) => inspectDevpodRuntimeStatus(devpodId,
|
|
17904
|
+
dependencies.inspectDevpodRuntime ?? ((devpodId) => inspectDevpodRuntimeStatus(devpodId, record4.worktreePath))
|
|
17493
17905
|
);
|
|
17494
17906
|
return measureSize ? {
|
|
17495
17907
|
...row,
|
|
17496
|
-
consumption: collectConsumption(
|
|
17908
|
+
consumption: collectConsumption(record4.worktreePath, measureWorktreeFn, containers2)
|
|
17497
17909
|
} : row;
|
|
17498
17910
|
}).sort((left, right) => left.workspace.localeCompare(right.workspace));
|
|
17499
17911
|
return {
|
|
@@ -17559,7 +17971,7 @@ function warnMissingWorkspaceOwnership(repoPath) {
|
|
|
17559
17971
|
);
|
|
17560
17972
|
}
|
|
17561
17973
|
function defaultWorktreePath(mainRepo, ws) {
|
|
17562
|
-
return
|
|
17974
|
+
return import_node_path48.default.join(mainRepo, "trees", ws);
|
|
17563
17975
|
}
|
|
17564
17976
|
function assertDefaultWorktreeRootIgnored(mainRepo) {
|
|
17565
17977
|
const ignored = (0, import_node_child_process30.spawnSync)("git", ["-C", mainRepo, "check-ignore", "-q", "--no-index", "trees/"], {
|
|
@@ -17567,12 +17979,12 @@ function assertDefaultWorktreeRootIgnored(mainRepo) {
|
|
|
17567
17979
|
});
|
|
17568
17980
|
if (ignored.status !== 0) {
|
|
17569
17981
|
throw new Error(
|
|
17570
|
-
`Default worktree root '${
|
|
17982
|
+
`Default worktree root '${import_node_path48.default.join(mainRepo, "trees")}' is not ignored. Add 'trees/' to '${import_node_path48.default.join(mainRepo, ".gitignore")}' or use --path.`
|
|
17571
17983
|
);
|
|
17572
17984
|
}
|
|
17573
17985
|
}
|
|
17574
17986
|
function legacyDefaultWorktreePath(mainRepo, ws) {
|
|
17575
|
-
return
|
|
17987
|
+
return import_node_path48.default.join(import_node_path48.default.dirname(mainRepo), `${import_node_path48.default.basename(mainRepo)}-${ws}`);
|
|
17576
17988
|
}
|
|
17577
17989
|
function teardownFallbackPath(mainRepo, workspace) {
|
|
17578
17990
|
const candidates = [
|
|
@@ -17625,7 +18037,7 @@ function resolveWorkspaceTarget(mainRepo, target, worktrees, records) {
|
|
|
17625
18037
|
}
|
|
17626
18038
|
const liveRecord = oneRecordMatch(
|
|
17627
18039
|
target,
|
|
17628
|
-
records.filter((
|
|
18040
|
+
records.filter((record5) => sameWorkspacePath(record5.worktreePath, liveBranchWorktree.path))
|
|
17629
18041
|
);
|
|
17630
18042
|
return {
|
|
17631
18043
|
workspace: liveRecord?.workspace ?? liveWorkspace,
|
|
@@ -17634,13 +18046,13 @@ function resolveWorkspaceTarget(mainRepo, target, worktrees, records) {
|
|
|
17634
18046
|
record: liveRecord
|
|
17635
18047
|
};
|
|
17636
18048
|
}
|
|
17637
|
-
const
|
|
17638
|
-
if (
|
|
18049
|
+
const record4 = records.find((candidate) => candidate.workspace === requestedWorkspace);
|
|
18050
|
+
if (record4) {
|
|
17639
18051
|
return {
|
|
17640
|
-
workspace:
|
|
17641
|
-
worktreePath:
|
|
17642
|
-
worktree: worktreeForRecord(worktrees,
|
|
17643
|
-
record:
|
|
18052
|
+
workspace: record4.workspace,
|
|
18053
|
+
worktreePath: record4.worktreePath,
|
|
18054
|
+
worktree: worktreeForRecord(worktrees, record4),
|
|
18055
|
+
record: record4
|
|
17644
18056
|
};
|
|
17645
18057
|
}
|
|
17646
18058
|
const linked = worktrees.filter((worktree2) => worktree2.workspace !== void 0);
|
|
@@ -17661,8 +18073,8 @@ function resolveWorkspaceTarget(mainRepo, target, worktrees, records) {
|
|
|
17661
18073
|
record: void 0
|
|
17662
18074
|
};
|
|
17663
18075
|
}
|
|
17664
|
-
function worktreeForRecord(worktrees,
|
|
17665
|
-
return worktrees.find((candidate) => sameWorkspacePath(candidate.path,
|
|
18076
|
+
function worktreeForRecord(worktrees, record4) {
|
|
18077
|
+
return worktrees.find((candidate) => sameWorkspacePath(candidate.path, record4.worktreePath));
|
|
17666
18078
|
}
|
|
17667
18079
|
function assertDevpodTargetSafe(target, worktrees, devpods) {
|
|
17668
18080
|
if (target.record) {
|
|
@@ -17726,7 +18138,7 @@ async function workspaceUp(branch, opts = {}) {
|
|
|
17726
18138
|
const generatedPath = candidatePaths.find(
|
|
17727
18139
|
(candidate) => !import_node_fs41.default.existsSync(candidate) && !worktrees.some((worktree) => sameWorkspacePath(worktree.path, candidate))
|
|
17728
18140
|
);
|
|
17729
|
-
const selectedPath = opts.path ?
|
|
18141
|
+
const selectedPath = opts.path ? import_node_path48.default.resolve(opts.path) : existingBranch?.path ?? generatedPath;
|
|
17730
18142
|
if (!selectedPath) {
|
|
17731
18143
|
throw new Error(`Could not allocate a collision-safe worktree path for branch '${branch}'.`);
|
|
17732
18144
|
}
|
|
@@ -17799,26 +18211,26 @@ function workspaceLs(repoPath) {
|
|
|
17799
18211
|
const paths = Array.from(
|
|
17800
18212
|
/* @__PURE__ */ new Set([
|
|
17801
18213
|
...worktrees.map((worktree) => worktree.path),
|
|
17802
|
-
...records.map((
|
|
18214
|
+
...records.map((record4) => record4.worktreePath)
|
|
17803
18215
|
])
|
|
17804
18216
|
);
|
|
17805
18217
|
const routesByWorktreePath = listRoutesForWorktreePaths(paths);
|
|
17806
18218
|
const worktreesByPath = new Map(worktrees.map((worktree) => [worktree.path, worktree]));
|
|
17807
|
-
const recordsByPath = new Map(records.map((
|
|
18219
|
+
const recordsByPath = new Map(records.map((record4) => [record4.worktreePath, record4]));
|
|
17808
18220
|
return paths.map((worktreePath) => {
|
|
17809
18221
|
const worktree = worktreesByPath.get(worktreePath);
|
|
17810
|
-
const
|
|
18222
|
+
const record4 = recordsByPath.get(worktreePath);
|
|
17811
18223
|
const devpods = devpodsForPath(worktreePath);
|
|
17812
|
-
const status =
|
|
18224
|
+
const status = record4 ? inspectWorkspaceOwnership(record4, worktrees, devpods) : void 0;
|
|
17813
18225
|
const matchingDevpods = devpods?.filter(
|
|
17814
18226
|
(devpod) => sameWorkspacePath(devpod.source.localFolder, worktreePath)
|
|
17815
18227
|
);
|
|
17816
18228
|
const wsRoutes = routesByWorktreePath.get(worktreePath) ?? [];
|
|
17817
18229
|
return {
|
|
17818
|
-
workspace:
|
|
17819
|
-
branch: worktree?.branch ??
|
|
18230
|
+
workspace: record4?.workspace ?? worktree?.workspace,
|
|
18231
|
+
branch: worktree?.branch ?? record4?.branch ?? void 0,
|
|
17820
18232
|
worktreePath,
|
|
17821
|
-
legacy: !
|
|
18233
|
+
legacy: !record4 && worktree?.workspace !== void 0,
|
|
17822
18234
|
ownerStatus: status?.ownerStatus,
|
|
17823
18235
|
devpodStatus: status?.devpodStatus ?? (devpods === void 0 ? "unknown" : matchingDevpods?.length === 0 ? "absent" : matchingDevpods?.length === 1 ? "owned" : "conflict"),
|
|
17824
18236
|
routeCount: wsRoutes.length,
|
|
@@ -17902,13 +18314,13 @@ async function workspaceStop(target, opts = {}) {
|
|
|
17902
18314
|
async function workspaceDown(target, opts = {}) {
|
|
17903
18315
|
return runWorkspaceLifecycle("down", target, opts);
|
|
17904
18316
|
}
|
|
17905
|
-
var import_node_child_process30, import_node_fs41,
|
|
18317
|
+
var import_node_child_process30, import_node_fs41, import_node_path48, WORKSPACE_ALLOCATION_LOCK_WAIT_MS;
|
|
17906
18318
|
var init_workspace_lifecycle = __esm({
|
|
17907
18319
|
"src/core/workspace-lifecycle.ts"() {
|
|
17908
18320
|
"use strict";
|
|
17909
18321
|
import_node_child_process30 = require("child_process");
|
|
17910
18322
|
import_node_fs41 = __toESM(require("fs"));
|
|
17911
|
-
|
|
18323
|
+
import_node_path48 = __toESM(require("path"));
|
|
17912
18324
|
init_devpod_mutation();
|
|
17913
18325
|
init_devpod_workspaces();
|
|
17914
18326
|
init_managed_runtime_state();
|
|
@@ -18067,7 +18479,7 @@ var init_version = __esm({
|
|
|
18067
18479
|
|
|
18068
18480
|
// src/cli.ts
|
|
18069
18481
|
var import_commander = require("commander");
|
|
18070
|
-
var CLI_VERSION = true ? "0.0.
|
|
18482
|
+
var CLI_VERSION = true ? "0.0.61" : "0.0.0-dev";
|
|
18071
18483
|
var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
|
|
18072
18484
|
function withErrorHandling(action2) {
|
|
18073
18485
|
return async (...args) => {
|