@devrouter/cli 0.0.60 → 0.0.62
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 +3487 -2817
- package/dist/devrouter.js +1177 -601
- package/package.json +2 -1
- package/upgrade-prompts/0.0.61.md +40 -0
- package/upgrade-prompts/0.0.62.md +35 -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.62" : "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,121 @@ 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 resolveManagedStopEndpoint() {
|
|
4562
|
+
const endpoint = process.env.DOCKER_HOST && !process.env.DOCKER_CONTEXT ? process.env.DOCKER_HOST : parseManagedStopJson(
|
|
4563
|
+
runManagedStopDocker([
|
|
4564
|
+
"context",
|
|
4565
|
+
"inspect",
|
|
4566
|
+
"--format",
|
|
4567
|
+
"{{json .Endpoints.docker.Host}}"
|
|
4568
|
+
])
|
|
4569
|
+
);
|
|
4570
|
+
if (typeof endpoint !== "string") throw new Error("Docker endpoint is unavailable.");
|
|
4571
|
+
supportsManagedStopBaseline(endpoint);
|
|
4572
|
+
return endpoint;
|
|
4573
|
+
}
|
|
4574
|
+
function inspectManagedStopDaemon(endpoint) {
|
|
4575
|
+
const value = parseManagedStopJson(
|
|
4576
|
+
runManagedStopDocker(["info", "--format", "{{json .ID}}"], endpoint)
|
|
4577
|
+
);
|
|
4578
|
+
if (typeof value !== "string" || !value.length || value.length > 256 || /\s/.test(value) || [...value].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127)) {
|
|
4579
|
+
throw new Error("Docker daemon identity is unavailable.");
|
|
4580
|
+
}
|
|
4581
|
+
return value;
|
|
4582
|
+
}
|
|
4583
|
+
function inspectManagedStopRunnerId(endpoint, containerId) {
|
|
4584
|
+
assertFullContainerId(containerId);
|
|
4585
|
+
const value = parseManagedStopJson(
|
|
4586
|
+
runManagedStopDocker(
|
|
4587
|
+
["inspect", "--format", '{{json (index .Config.Labels "dev.containers.id")}}', containerId],
|
|
4588
|
+
endpoint
|
|
4589
|
+
)
|
|
4590
|
+
);
|
|
4591
|
+
if (typeof value !== "string" || value.length > 4096)
|
|
4592
|
+
throw new Error("Provider container binding is unavailable.");
|
|
4593
|
+
return value;
|
|
4594
|
+
}
|
|
4595
|
+
function inspectProviderRunnerContainers(endpoint, runnerId) {
|
|
4596
|
+
if (!runnerId || runnerId.length > 4096 || /[\r\n\0]/.test(runnerId))
|
|
4597
|
+
throw new Error("Provider runner identity is malformed.");
|
|
4598
|
+
const ids = parseDockerLines(
|
|
4599
|
+
runManagedStopDocker(
|
|
4600
|
+
[
|
|
4601
|
+
"ps",
|
|
4602
|
+
"-a",
|
|
4603
|
+
"--no-trunc",
|
|
4604
|
+
"--filter",
|
|
4605
|
+
`label=dev.containers.id=${runnerId}`,
|
|
4606
|
+
"--format",
|
|
4607
|
+
"{{.ID}}"
|
|
4608
|
+
],
|
|
4609
|
+
endpoint
|
|
4610
|
+
)
|
|
4611
|
+
);
|
|
4612
|
+
if (ids.length > 256) throw new Error("Provider runner population exceeds its bound.");
|
|
4613
|
+
ids.forEach(assertFullContainerId);
|
|
4614
|
+
assertUniqueContainerIds(ids);
|
|
4615
|
+
return ids;
|
|
4616
|
+
}
|
|
4617
|
+
function inspectManagedStopWorkspaceIds(endpoint, composeDirectory) {
|
|
4618
|
+
const ids = parseDockerLines(
|
|
4619
|
+
runManagedStopDocker(
|
|
4620
|
+
[
|
|
4621
|
+
"ps",
|
|
4622
|
+
"-a",
|
|
4623
|
+
"--no-trunc",
|
|
4624
|
+
"--filter",
|
|
4625
|
+
`label=com.docker.compose.project.working_dir=${composeDirectory}`,
|
|
4626
|
+
"--format",
|
|
4627
|
+
"{{.ID}}"
|
|
4628
|
+
],
|
|
4629
|
+
endpoint
|
|
4630
|
+
)
|
|
4631
|
+
);
|
|
4632
|
+
if (ids.length > 256) throw new Error("Managed stop population exceeds its bound.");
|
|
4633
|
+
ids.forEach(assertFullContainerId);
|
|
4634
|
+
assertUniqueContainerIds(ids);
|
|
4635
|
+
return ids;
|
|
4636
|
+
}
|
|
4637
|
+
function stopPinnedManagedContainer(endpoint, containerId) {
|
|
4638
|
+
assertFullContainerId(containerId);
|
|
4639
|
+
runManagedStopDocker(["stop", containerId], endpoint, 3e4);
|
|
4640
|
+
}
|
|
4517
4641
|
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
4642
|
var init_devpod_environment = __esm({
|
|
4519
4643
|
"src/core/devpod-environment.ts"() {
|
|
@@ -5084,12 +5208,147 @@ var init_http_route_probe = __esm({
|
|
|
5084
5208
|
}
|
|
5085
5209
|
});
|
|
5086
5210
|
|
|
5211
|
+
// src/core/managed-stop-baseline.ts
|
|
5212
|
+
function record2(value, keys2) {
|
|
5213
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).length !== keys2.length || Object.keys(value).some((key) => !keys2.includes(key))) {
|
|
5214
|
+
throw new Error("Invalid managed stop baseline fields.");
|
|
5215
|
+
}
|
|
5216
|
+
return value;
|
|
5217
|
+
}
|
|
5218
|
+
function text(value, empty = false) {
|
|
5219
|
+
if (typeof value !== "string" || value.length > 4096 || !empty && !value.length || value.trim() !== value || [...value].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127)) {
|
|
5220
|
+
throw new Error("Invalid managed stop baseline identity.");
|
|
5221
|
+
}
|
|
5222
|
+
return value;
|
|
5223
|
+
}
|
|
5224
|
+
function absolute(value) {
|
|
5225
|
+
const result = text(value);
|
|
5226
|
+
if (!import_node_path16.default.isAbsolute(result) || import_node_path16.default.normalize(result) !== result) {
|
|
5227
|
+
throw new Error("Invalid managed stop baseline path.");
|
|
5228
|
+
}
|
|
5229
|
+
return result;
|
|
5230
|
+
}
|
|
5231
|
+
function list(value, parse2 = text) {
|
|
5232
|
+
if (!Array.isArray(value) || !value.length || value.length > 256) {
|
|
5233
|
+
throw new Error("Invalid managed stop baseline population.");
|
|
5234
|
+
}
|
|
5235
|
+
const result = value.map((item) => parse2(item));
|
|
5236
|
+
if (new Set(result).size !== result.length) throw new Error("Duplicate stop baseline identity.");
|
|
5237
|
+
return result;
|
|
5238
|
+
}
|
|
5239
|
+
function id(value) {
|
|
5240
|
+
const result = text(value);
|
|
5241
|
+
if (!/^[a-f0-9]{64}$/.test(result)) throw new Error("Invalid stop baseline container ID.");
|
|
5242
|
+
return result;
|
|
5243
|
+
}
|
|
5244
|
+
function validateManagedStopBaseline(value, state) {
|
|
5245
|
+
const v = record2(value, [
|
|
5246
|
+
"version",
|
|
5247
|
+
"provider",
|
|
5248
|
+
"context",
|
|
5249
|
+
"providerId",
|
|
5250
|
+
"uid",
|
|
5251
|
+
"sourcePath",
|
|
5252
|
+
"sourceContainer",
|
|
5253
|
+
"endpoint",
|
|
5254
|
+
"daemonId",
|
|
5255
|
+
"sourceConfigSha256",
|
|
5256
|
+
"effectiveConfigSha256",
|
|
5257
|
+
"project",
|
|
5258
|
+
"primaryService",
|
|
5259
|
+
"requiredServices",
|
|
5260
|
+
"allowedServices",
|
|
5261
|
+
"composeDirectory",
|
|
5262
|
+
"composeFiles",
|
|
5263
|
+
"featureDirectory",
|
|
5264
|
+
"containers"
|
|
5265
|
+
]);
|
|
5266
|
+
if (v.version !== 1 || v.provider !== "devsy" || Buffer.byteLength(JSON.stringify(value)) > 1024 * 1024) {
|
|
5267
|
+
throw new Error("Unsupported or oversized managed stop baseline.");
|
|
5268
|
+
}
|
|
5269
|
+
const endpoint = text(v.endpoint);
|
|
5270
|
+
if (!endpoint.startsWith("unix://"))
|
|
5271
|
+
throw new Error("Stop baseline requires a pinned local Docker endpoint.");
|
|
5272
|
+
absolute(endpoint.slice(7));
|
|
5273
|
+
const result = {
|
|
5274
|
+
version: 1,
|
|
5275
|
+
provider: "devsy",
|
|
5276
|
+
context: text(v.context),
|
|
5277
|
+
providerId: text(v.providerId),
|
|
5278
|
+
uid: text(v.uid, true),
|
|
5279
|
+
sourcePath: absolute(v.sourcePath),
|
|
5280
|
+
sourceContainer: text(v.sourceContainer, true),
|
|
5281
|
+
endpoint,
|
|
5282
|
+
daemonId: text(v.daemonId),
|
|
5283
|
+
sourceConfigSha256: id(v.sourceConfigSha256),
|
|
5284
|
+
effectiveConfigSha256: id(v.effectiveConfigSha256),
|
|
5285
|
+
project: text(v.project),
|
|
5286
|
+
primaryService: text(v.primaryService),
|
|
5287
|
+
requiredServices: list(v.requiredServices),
|
|
5288
|
+
allowedServices: list(v.allowedServices),
|
|
5289
|
+
composeDirectory: absolute(v.composeDirectory),
|
|
5290
|
+
composeFiles: list(v.composeFiles, absolute),
|
|
5291
|
+
featureDirectory: absolute(v.featureDirectory),
|
|
5292
|
+
containers: []
|
|
5293
|
+
};
|
|
5294
|
+
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))) {
|
|
5295
|
+
throw new Error("Managed stop baseline does not bind the retained generation.");
|
|
5296
|
+
}
|
|
5297
|
+
if (!Array.isArray(v.containers) || !v.containers.length || v.containers.length > 256) {
|
|
5298
|
+
throw new Error("Invalid managed stop baseline population.");
|
|
5299
|
+
}
|
|
5300
|
+
result.containers = v.containers.map((entry) => {
|
|
5301
|
+
const c = record2(entry, ["id", "service", "configFiles", "mounts"]);
|
|
5302
|
+
const configFiles = list(c.configFiles, absolute);
|
|
5303
|
+
if (configFiles.length < result.composeFiles.length || result.composeFiles.some((file, index) => configFiles[index] !== file) || configFiles.slice(result.composeFiles.length).some(
|
|
5304
|
+
(file) => import_node_path16.default.dirname(file) !== result.featureDirectory || !/^docker-compose\.devcontainer\.containerFeatures-[a-zA-Z0-9_-]+\.yml$/.test(
|
|
5305
|
+
import_node_path16.default.basename(file)
|
|
5306
|
+
)
|
|
5307
|
+
)) {
|
|
5308
|
+
throw new Error("Invalid stop baseline Compose provenance.");
|
|
5309
|
+
}
|
|
5310
|
+
if (!Array.isArray(c.mounts) || c.mounts.length > 256)
|
|
5311
|
+
throw new Error("Invalid baseline mounts.");
|
|
5312
|
+
return {
|
|
5313
|
+
id: id(c.id),
|
|
5314
|
+
service: text(c.service),
|
|
5315
|
+
configFiles,
|
|
5316
|
+
mounts: c.mounts.map((item) => {
|
|
5317
|
+
const mount = record2(item, ["Type", "Source", "Destination"]);
|
|
5318
|
+
return {
|
|
5319
|
+
Type: text(mount.Type),
|
|
5320
|
+
Source: mount.Type === "tmpfs" && mount.Source === "" ? "" : absolute(mount.Source),
|
|
5321
|
+
Destination: absolute(mount.Destination)
|
|
5322
|
+
};
|
|
5323
|
+
})
|
|
5324
|
+
};
|
|
5325
|
+
});
|
|
5326
|
+
const services = result.containers.map((container) => container.service);
|
|
5327
|
+
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))) {
|
|
5328
|
+
throw new Error("Incomplete or duplicate stop baseline population.");
|
|
5329
|
+
}
|
|
5330
|
+
const primary = result.containers.find(
|
|
5331
|
+
(container) => container.service === result.primaryService
|
|
5332
|
+
);
|
|
5333
|
+
if (primary?.mounts.filter((mount) => mount.Type === "bind" && mount.Source === state.repoPath).length !== 1 || result.sourceContainer && result.sourceContainer !== primary.id) {
|
|
5334
|
+
throw new Error("Stop baseline has no exact primary source binding.");
|
|
5335
|
+
}
|
|
5336
|
+
return result;
|
|
5337
|
+
}
|
|
5338
|
+
var import_node_path16;
|
|
5339
|
+
var init_managed_stop_baseline = __esm({
|
|
5340
|
+
"src/core/managed-stop-baseline.ts"() {
|
|
5341
|
+
"use strict";
|
|
5342
|
+
import_node_path16 = __toESM(require("path"));
|
|
5343
|
+
}
|
|
5344
|
+
});
|
|
5345
|
+
|
|
5087
5346
|
// src/core/managed-runtime-state.ts
|
|
5088
5347
|
function stateKey(repoPath, workspace) {
|
|
5089
5348
|
return (0, import_node_crypto10.createHash)("sha256").update(`${repoPath}\0${workspace ?? ""}`, "utf-8").digest("hex");
|
|
5090
5349
|
}
|
|
5091
5350
|
function managedRuntimeStatePath(repoPath, workspace) {
|
|
5092
|
-
return
|
|
5351
|
+
return import_node_path17.default.join(DEVROUTER_HOME, "managed-runtime", `${stateKey(repoPath, workspace)}.json`);
|
|
5093
5352
|
}
|
|
5094
5353
|
function isStringArray(value) {
|
|
5095
5354
|
return Array.isArray(value) && value.every((item) => typeof item === "string") && new Set(value).size === value.length;
|
|
@@ -5131,7 +5390,7 @@ function validateState(value, repoPath, workspace) {
|
|
|
5131
5390
|
if (typeof candidate.updatedAt !== "string" || !Number.isFinite(Date.parse(candidate.updatedAt))) {
|
|
5132
5391
|
throw new Error("Managed runtime state has an invalid timestamp.");
|
|
5133
5392
|
}
|
|
5134
|
-
|
|
5393
|
+
const result = {
|
|
5135
5394
|
version: 1,
|
|
5136
5395
|
repoPath,
|
|
5137
5396
|
...workspace !== void 0 ? { workspace } : {},
|
|
@@ -5149,6 +5408,10 @@ function validateState(value, repoPath, workspace) {
|
|
|
5149
5408
|
...candidate.transitionPhase ? { transitionPhase: candidate.transitionPhase } : {},
|
|
5150
5409
|
updatedAt: candidate.updatedAt
|
|
5151
5410
|
};
|
|
5411
|
+
if (Object.hasOwn(candidate, "stopBaseline")) {
|
|
5412
|
+
result.stopBaseline = validateManagedStopBaseline(candidate.stopBaseline, result);
|
|
5413
|
+
}
|
|
5414
|
+
return result;
|
|
5152
5415
|
}
|
|
5153
5416
|
function readManagedRuntimeState(repoPath, workspace, read = (file) => import_node_fs13.default.readFileSync(file, "utf-8")) {
|
|
5154
5417
|
const statePath = managedRuntimeStatePath(repoPath, workspace);
|
|
@@ -5161,14 +5424,15 @@ function readManagedRuntimeState(repoPath, workspace, read = (file) => import_no
|
|
|
5161
5424
|
}
|
|
5162
5425
|
return validateState(parsed, repoPath, workspace);
|
|
5163
5426
|
}
|
|
5164
|
-
var import_node_crypto10, import_node_fs13,
|
|
5427
|
+
var import_node_crypto10, import_node_fs13, import_node_path17;
|
|
5165
5428
|
var init_managed_runtime_state = __esm({
|
|
5166
5429
|
"src/core/managed-runtime-state.ts"() {
|
|
5167
5430
|
"use strict";
|
|
5168
5431
|
import_node_crypto10 = require("crypto");
|
|
5169
5432
|
import_node_fs13 = __toESM(require("fs"));
|
|
5170
|
-
|
|
5433
|
+
import_node_path17 = __toESM(require("path"));
|
|
5171
5434
|
init_atomic_file();
|
|
5435
|
+
init_managed_stop_baseline();
|
|
5172
5436
|
init_router();
|
|
5173
5437
|
}
|
|
5174
5438
|
});
|
|
@@ -5197,7 +5461,7 @@ async function containers(project, signal) {
|
|
|
5197
5461
|
signal
|
|
5198
5462
|
);
|
|
5199
5463
|
const ids = listed.trim() ? listed.trim().split(/\r?\n/).sort() : [];
|
|
5200
|
-
if (ids.length > 128 || new Set(ids).size !== ids.length || ids.some((
|
|
5464
|
+
if (ids.length > 128 || new Set(ids).size !== ids.length || ids.some((id3) => !/^[a-f0-9]{64}$/.test(id3)))
|
|
5201
5465
|
throw new Error("Observation container population is invalid.");
|
|
5202
5466
|
if (!ids.length) return [];
|
|
5203
5467
|
const output2 = await runControllerProbe(
|
|
@@ -5214,12 +5478,12 @@ async function containers(project, signal) {
|
|
|
5214
5478
|
}
|
|
5215
5479
|
return values.sort((a, b) => a.id.localeCompare(b.id));
|
|
5216
5480
|
}
|
|
5217
|
-
var import_node_crypto11,
|
|
5481
|
+
var import_node_crypto11, import_node_path18, OBSERVATION_INSPECT_TEMPLATE, collectControllerObservation;
|
|
5218
5482
|
var init_controller_observation = __esm({
|
|
5219
5483
|
"src/core/controller-observation.ts"() {
|
|
5220
5484
|
"use strict";
|
|
5221
5485
|
import_node_crypto11 = require("crypto");
|
|
5222
|
-
|
|
5486
|
+
import_node_path18 = __toESM(require("path"));
|
|
5223
5487
|
init_controller_binding();
|
|
5224
5488
|
init_controller_monitor();
|
|
5225
5489
|
init_controller_probe();
|
|
@@ -5262,14 +5526,14 @@ var init_controller_observation = __esm({
|
|
|
5262
5526
|
)).trim().split("\n");
|
|
5263
5527
|
if (metadata.length !== 2) throw new Error("Observation ownership paths unavailable.");
|
|
5264
5528
|
const [gitDir, commonDir] = metadata;
|
|
5265
|
-
const sourcePath =
|
|
5529
|
+
const sourcePath = import_node_path18.default.join(environment.repoPath, ".devcontainer/devcontainer.json");
|
|
5266
5530
|
const files = [
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5531
|
+
import_node_path18.default.join(commonDir, "devrouter/workspaces", `${environment.workspace}.json`),
|
|
5532
|
+
import_node_path18.default.join(environment.repoPath, ".devrouter.yml"),
|
|
5533
|
+
import_node_path18.default.join(gitDir, "devrouter-workspace"),
|
|
5270
5534
|
managedRuntimeStatePath(environment.repoPath, environment.workspace),
|
|
5271
5535
|
sourcePath,
|
|
5272
|
-
|
|
5536
|
+
import_node_path18.default.join(environment.repoPath, MANAGED_DEVCONTAINER_PATH)
|
|
5273
5537
|
];
|
|
5274
5538
|
const persisted = captureControllerEvidence(files);
|
|
5275
5539
|
if (controllerBindingFingerprint(persisted.contents[0], persisted.contents[1]) !== environment.fingerprint)
|
|
@@ -5285,16 +5549,16 @@ var init_controller_observation = __esm({
|
|
|
5285
5549
|
const source = parseDevcontainerConfig(persisted.contents[4], sourcePath);
|
|
5286
5550
|
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
5551
|
throw new ControllerObservationBindingChanged("Observation retained configuration changed.");
|
|
5288
|
-
const
|
|
5552
|
+
const identity2 = {
|
|
5289
5553
|
repoPath: environment.repoPath,
|
|
5290
5554
|
workspace: environment.workspace,
|
|
5291
5555
|
provider: environment.provider
|
|
5292
5556
|
};
|
|
5293
|
-
const journal = readReliabilityOperation(
|
|
5557
|
+
const journal = readReliabilityOperation(identity2);
|
|
5294
5558
|
if (!journal) throw new Error("Observation journal is unavailable.");
|
|
5295
5559
|
const routes = readRoutes().filter((route) => route.repoPath === environment.repoPath);
|
|
5296
5560
|
const before = await containers(state.composeProject, signal);
|
|
5297
|
-
const composeDirectory =
|
|
5561
|
+
const composeDirectory = import_node_path18.default.join(environment.repoPath, ".devcontainer");
|
|
5298
5562
|
const references = typeof source.dockerComposeFile === "string" ? [source.dockerComposeFile] : source.dockerComposeFile;
|
|
5299
5563
|
if (!Array.isArray(references) || !references.length || references.some((value) => typeof value !== "string"))
|
|
5300
5564
|
throw new Error("Observation Compose source is unavailable.");
|
|
@@ -5311,7 +5575,7 @@ var init_controller_observation = __esm({
|
|
|
5311
5575
|
if (!recorded || container.labels["com.docker.compose.project.working_dir"] !== composeDirectory)
|
|
5312
5576
|
throw new Error("Observation Compose ownership is unavailable.");
|
|
5313
5577
|
const files2 = recorded.split(",").map((file) => file.trim());
|
|
5314
|
-
if (files2.some((file) => !
|
|
5578
|
+
if (files2.some((file) => !import_node_path18.default.isAbsolute(file)) || !hasExactComposeIdentity(container, {
|
|
5315
5579
|
repoPath: environment.repoPath,
|
|
5316
5580
|
service: container.labels["com.docker.compose.service"] ?? "",
|
|
5317
5581
|
composeProject: state.composeProject,
|
|
@@ -5440,7 +5704,7 @@ var init_controller_observation = __esm({
|
|
|
5440
5704
|
const routeFingerprint = digest2(routes);
|
|
5441
5705
|
return {
|
|
5442
5706
|
environment,
|
|
5443
|
-
identity,
|
|
5707
|
+
identity: identity2,
|
|
5444
5708
|
journal,
|
|
5445
5709
|
sampledAtMs: Math.floor(performance.now()),
|
|
5446
5710
|
runtimeFingerprint: digest2({ before, processes }),
|
|
@@ -5538,11 +5802,11 @@ var init_controller_sessions = __esm({
|
|
|
5538
5802
|
this.previousTime = { monotonic, wall };
|
|
5539
5803
|
return discontinuity;
|
|
5540
5804
|
}
|
|
5541
|
-
acquire(
|
|
5805
|
+
acquire(id3, environment, requirements, monotonic, wall) {
|
|
5542
5806
|
this.tick(monotonic, wall);
|
|
5543
5807
|
const next = this.read();
|
|
5544
5808
|
const required = [...requirements].sort();
|
|
5545
|
-
const existing = next.sessions.find((s) => s.id ===
|
|
5809
|
+
const existing = next.sessions.find((s) => s.id === id3);
|
|
5546
5810
|
const shared = next.environments.find((env) => env.repoPath === environment.repoPath);
|
|
5547
5811
|
if (shared && JSON.stringify(shared) !== JSON.stringify(environment))
|
|
5548
5812
|
throw new Error("Controller environment binding conflicts.");
|
|
@@ -5554,7 +5818,7 @@ var init_controller_sessions = __esm({
|
|
|
5554
5818
|
if (next.sessions.length >= 128 || !shared && next.environments.length >= 32)
|
|
5555
5819
|
throw new Error("Controller coordination capacity exhausted.");
|
|
5556
5820
|
const session = {
|
|
5557
|
-
id:
|
|
5821
|
+
id: id3,
|
|
5558
5822
|
environmentId: environment.id,
|
|
5559
5823
|
generation: (0, import_node_crypto12.randomUUID)(),
|
|
5560
5824
|
requirements: required,
|
|
@@ -5564,7 +5828,7 @@ var init_controller_sessions = __esm({
|
|
|
5564
5828
|
next.sessions.push(session);
|
|
5565
5829
|
this.event(next, session, "acquired");
|
|
5566
5830
|
this.commit(next);
|
|
5567
|
-
this.leases.set(
|
|
5831
|
+
this.leases.set(id3, monotonic + 3e4);
|
|
5568
5832
|
return this.binding(session);
|
|
5569
5833
|
}
|
|
5570
5834
|
publish(bindings, projections, monotonic, wall) {
|
|
@@ -5610,8 +5874,8 @@ var init_controller_sessions = __esm({
|
|
|
5610
5874
|
if (changed) {
|
|
5611
5875
|
this.commit(next);
|
|
5612
5876
|
if (bindingChanged) {
|
|
5613
|
-
for (const
|
|
5614
|
-
if (!next.sessions.some((s) => s.id ===
|
|
5877
|
+
for (const id3 of this.leases.keys())
|
|
5878
|
+
if (!next.sessions.some((s) => s.id === id3)) this.leases.delete(id3);
|
|
5615
5879
|
}
|
|
5616
5880
|
}
|
|
5617
5881
|
}
|
|
@@ -5677,11 +5941,11 @@ function fields(value, expected) {
|
|
|
5677
5941
|
const actual = Object.keys(value);
|
|
5678
5942
|
if (actual.length !== expected.length || actual.some((key) => !expected.includes(key))) fail2();
|
|
5679
5943
|
}
|
|
5680
|
-
function
|
|
5944
|
+
function text2(value, max = 256) {
|
|
5681
5945
|
return typeof value === "string" && value.length > 0 && value.length <= max && ![...value].some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127);
|
|
5682
5946
|
}
|
|
5683
|
-
function
|
|
5684
|
-
return
|
|
5947
|
+
function id2(value) {
|
|
5948
|
+
return text2(value, 128) && /^[a-zA-Z0-9_-]+$/.test(value);
|
|
5685
5949
|
}
|
|
5686
5950
|
function counter(value) {
|
|
5687
5951
|
return Number.isSafeInteger(value) && Number(value) >= 0;
|
|
@@ -5697,7 +5961,7 @@ function validateControllerSnapshot(value) {
|
|
|
5697
5961
|
"sessions",
|
|
5698
5962
|
"events"
|
|
5699
5963
|
]);
|
|
5700
|
-
if (value.version !== 1 || !
|
|
5964
|
+
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
5965
|
fail2();
|
|
5702
5966
|
const environments = /* @__PURE__ */ new Set();
|
|
5703
5967
|
const checkouts = /* @__PURE__ */ new Set();
|
|
@@ -5711,7 +5975,7 @@ function validateControllerSnapshot(value) {
|
|
|
5711
5975
|
"profile",
|
|
5712
5976
|
"fingerprint"
|
|
5713
5977
|
]);
|
|
5714
|
-
if (!
|
|
5978
|
+
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
5979
|
fail2();
|
|
5716
5980
|
environments.add(env.id);
|
|
5717
5981
|
checkouts.add(env.repoPath);
|
|
@@ -5727,8 +5991,8 @@ function validateControllerSnapshot(value) {
|
|
|
5727
5991
|
"renewedAtMs",
|
|
5728
5992
|
...Object.hasOwn(session, "observation") ? ["observation"] : []
|
|
5729
5993
|
]);
|
|
5730
|
-
if (!
|
|
5731
|
-
(r) => !
|
|
5994
|
+
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(
|
|
5995
|
+
(r) => !text2(r, 132) || !/^(runtime|app:[a-z0-9][a-z0-9-]*)$/.test(r)
|
|
5732
5996
|
))
|
|
5733
5997
|
fail2();
|
|
5734
5998
|
if (session.observation !== void 0) {
|
|
@@ -5750,7 +6014,7 @@ function validateControllerSnapshot(value) {
|
|
|
5750
6014
|
"WAITING_CAPACITY",
|
|
5751
6015
|
"PARKED_CAPACITY",
|
|
5752
6016
|
"UNKNOWN"
|
|
5753
|
-
].includes(String(observation.status)) || !counter(observation.sampledAtMs) || !counter(observation.validUntilMs) || observation.validUntilMs < observation.sampledAtMs || !counter(observation.journalRevision) || !
|
|
6017
|
+
].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
6018
|
fail2();
|
|
5755
6019
|
}
|
|
5756
6020
|
sessions.add(session.id);
|
|
@@ -5760,7 +6024,7 @@ function validateControllerSnapshot(value) {
|
|
|
5760
6024
|
let previous = 0;
|
|
5761
6025
|
for (const event of value.events) {
|
|
5762
6026
|
fields(event, ["sequence", "session", "generation", "kind"]);
|
|
5763
|
-
if (!counter(event.sequence) || event.sequence <= previous || event.sequence >= value.nextSequence || !
|
|
6027
|
+
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
6028
|
String(event.kind)
|
|
5765
6029
|
))
|
|
5766
6030
|
fail2();
|
|
@@ -5769,20 +6033,20 @@ function validateControllerSnapshot(value) {
|
|
|
5769
6033
|
if (Buffer.byteLength(JSON.stringify(value.events)) > 262144 || Buffer.byteLength(JSON.stringify(value)) + 1 > CONTROLLER_SNAPSHOT_BYTES)
|
|
5770
6034
|
fail2();
|
|
5771
6035
|
}
|
|
5772
|
-
var import_node_crypto13, import_node_fs14,
|
|
6036
|
+
var import_node_crypto13, import_node_fs14, import_node_path19, CONTROLLER_SNAPSHOT_BYTES, ControllerStore;
|
|
5773
6037
|
var init_controller_store = __esm({
|
|
5774
6038
|
"src/core/controller-store.ts"() {
|
|
5775
6039
|
"use strict";
|
|
5776
6040
|
import_node_crypto13 = require("crypto");
|
|
5777
6041
|
import_node_fs14 = __toESM(require("fs"));
|
|
5778
|
-
|
|
6042
|
+
import_node_path19 = __toESM(require("path"));
|
|
5779
6043
|
init_atomic_file();
|
|
5780
6044
|
CONTROLLER_SNAPSHOT_BYTES = 1048576;
|
|
5781
6045
|
ControllerStore = class {
|
|
5782
6046
|
constructor(directory, write = writeFileAtomically) {
|
|
5783
6047
|
this.directory = directory;
|
|
5784
6048
|
this.write = write;
|
|
5785
|
-
this.file =
|
|
6049
|
+
this.file = import_node_path19.default.join(directory, "snapshot.json");
|
|
5786
6050
|
}
|
|
5787
6051
|
file;
|
|
5788
6052
|
read() {
|
|
@@ -5862,10 +6126,10 @@ var init_controller_store = __esm({
|
|
|
5862
6126
|
|
|
5863
6127
|
// src/core/controller-server.ts
|
|
5864
6128
|
function privateDirectory(directory) {
|
|
5865
|
-
const
|
|
5866
|
-
let current =
|
|
5867
|
-
for (const part of
|
|
5868
|
-
current =
|
|
6129
|
+
const absolute2 = import_node_path20.default.resolve(directory);
|
|
6130
|
+
let current = import_node_path20.default.parse(absolute2).root;
|
|
6131
|
+
for (const part of absolute2.slice(current.length).split(import_node_path20.default.sep)) {
|
|
6132
|
+
current = import_node_path20.default.join(current, part);
|
|
5869
6133
|
try {
|
|
5870
6134
|
const stat2 = import_node_fs15.default.lstatSync(current);
|
|
5871
6135
|
if (!stat2.isDirectory() || stat2.isSymbolicLink())
|
|
@@ -5875,7 +6139,7 @@ function privateDirectory(directory) {
|
|
|
5875
6139
|
import_node_fs15.default.mkdirSync(current, { mode: 448 });
|
|
5876
6140
|
}
|
|
5877
6141
|
}
|
|
5878
|
-
const stat = import_node_fs15.default.lstatSync(
|
|
6142
|
+
const stat = import_node_fs15.default.lstatSync(absolute2);
|
|
5879
6143
|
if (stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0)
|
|
5880
6144
|
throw new Error("Controller directory is not private.");
|
|
5881
6145
|
}
|
|
@@ -5891,10 +6155,10 @@ function validateOwnedFile(file, socket) {
|
|
|
5891
6155
|
}
|
|
5892
6156
|
}
|
|
5893
6157
|
async function runController(options) {
|
|
5894
|
-
const socketPath =
|
|
6158
|
+
const socketPath = import_node_path20.default.join(options.directory, "control.sock");
|
|
5895
6159
|
if (Buffer.byteLength(socketPath) > 103) throw new Error("Controller socket path is too long.");
|
|
5896
6160
|
privateDirectory(options.directory);
|
|
5897
|
-
const lockPath =
|
|
6161
|
+
const lockPath = import_node_path20.default.join(options.directory, "owner.lock");
|
|
5898
6162
|
try {
|
|
5899
6163
|
const stat = import_node_fs15.default.lstatSync(lockPath);
|
|
5900
6164
|
if (!stat.isFile() || stat.uid !== process.getuid?.())
|
|
@@ -6197,13 +6461,13 @@ async function runController(options) {
|
|
|
6197
6461
|
}
|
|
6198
6462
|
});
|
|
6199
6463
|
}
|
|
6200
|
-
var import_node_fs15, import_node_net2,
|
|
6464
|
+
var import_node_fs15, import_node_net2, import_node_path20, FRAME_BYTES;
|
|
6201
6465
|
var init_controller_server = __esm({
|
|
6202
6466
|
"src/core/controller-server.ts"() {
|
|
6203
6467
|
"use strict";
|
|
6204
6468
|
import_node_fs15 = __toESM(require("fs"));
|
|
6205
6469
|
import_node_net2 = __toESM(require("net"));
|
|
6206
|
-
|
|
6470
|
+
import_node_path20 = __toESM(require("path"));
|
|
6207
6471
|
init_controller_monitor();
|
|
6208
6472
|
init_controller_protocol();
|
|
6209
6473
|
init_controller_sessions();
|
|
@@ -6219,7 +6483,7 @@ __export(controller_exports, {
|
|
|
6219
6483
|
runControllerCommand: () => runControllerCommand
|
|
6220
6484
|
});
|
|
6221
6485
|
async function runControllerCommand(method, options, repo) {
|
|
6222
|
-
const directory =
|
|
6486
|
+
const directory = import_node_path21.default.join(DEVROUTER_HOME, "controller");
|
|
6223
6487
|
try {
|
|
6224
6488
|
if (method === "run") {
|
|
6225
6489
|
const controller = new AbortController();
|
|
@@ -6244,7 +6508,7 @@ async function runControllerCommand(method, options, repo) {
|
|
|
6244
6508
|
if (fields2.timeout !== void 0) fields2.timeout = Number(fields2.timeout);
|
|
6245
6509
|
await controllerRequest(
|
|
6246
6510
|
directory,
|
|
6247
|
-
{ method, ...fields2, ...repo ? { path:
|
|
6511
|
+
{ method, ...fields2, ...repo ? { path: import_node_path21.default.resolve(repo) } : {} },
|
|
6248
6512
|
(value) => process.stdout.write(`${JSON.stringify(value)}
|
|
6249
6513
|
`)
|
|
6250
6514
|
);
|
|
@@ -6256,11 +6520,11 @@ async function runControllerCommand(method, options, repo) {
|
|
|
6256
6520
|
process.exitCode = 1;
|
|
6257
6521
|
}
|
|
6258
6522
|
}
|
|
6259
|
-
var
|
|
6523
|
+
var import_node_path21;
|
|
6260
6524
|
var init_controller = __esm({
|
|
6261
6525
|
"src/commands/controller.ts"() {
|
|
6262
6526
|
"use strict";
|
|
6263
|
-
|
|
6527
|
+
import_node_path21 = __toESM(require("path"));
|
|
6264
6528
|
init_controller_binding();
|
|
6265
6529
|
init_controller_client();
|
|
6266
6530
|
init_controller_observation();
|
|
@@ -6308,13 +6572,13 @@ function replaceDevrouterSection(content) {
|
|
|
6308
6572
|
return `${content.slice(0, start)}${buildDevrouterSection()}${content.slice(end)}`;
|
|
6309
6573
|
}
|
|
6310
6574
|
function writeRepoFile(repoPath, relPath, content) {
|
|
6311
|
-
const absolutePath = (0,
|
|
6312
|
-
(0, import_node_fs16.mkdirSync)((0,
|
|
6575
|
+
const absolutePath = (0, import_node_path22.join)(repoPath, relPath);
|
|
6576
|
+
(0, import_node_fs16.mkdirSync)((0, import_node_path22.dirname)(absolutePath), { recursive: true });
|
|
6313
6577
|
(0, import_node_fs16.writeFileSync)(absolutePath, content, "utf-8");
|
|
6314
6578
|
return absolutePath;
|
|
6315
6579
|
}
|
|
6316
6580
|
function ensureAgentsMdSection(repoPath) {
|
|
6317
|
-
const filePath = (0,
|
|
6581
|
+
const filePath = (0, import_node_path22.join)(repoPath, AGENTS_MD);
|
|
6318
6582
|
if ((0, import_node_fs16.existsSync)(filePath)) {
|
|
6319
6583
|
const content = (0, import_node_fs16.readFileSync)(filePath, "utf-8");
|
|
6320
6584
|
if (content.includes(DEVROUTER_SENTINEL)) {
|
|
@@ -6339,12 +6603,12 @@ function ensureSkillFile(repoPath) {
|
|
|
6339
6603
|
const filePath = writeRepoFile(repoPath, DEVROUTER_SKILL_REL_PATH, DEVROUTER_SKILL_CONTENT);
|
|
6340
6604
|
return { path: filePath, written: true };
|
|
6341
6605
|
}
|
|
6342
|
-
var import_node_fs16,
|
|
6606
|
+
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
6607
|
var init_agents_md = __esm({
|
|
6344
6608
|
"src/core/agents-md.ts"() {
|
|
6345
6609
|
"use strict";
|
|
6346
6610
|
import_node_fs16 = require("fs");
|
|
6347
|
-
|
|
6611
|
+
import_node_path22 = require("path");
|
|
6348
6612
|
DEVROUTER_SENTINEL = "<!-- devrouter -->";
|
|
6349
6613
|
DEVROUTER_END_SENTINEL = "<!-- /devrouter -->";
|
|
6350
6614
|
LEGACY_DEVROUTER_SECTION_END = "- `devrouter ls`";
|
|
@@ -6877,13 +7141,14 @@ function buildOnboardingPrompt(options = {}) {
|
|
|
6877
7141
|
`- When a workspace is active: hosts auto-namespace (\`web.localhost\` \u2192 \`web.<ws>.localhost\`), \`${WORKSPACE_PLACEHOLDER}\` in \`upstream\` is substituted with the token, and the docker \`router\` key is suffixed per workspace. Managed ensure rejects every HTTP/TCP upstream outside that exact alias namespace before mutation. The runtime config is computed in memory only \u2014 the committed \`.devrouter.yml\` is never rewritten.`,
|
|
6878
7142
|
"- TLS: namespaced hosts (`web.<ws>.localhost`) are not covered by the `*.localhost` wildcard; devrouter auto-extends the mkcert cert SANs for active hosts when TLS is enabled.",
|
|
6879
7143
|
"- Lifecycle: after one-time setup, use `devrouter ensure .` for both primary and linked checkouts; never branch on checkout kind or use live verify as startup. Managed consumer images contain no devrouter package/helper: ensure delivers its matching helper at runtime and invokes an exact captured snapshot of the repository-owned post-start adapter. Keep `.devrouter.yml` as the only consumer-side version pin. Use `devrouter stop .` for a non-destructive pause, `devrouter stop . --delete` only for explicit exact-owner cleanup without removing the checkout, and `devrouter exec . -- <command...>` for container commands. Never substitute raw DevPod/Devsy mutations; they bypass the machine-global ownership lock, which serializes provider mutations in a fair arrival-order queue and lets contenders wait up to thirty minutes with throttled stderr progress before failing with the queue position or holder PID and true durations. `workspace up` creates linked worktrees; destructive worktree removal and GC remain ledger-scoped.",
|
|
7144
|
+
"- After a drained interrupted ensure, manual exec can use an exactly proven retained Devsy runtime with the plain local Docker command. Revalidate identity for each tooling command until later preparation reconciles startup; preserve configuration, data and interrupted history. This does not repair absent stop baselines or provide OOM protection.",
|
|
6880
7145
|
"- Ensure reconciles interrupted or never-dispatched ensure after positive worker drainage. Unknown arbitrary exec never replays; use explicit stop to reconcile that uncertainty. Preserve journals and generated configuration if evidence is unavailable. This does not prevent OOM or activate capacity-managed recovery.",
|
|
6881
7146
|
"- Devsy agent readiness: run `devrouter setup --yes --workspace-runtime devsy` once so Devrouter acquires and verifies the pinned agent in its own machine cache. `doctor` reports `ready`, `missing`, `stale`, or `invalid` without network access, and `ensure` fails before the provider queue when readiness is not `ready`. An explicit `DEVSY_AGENT_BINARY` remains authoritative and must match a pinned official asset.",
|
|
6882
7147
|
"- A managed adapter plus `postCreateCommand` requires `waitFor` exactly `postCreateCommand` or `postStartCommand`; managed selective config preserves lifecycle fields and changes only `runServices`.",
|
|
6883
7148
|
"- 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
7149
|
"- 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
7150
|
"- 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
|
-
"-
|
|
7151
|
+
"- 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
7152
|
"- 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
7153
|
"- `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
7154
|
'- `--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 +7883,7 @@ function readPromptDirectory(promptDirPath) {
|
|
|
7618
7883
|
continue;
|
|
7619
7884
|
}
|
|
7620
7885
|
const version = normalizeVersion(basename);
|
|
7621
|
-
const promptPath =
|
|
7886
|
+
const promptPath = import_node_path23.default.join(promptDirPath, file.name);
|
|
7622
7887
|
const prompt = import_node_fs17.default.readFileSync(promptPath, "utf-8").trim();
|
|
7623
7888
|
releases.push({ version, prompt, promptPath });
|
|
7624
7889
|
}
|
|
@@ -7640,17 +7905,17 @@ function listAvailableUpgradeTargets(currentVersion, releases) {
|
|
|
7640
7905
|
}
|
|
7641
7906
|
function resolvePromptDirectory(explicitPath) {
|
|
7642
7907
|
if (explicitPath) {
|
|
7643
|
-
return
|
|
7908
|
+
return import_node_path23.default.resolve(explicitPath);
|
|
7644
7909
|
}
|
|
7645
|
-
const entryFile = process.argv[1] ?
|
|
7646
|
-
const entryDir =
|
|
7647
|
-
const resolvedEntryDir = import_node_fs17.default.existsSync(entryFile) ?
|
|
7910
|
+
const entryFile = process.argv[1] ? import_node_path23.default.resolve(process.argv[1]) : __filename;
|
|
7911
|
+
const entryDir = import_node_path23.default.dirname(entryFile);
|
|
7912
|
+
const resolvedEntryDir = import_node_fs17.default.existsSync(entryFile) ? import_node_path23.default.dirname(import_node_fs17.default.realpathSync(entryFile)) : entryDir;
|
|
7648
7913
|
const candidates = [
|
|
7649
|
-
|
|
7650
|
-
|
|
7651
|
-
|
|
7652
|
-
|
|
7653
|
-
|
|
7914
|
+
import_node_path23.default.resolve(resolvedEntryDir, "..", UPGRADE_PROMPTS_DIR),
|
|
7915
|
+
import_node_path23.default.resolve(entryDir, "..", UPGRADE_PROMPTS_DIR),
|
|
7916
|
+
import_node_path23.default.resolve(__dirname, "..", UPGRADE_PROMPTS_DIR),
|
|
7917
|
+
import_node_path23.default.resolve(__dirname, "..", "..", UPGRADE_PROMPTS_DIR),
|
|
7918
|
+
import_node_path23.default.resolve(process.cwd(), UPGRADE_PROMPTS_DIR)
|
|
7654
7919
|
];
|
|
7655
7920
|
const existing = candidates.find((candidate) => import_node_fs17.default.existsSync(candidate));
|
|
7656
7921
|
return existing ?? candidates[0];
|
|
@@ -7668,12 +7933,12 @@ function loadUpgradeCatalog(options = {}) {
|
|
|
7668
7933
|
releases
|
|
7669
7934
|
};
|
|
7670
7935
|
}
|
|
7671
|
-
var import_node_fs17,
|
|
7936
|
+
var import_node_fs17, import_node_path23, SEMVER_RE, UPGRADE_PROMPTS_DIR;
|
|
7672
7937
|
var init_upgrade = __esm({
|
|
7673
7938
|
"src/core/upgrade.ts"() {
|
|
7674
7939
|
"use strict";
|
|
7675
7940
|
import_node_fs17 = __toESM(require("fs"));
|
|
7676
|
-
|
|
7941
|
+
import_node_path23 = __toESM(require("path"));
|
|
7677
7942
|
init_repo_config();
|
|
7678
7943
|
SEMVER_RE = /^v?(\d+)\.(\d+)\.(\d+)$/;
|
|
7679
7944
|
UPGRADE_PROMPTS_DIR = "upgrade-prompts";
|
|
@@ -7786,7 +8051,7 @@ function nativeAssetName(platform, arch) {
|
|
|
7786
8051
|
return void 0;
|
|
7787
8052
|
}
|
|
7788
8053
|
function managedBinaryPath(cacheRoot, asset) {
|
|
7789
|
-
return
|
|
8054
|
+
return import_node_path24.default.join(cacheRoot, `v${SUPPORTED_DEVSY_VERSION}`, asset.name);
|
|
7790
8055
|
}
|
|
7791
8056
|
function hashOpenFile(file) {
|
|
7792
8057
|
const digest3 = (0, import_node_crypto14.createHash)("sha256");
|
|
@@ -7828,7 +8093,7 @@ function resolvedOptions(options) {
|
|
|
7828
8093
|
platform: options.platform ?? process.platform,
|
|
7829
8094
|
arch: options.arch ?? process.arch,
|
|
7830
8095
|
versionOutput: options.versionOutput ?? installedVersionOutput(),
|
|
7831
|
-
cacheRoot: options.cacheRoot ??
|
|
8096
|
+
cacheRoot: options.cacheRoot ?? import_node_path24.default.join(CACHE_DIR, "devsy", "agents"),
|
|
7832
8097
|
lockPath: options.lockPath ?? DEVSY_AGENT_CACHE_LOCK,
|
|
7833
8098
|
assets: options.assets ?? DEVSY_AGENT_ASSETS,
|
|
7834
8099
|
nativeAssetName: options.nativeAssetName
|
|
@@ -7953,11 +8218,11 @@ function fsyncDirectory2(directory) {
|
|
|
7953
8218
|
}
|
|
7954
8219
|
}
|
|
7955
8220
|
async function downloadAndPublish(asset, binaryPath, fetcher, githubCliDownloader) {
|
|
7956
|
-
const directory =
|
|
8221
|
+
const directory = import_node_path24.default.dirname(binaryPath);
|
|
7957
8222
|
import_node_fs18.default.mkdirSync(directory, { recursive: true, mode: 448 });
|
|
7958
|
-
const temporaryPath =
|
|
8223
|
+
const temporaryPath = import_node_path24.default.join(
|
|
7959
8224
|
directory,
|
|
7960
|
-
`.${
|
|
8225
|
+
`.${import_node_path24.default.basename(binaryPath)}.${process.pid}.${(0, import_node_crypto14.randomUUID)()}.tmp`
|
|
7961
8226
|
);
|
|
7962
8227
|
let handle;
|
|
7963
8228
|
try {
|
|
@@ -8041,7 +8306,7 @@ async function prepareDevsyAgent(options = {}) {
|
|
|
8041
8306
|
throw new DevsyAgentReadinessError(before);
|
|
8042
8307
|
}
|
|
8043
8308
|
const lock = options.withLock ?? ((operation) => {
|
|
8044
|
-
import_node_fs18.default.mkdirSync(
|
|
8309
|
+
import_node_fs18.default.mkdirSync(import_node_path24.default.dirname(resolved.lockPath), { recursive: true });
|
|
8045
8310
|
return withFileLock(
|
|
8046
8311
|
resolved.lockPath,
|
|
8047
8312
|
{
|
|
@@ -8086,14 +8351,14 @@ async function prepareDevsyAgent(options = {}) {
|
|
|
8086
8351
|
};
|
|
8087
8352
|
});
|
|
8088
8353
|
}
|
|
8089
|
-
var import_node_child_process10, import_node_crypto14, import_node_fs18,
|
|
8354
|
+
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
8355
|
var init_devsy_agent = __esm({
|
|
8091
8356
|
"src/core/devsy-agent.ts"() {
|
|
8092
8357
|
"use strict";
|
|
8093
8358
|
import_node_child_process10 = require("child_process");
|
|
8094
8359
|
import_node_crypto14 = require("crypto");
|
|
8095
8360
|
import_node_fs18 = __toESM(require("fs"));
|
|
8096
|
-
|
|
8361
|
+
import_node_path24 = __toESM(require("path"));
|
|
8097
8362
|
init_file_lock();
|
|
8098
8363
|
init_router();
|
|
8099
8364
|
SUPPORTED_DEVSY_VERSION = "1.16.2";
|
|
@@ -8121,7 +8386,7 @@ var init_devsy_agent = __esm({
|
|
|
8121
8386
|
this.name = "DevsyAgentReadinessError";
|
|
8122
8387
|
}
|
|
8123
8388
|
};
|
|
8124
|
-
DEVSY_AGENT_CACHE_LOCK =
|
|
8389
|
+
DEVSY_AGENT_CACHE_LOCK = import_node_path24.default.join(DEVROUTER_HOME, "devsy-agent-cache.lock");
|
|
8125
8390
|
DEVSY_AGENT_LOCK_WAIT_MS = 18e5;
|
|
8126
8391
|
GITHUB_CLI_STDERR_TAIL_BYTES = 4096;
|
|
8127
8392
|
}
|
|
@@ -8139,11 +8404,11 @@ function adapterFingerprint(adapter) {
|
|
|
8139
8404
|
return (0, import_node_crypto15.createHash)("sha256").update(adapter).digest("hex");
|
|
8140
8405
|
}
|
|
8141
8406
|
function resolveManagedPostStartPlan(repoPath) {
|
|
8142
|
-
const adapterPath =
|
|
8407
|
+
const adapterPath = import_node_path25.default.join(repoPath, MANAGED_ADAPTER_PATH);
|
|
8143
8408
|
const adapterBytes = readRegularFileBytes(adapterPath);
|
|
8144
8409
|
const adapterText = adapterBytes?.toString("utf-8");
|
|
8145
|
-
const dockerfilePath =
|
|
8146
|
-
const devcontainerPath =
|
|
8410
|
+
const dockerfilePath = import_node_path25.default.join(repoPath, ".devcontainer", "Dockerfile");
|
|
8411
|
+
const devcontainerPath = import_node_path25.default.join(repoPath, ".devcontainer", "devcontainer.json");
|
|
8147
8412
|
const dockerfile = readRegularFile(dockerfilePath) ?? "";
|
|
8148
8413
|
const devcontainer = readRegularFile(devcontainerPath) ?? "";
|
|
8149
8414
|
const devrouterPattern = [adapterText, dockerfile, devcontainer].filter((value) => value !== void 0).some(
|
|
@@ -8225,14 +8490,14 @@ function runManagedProcessAction(options) {
|
|
|
8225
8490
|
}
|
|
8226
8491
|
return "drifted";
|
|
8227
8492
|
}
|
|
8228
|
-
var import_node_child_process11, import_node_crypto15, import_node_fs19,
|
|
8493
|
+
var import_node_child_process11, import_node_crypto15, import_node_fs19, import_node_path25, MANAGED_MARKER, MANAGED_ADAPTER_PATH, RUNTIME_HELPER_PATH;
|
|
8229
8494
|
var init_managed_post_start = __esm({
|
|
8230
8495
|
"src/core/managed-post-start.ts"() {
|
|
8231
8496
|
"use strict";
|
|
8232
8497
|
import_node_child_process11 = require("child_process");
|
|
8233
8498
|
import_node_crypto15 = require("crypto");
|
|
8234
8499
|
import_node_fs19 = __toESM(require("fs"));
|
|
8235
|
-
|
|
8500
|
+
import_node_path25 = __toESM(require("path"));
|
|
8236
8501
|
init_devcontainer_config();
|
|
8237
8502
|
MANAGED_MARKER = "devrouter:managed devcontainer";
|
|
8238
8503
|
MANAGED_ADAPTER_PATH = ".devcontainer/post-start.sh";
|
|
@@ -8272,19 +8537,19 @@ function formatPublishedPort(serviceName, value) {
|
|
|
8272
8537
|
if (typeof value === "string") {
|
|
8273
8538
|
return `${serviceName}: ${value}`;
|
|
8274
8539
|
}
|
|
8275
|
-
const
|
|
8276
|
-
if (!
|
|
8540
|
+
const record4 = asRecord(value);
|
|
8541
|
+
if (!record4) {
|
|
8277
8542
|
return void 0;
|
|
8278
8543
|
}
|
|
8279
|
-
const target =
|
|
8280
|
-
const published =
|
|
8544
|
+
const target = record4.target;
|
|
8545
|
+
const published = record4.published;
|
|
8281
8546
|
if (published !== void 0 && target !== void 0) {
|
|
8282
8547
|
return `${serviceName}: ${String(published)}:${String(target)}`;
|
|
8283
8548
|
}
|
|
8284
8549
|
if (target !== void 0) {
|
|
8285
8550
|
return `${serviceName}: target ${String(target)}`;
|
|
8286
8551
|
}
|
|
8287
|
-
return `${serviceName}: ${JSON.stringify(
|
|
8552
|
+
return `${serviceName}: ${JSON.stringify(record4)}`;
|
|
8288
8553
|
}
|
|
8289
8554
|
function upstreamHost(upstream) {
|
|
8290
8555
|
const separator = upstream.lastIndexOf(":");
|
|
@@ -8360,11 +8625,11 @@ function routedProxyApps(config) {
|
|
|
8360
8625
|
);
|
|
8361
8626
|
}
|
|
8362
8627
|
function buildDevcontainerChecks(repoPath, config, workspace) {
|
|
8363
|
-
const devcontainerDir =
|
|
8628
|
+
const devcontainerDir = import_node_path26.default.join(repoPath, ".devcontainer");
|
|
8364
8629
|
if (!import_node_fs20.default.existsSync(devcontainerDir)) {
|
|
8365
8630
|
return [];
|
|
8366
8631
|
}
|
|
8367
|
-
const compose = inspectCompose(
|
|
8632
|
+
const compose = inspectCompose(import_node_path26.default.join(devcontainerDir, "docker-compose.yml"), workspace);
|
|
8368
8633
|
const checks = [];
|
|
8369
8634
|
if (compose.parseError) {
|
|
8370
8635
|
checks.push({
|
|
@@ -8391,7 +8656,7 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
|
|
|
8391
8656
|
details: compose.publishedPorts.length > 0 ? compose.publishedPorts.join(", ") : void 0,
|
|
8392
8657
|
suggestion: compose.parseError || compose.publishedPorts.length > 0 ? "Remove published ports and route services through devnet aliases with devrouter proxy apps." : void 0
|
|
8393
8658
|
});
|
|
8394
|
-
const dockerfilePath =
|
|
8659
|
+
const dockerfilePath = import_node_path26.default.join(devcontainerDir, "Dockerfile");
|
|
8395
8660
|
const dockerfileExists = import_node_fs20.default.existsSync(dockerfilePath);
|
|
8396
8661
|
const dockerfile = dockerfileExists ? import_node_fs20.default.readFileSync(dockerfilePath, "utf-8") : "";
|
|
8397
8662
|
const devrouterArtifacts = ["@devrouter/cli", "devrouter-process"].filter(
|
|
@@ -8448,12 +8713,12 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
|
|
|
8448
8713
|
});
|
|
8449
8714
|
return checks;
|
|
8450
8715
|
}
|
|
8451
|
-
var import_node_fs20,
|
|
8716
|
+
var import_node_fs20, import_node_path26, import_yaml4;
|
|
8452
8717
|
var init_devcontainer_diagnostics = __esm({
|
|
8453
8718
|
"src/core/devcontainer-diagnostics.ts"() {
|
|
8454
8719
|
"use strict";
|
|
8455
8720
|
import_node_fs20 = __toESM(require("fs"));
|
|
8456
|
-
|
|
8721
|
+
import_node_path26 = __toESM(require("path"));
|
|
8457
8722
|
import_yaml4 = __toESM(require("yaml"));
|
|
8458
8723
|
init_managed_post_start();
|
|
8459
8724
|
}
|
|
@@ -9393,11 +9658,22 @@ function listDevsyWorkspaces() {
|
|
|
9393
9658
|
if (typeof candidate.id !== "string" || !source || typeof source.localFolder !== "string") {
|
|
9394
9659
|
throw new Error("devsy workspace list returned a workspace without id/source.localFolder.");
|
|
9395
9660
|
}
|
|
9661
|
+
if (candidate.uid !== void 0 && typeof candidate.uid !== "string" || source.container !== void 0 && typeof source.container !== "string") {
|
|
9662
|
+
throw new Error("Devsy workspace list returned invalid provider identity.");
|
|
9663
|
+
}
|
|
9396
9664
|
const workspace = {
|
|
9397
9665
|
id: candidate.id,
|
|
9398
|
-
source: {
|
|
9666
|
+
source: {
|
|
9667
|
+
localFolder: source.localFolder,
|
|
9668
|
+
...typeof source.container === "string" ? { container: source.container } : {}
|
|
9669
|
+
},
|
|
9670
|
+
...typeof candidate.uid === "string" ? { uid: candidate.uid } : {},
|
|
9399
9671
|
...typeof candidate.context === "string" ? { context: candidate.context } : {}
|
|
9400
9672
|
};
|
|
9673
|
+
const provider = candidate.provider;
|
|
9674
|
+
if (provider && typeof provider.name === "string") workspace.providerName = provider.name;
|
|
9675
|
+
if (typeof provider?.options?.DOCKER_PATH?.value === "string")
|
|
9676
|
+
workspace.dockerPathOption = provider.options.DOCKER_PATH.value;
|
|
9401
9677
|
if ("lastUsed" in candidate) {
|
|
9402
9678
|
if (typeof candidate.lastUsed === "string") {
|
|
9403
9679
|
workspace.lastUsed = candidate.lastUsed;
|
|
@@ -9455,6 +9731,16 @@ function inspectDevsyWorkspaceOwnership(workspaces, devsyId, worktreePath) {
|
|
|
9455
9731
|
}
|
|
9456
9732
|
return exact[0] ? { status: "owned", workspace: exact[0] } : { status: "absent" };
|
|
9457
9733
|
}
|
|
9734
|
+
function selectDevsyWorkspace(workspaces, repoPath) {
|
|
9735
|
+
const matches = workspaces.filter(
|
|
9736
|
+
(workspace) => sameWorkspacePath(workspace.source.localFolder, repoPath)
|
|
9737
|
+
);
|
|
9738
|
+
if (matches.length > 1) {
|
|
9739
|
+
const ids = matches.map((match) => match.id).join(", ");
|
|
9740
|
+
throw new Error(`Multiple Devsy workspaces reference '${repoPath}': ${ids}`);
|
|
9741
|
+
}
|
|
9742
|
+
return matches[0];
|
|
9743
|
+
}
|
|
9458
9744
|
var import_node_child_process13;
|
|
9459
9745
|
var init_devsy_workspaces = __esm({
|
|
9460
9746
|
"src/core/devsy-workspaces.ts"() {
|
|
@@ -9662,20 +9948,20 @@ function resetWorkspaceRuntimeCaches() {
|
|
|
9662
9948
|
cachedRuntime = void 0;
|
|
9663
9949
|
cachedSnapshots = void 0;
|
|
9664
9950
|
}
|
|
9665
|
-
var import_node_child_process14, import_node_fs23,
|
|
9951
|
+
var import_node_child_process14, import_node_fs23, import_node_path27, SUPPORTED_RUNTIMES2, RUNTIME_CONFIG_FILE, INACTIVITY_TIMEOUT_PATTERN, cachedRuntime, cachedSnapshots, UnsupportedWorkspaceRuntimeError, WorkspaceRuntimeOwnershipError;
|
|
9666
9952
|
var init_workspace_runtime = __esm({
|
|
9667
9953
|
"src/core/workspace-runtime.ts"() {
|
|
9668
9954
|
"use strict";
|
|
9669
9955
|
import_node_child_process14 = require("child_process");
|
|
9670
9956
|
import_node_fs23 = __toESM(require("fs"));
|
|
9671
|
-
|
|
9957
|
+
import_node_path27 = __toESM(require("path"));
|
|
9672
9958
|
init_atomic_file();
|
|
9673
9959
|
init_devpod_registry();
|
|
9674
9960
|
init_devsy_workspaces();
|
|
9675
9961
|
init_router();
|
|
9676
9962
|
init_workspace();
|
|
9677
9963
|
SUPPORTED_RUNTIMES2 = ["devpod", "devsy"];
|
|
9678
|
-
RUNTIME_CONFIG_FILE =
|
|
9964
|
+
RUNTIME_CONFIG_FILE = import_node_path27.default.join(DEVROUTER_HOME, "workspace-runtime.json");
|
|
9679
9965
|
INACTIVITY_TIMEOUT_PATTERN = /^(?:\d+(?:ms|s|m|h))+$/;
|
|
9680
9966
|
UnsupportedWorkspaceRuntimeError = class extends Error {
|
|
9681
9967
|
};
|
|
@@ -9753,7 +10039,7 @@ function parseMinimumNodeMajor(value) {
|
|
|
9753
10039
|
return Number(match[1]);
|
|
9754
10040
|
}
|
|
9755
10041
|
function readPackageJson(repoPath) {
|
|
9756
|
-
const packagePath =
|
|
10042
|
+
const packagePath = import_node_path28.default.join(repoPath, "package.json");
|
|
9757
10043
|
if (!import_node_fs24.default.existsSync(packagePath)) {
|
|
9758
10044
|
return void 0;
|
|
9759
10045
|
}
|
|
@@ -9942,13 +10228,13 @@ function buildGlobalToolChecksWithoutTelemetry(repoPath) {
|
|
|
9942
10228
|
checks.push(nodeToolchainCheck(repoPath));
|
|
9943
10229
|
return checks;
|
|
9944
10230
|
}
|
|
9945
|
-
var import_node_child_process15, import_node_fs24,
|
|
10231
|
+
var import_node_child_process15, import_node_fs24, import_node_path28, DEVSY_DISABLE_TELEMETRY;
|
|
9946
10232
|
var init_tool_diagnostics = __esm({
|
|
9947
10233
|
"src/core/tool-diagnostics.ts"() {
|
|
9948
10234
|
"use strict";
|
|
9949
10235
|
import_node_child_process15 = require("child_process");
|
|
9950
10236
|
import_node_fs24 = __toESM(require("fs"));
|
|
9951
|
-
|
|
10237
|
+
import_node_path28 = __toESM(require("path"));
|
|
9952
10238
|
init_devsy_agent();
|
|
9953
10239
|
init_workspace_runtime();
|
|
9954
10240
|
DEVSY_DISABLE_TELEMETRY = "DEVSY_DISABLE_TELEMETRY";
|
|
@@ -10054,6 +10340,54 @@ var init_devpod_workspaces = __esm({
|
|
|
10054
10340
|
}
|
|
10055
10341
|
});
|
|
10056
10342
|
|
|
10343
|
+
// src/core/managed-compose-population.ts
|
|
10344
|
+
function proveManagedComposePopulation(options) {
|
|
10345
|
+
const { plan, repoPath, composeProject, featureDirectory, devsyRoot, containers: containers2 } = options;
|
|
10346
|
+
const services = /* @__PURE__ */ new Set();
|
|
10347
|
+
for (const container of containers2) {
|
|
10348
|
+
const service = container.labels["com.docker.compose.service"] ?? "";
|
|
10349
|
+
if (!plan.nativeRunServices.includes(service) || services.has(service) || container.labels["com.docker.compose.project"] !== composeProject || !sameWorkspacePath(
|
|
10350
|
+
container.labels["com.docker.compose.project.working_dir"] ?? "",
|
|
10351
|
+
plan.composeDirectory
|
|
10352
|
+
)) {
|
|
10353
|
+
throw new Error("Managed stop found unexpected or duplicate project membership.");
|
|
10354
|
+
}
|
|
10355
|
+
services.add(service);
|
|
10356
|
+
const files = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").map((file) => file.trim());
|
|
10357
|
+
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]))) {
|
|
10358
|
+
throw new Error("Managed stop Compose file identity changed.");
|
|
10359
|
+
}
|
|
10360
|
+
for (const file of files.slice(plan.composeFiles.length)) {
|
|
10361
|
+
if (import_node_path29.default.dirname(file) !== featureDirectory || !/^docker-compose\.devcontainer\.containerFeatures-[a-zA-Z0-9_-]+\.yml$/.test(
|
|
10362
|
+
import_node_path29.default.basename(file)
|
|
10363
|
+
) || import_node_fs25.default.realpathSync(file) !== import_node_path29.default.join(import_node_fs25.default.realpathSync(devsyRoot), import_node_path29.default.relative(devsyRoot, file))) {
|
|
10364
|
+
throw new Error("Managed stop refuses a foreign or escaped provider Compose file.");
|
|
10365
|
+
}
|
|
10366
|
+
}
|
|
10367
|
+
}
|
|
10368
|
+
if (plan.desiredServices.some((service) => !services.has(service))) {
|
|
10369
|
+
throw new Error("Managed stop cannot prove the complete retained service population.");
|
|
10370
|
+
}
|
|
10371
|
+
const primary = containers2.find(
|
|
10372
|
+
(c) => c.labels["com.docker.compose.service"] === plan.primaryService
|
|
10373
|
+
);
|
|
10374
|
+
if (primary?.mounts.filter(
|
|
10375
|
+
(mount) => mount.Type === "bind" && sameWorkspacePath(mount.Source, repoPath)
|
|
10376
|
+
).length !== 1) {
|
|
10377
|
+
throw new Error("Managed stop cannot prove the exact primary workspace mount.");
|
|
10378
|
+
}
|
|
10379
|
+
return primary;
|
|
10380
|
+
}
|
|
10381
|
+
var import_node_fs25, import_node_path29;
|
|
10382
|
+
var init_managed_compose_population = __esm({
|
|
10383
|
+
"src/core/managed-compose-population.ts"() {
|
|
10384
|
+
"use strict";
|
|
10385
|
+
import_node_fs25 = __toESM(require("fs"));
|
|
10386
|
+
import_node_path29 = __toESM(require("path"));
|
|
10387
|
+
init_workspace();
|
|
10388
|
+
}
|
|
10389
|
+
});
|
|
10390
|
+
|
|
10057
10391
|
// src/core/reliability-context.ts
|
|
10058
10392
|
function claimLifecycleEffect() {
|
|
10059
10393
|
claim?.();
|
|
@@ -10065,81 +10399,6 @@ var init_reliability_context = __esm({
|
|
|
10065
10399
|
}
|
|
10066
10400
|
});
|
|
10067
10401
|
|
|
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
10402
|
// src/core/workspace-ownership.ts
|
|
10144
10403
|
function commandError(command, repoPath, stderr) {
|
|
10145
10404
|
return new Error(
|
|
@@ -10155,7 +10414,7 @@ function resolveGitCommonDir(repoPath) {
|
|
|
10155
10414
|
if (result.status !== 0 || !output2) {
|
|
10156
10415
|
throw commandError("Could not resolve the Git common directory", repoPath, result.stderr);
|
|
10157
10416
|
}
|
|
10158
|
-
return comparableWorkspacePath(
|
|
10417
|
+
return comparableWorkspacePath(import_node_path30.default.isAbsolute(output2) ? output2 : import_node_path30.default.resolve(repoPath, output2));
|
|
10159
10418
|
}
|
|
10160
10419
|
function resolveGitTopLevel(repoPath) {
|
|
10161
10420
|
const result = (0, import_node_child_process17.spawnSync)("git", ["-C", repoPath, "rev-parse", "--show-toplevel"], {
|
|
@@ -10206,7 +10465,7 @@ function listGitWorktrees(repoPath) {
|
|
|
10206
10465
|
return worktrees;
|
|
10207
10466
|
}
|
|
10208
10467
|
function ownershipDirectory(repoPath) {
|
|
10209
|
-
return
|
|
10468
|
+
return import_node_path30.default.join(resolveGitCommonDir(repoPath), OWNERSHIP_DIR);
|
|
10210
10469
|
}
|
|
10211
10470
|
function validateWorkspace(value, label) {
|
|
10212
10471
|
if (typeof value !== "string" || wsFromBranch(value) !== value) {
|
|
@@ -10234,7 +10493,7 @@ function validateRecord(value, expectedWorkspace) {
|
|
|
10234
10493
|
`workspace ownership file '${expectedWorkspace}' contains identity '${workspace}'`
|
|
10235
10494
|
);
|
|
10236
10495
|
}
|
|
10237
|
-
if (typeof candidate.worktreePath !== "string" || !
|
|
10496
|
+
if (typeof candidate.worktreePath !== "string" || !import_node_path30.default.isAbsolute(candidate.worktreePath)) {
|
|
10238
10497
|
throw new Error("invalid workspace ownership worktreePath");
|
|
10239
10498
|
}
|
|
10240
10499
|
if (candidate.branch !== null && typeof candidate.branch !== "string") {
|
|
@@ -10252,7 +10511,7 @@ function validateRecord(value, expectedWorkspace) {
|
|
|
10252
10511
|
};
|
|
10253
10512
|
}
|
|
10254
10513
|
function recordPath(repoPath, workspace) {
|
|
10255
|
-
return
|
|
10514
|
+
return import_node_path30.default.join(
|
|
10256
10515
|
ownershipDirectory(repoPath),
|
|
10257
10516
|
`${validateWorkspace(workspace, "workspace")}.json`
|
|
10258
10517
|
);
|
|
@@ -10260,7 +10519,7 @@ function recordPath(repoPath, workspace) {
|
|
|
10260
10519
|
function readRecordFile(filePath, expectedWorkspace) {
|
|
10261
10520
|
let parsed;
|
|
10262
10521
|
try {
|
|
10263
|
-
parsed = JSON.parse(
|
|
10522
|
+
parsed = JSON.parse(import_node_fs26.default.readFileSync(filePath, "utf-8"));
|
|
10264
10523
|
} catch (error) {
|
|
10265
10524
|
if (error instanceof SyntaxError) {
|
|
10266
10525
|
throw new Error(`invalid workspace ownership JSON at '${filePath}'`);
|
|
@@ -10285,7 +10544,7 @@ function listWorkspaceOwnership(repoPath) {
|
|
|
10285
10544
|
function listWorkspaceOwnershipInDirectory(directory) {
|
|
10286
10545
|
let entries;
|
|
10287
10546
|
try {
|
|
10288
|
-
entries =
|
|
10547
|
+
entries = import_node_fs26.default.readdirSync(directory, { withFileTypes: true });
|
|
10289
10548
|
} catch (error) {
|
|
10290
10549
|
if (error.code === "ENOENT") return [];
|
|
10291
10550
|
throw error;
|
|
@@ -10293,24 +10552,24 @@ function listWorkspaceOwnershipInDirectory(directory) {
|
|
|
10293
10552
|
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).sort((left, right) => left.name.localeCompare(right.name)).map((entry) => {
|
|
10294
10553
|
const workspace = entry.name.slice(0, -".json".length);
|
|
10295
10554
|
validateWorkspace(workspace, "filename");
|
|
10296
|
-
return readRecordFile(
|
|
10555
|
+
return readRecordFile(import_node_path30.default.join(directory, entry.name), workspace);
|
|
10297
10556
|
});
|
|
10298
10557
|
}
|
|
10299
10558
|
function writeWorkspaceOwnershipInDirectory(directory, input2) {
|
|
10300
10559
|
const workspace = validateWorkspace(input2.workspace, "workspace");
|
|
10301
10560
|
const devpodId = validateWorkspace(input2.devpodId, "devpodId");
|
|
10302
10561
|
const worktreePath = comparableWorkspacePath(input2.worktreePath);
|
|
10303
|
-
const filePath =
|
|
10562
|
+
const filePath = import_node_path30.default.join(directory, `${workspace}.json`);
|
|
10304
10563
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
10305
10564
|
const records = listWorkspaceOwnershipInDirectory(directory);
|
|
10306
|
-
const existing = records.find((
|
|
10565
|
+
const existing = records.find((record5) => record5.workspace === workspace);
|
|
10307
10566
|
if (existing && !sameWorkspacePath(existing.worktreePath, worktreePath)) {
|
|
10308
10567
|
throw new Error(
|
|
10309
10568
|
`Workspace '${workspace}' already belongs to '${existing.worktreePath}', refusing '${worktreePath}'.`
|
|
10310
10569
|
);
|
|
10311
10570
|
}
|
|
10312
10571
|
const pathOwner = records.find(
|
|
10313
|
-
(
|
|
10572
|
+
(record5) => record5.workspace !== workspace && sameWorkspacePath(record5.worktreePath, worktreePath)
|
|
10314
10573
|
);
|
|
10315
10574
|
if (pathOwner) {
|
|
10316
10575
|
throw new Error(
|
|
@@ -10322,7 +10581,7 @@ function writeWorkspaceOwnershipInDirectory(directory, input2) {
|
|
|
10322
10581
|
`Workspace '${workspace}' already owns DevPod '${existing.devpodId}', refusing '${devpodId}'.`
|
|
10323
10582
|
);
|
|
10324
10583
|
}
|
|
10325
|
-
const
|
|
10584
|
+
const record4 = {
|
|
10326
10585
|
version: OWNERSHIP_VERSION,
|
|
10327
10586
|
workspace,
|
|
10328
10587
|
worktreePath,
|
|
@@ -10331,14 +10590,14 @@ function writeWorkspaceOwnershipInDirectory(directory, input2) {
|
|
|
10331
10590
|
createdAt: existing?.createdAt ?? validateTimestamp(now, "createdAt"),
|
|
10332
10591
|
updatedAt: validateTimestamp(now, "updatedAt")
|
|
10333
10592
|
};
|
|
10334
|
-
writeFileAtomically(filePath, `${JSON.stringify(
|
|
10593
|
+
writeFileAtomically(filePath, `${JSON.stringify(record4, null, 2)}
|
|
10335
10594
|
`);
|
|
10336
|
-
return
|
|
10595
|
+
return record4;
|
|
10337
10596
|
}
|
|
10338
10597
|
function removeWorkspaceOwnershipInDirectory(directory, workspace) {
|
|
10339
|
-
const filePath =
|
|
10598
|
+
const filePath = import_node_path30.default.join(directory, `${validateWorkspace(workspace, "workspace")}.json`);
|
|
10340
10599
|
try {
|
|
10341
|
-
|
|
10600
|
+
import_node_fs26.default.rmSync(filePath);
|
|
10342
10601
|
return true;
|
|
10343
10602
|
} catch (error) {
|
|
10344
10603
|
if (error.code === "ENOENT") return false;
|
|
@@ -10349,7 +10608,7 @@ function sameOwnershipRecord(left, right) {
|
|
|
10349
10608
|
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
10609
|
}
|
|
10351
10610
|
function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
|
|
10352
|
-
const filePath =
|
|
10611
|
+
const filePath = import_node_path30.default.join(
|
|
10353
10612
|
directory,
|
|
10354
10613
|
`${validateWorkspace(expected.workspace, "workspace")}.json`
|
|
10355
10614
|
);
|
|
@@ -10361,14 +10620,14 @@ function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
|
|
|
10361
10620
|
throw error;
|
|
10362
10621
|
}
|
|
10363
10622
|
if (!sameOwnershipRecord(current, expected)) return "changed";
|
|
10364
|
-
|
|
10623
|
+
import_node_fs26.default.rmSync(filePath);
|
|
10365
10624
|
return "removed";
|
|
10366
10625
|
}
|
|
10367
10626
|
function withWorkspaceOwnershipTransaction(repoPath, operation, options = {}) {
|
|
10368
10627
|
const directory = ownershipDirectory(repoPath);
|
|
10369
|
-
|
|
10628
|
+
import_node_fs26.default.mkdirSync(directory, { recursive: true });
|
|
10370
10629
|
return withFileLockSync(
|
|
10371
|
-
|
|
10630
|
+
import_node_path30.default.join(directory, ".lock"),
|
|
10372
10631
|
{
|
|
10373
10632
|
activity: "workspace ownership transaction",
|
|
10374
10633
|
target: `'${repoPath}'`,
|
|
@@ -10401,7 +10660,7 @@ function persistedWorkspaceOwners(repoPath, worktreePath) {
|
|
|
10401
10660
|
try {
|
|
10402
10661
|
workspace = readPersistedWorkspace(worktree.path);
|
|
10403
10662
|
} catch (error) {
|
|
10404
|
-
if (!
|
|
10663
|
+
if (!import_node_fs26.default.existsSync(worktree.path)) continue;
|
|
10405
10664
|
throw error;
|
|
10406
10665
|
}
|
|
10407
10666
|
if (!workspace) continue;
|
|
@@ -10415,7 +10674,7 @@ function persistedWorkspaceOwners(repoPath, worktreePath) {
|
|
|
10415
10674
|
}
|
|
10416
10675
|
function claimConflict(workspace, devpodId, worktreePath, records, providerWorkspaces, persistedOwners, exactRecord) {
|
|
10417
10676
|
const recordOwner = records.find(
|
|
10418
|
-
(
|
|
10677
|
+
(record4) => record4 !== exactRecord && (record4.workspace === workspace || record4.devpodId === devpodId)
|
|
10419
10678
|
);
|
|
10420
10679
|
if (recordOwner) {
|
|
10421
10680
|
return `workspace owner record '${recordOwner.workspace}' for '${recordOwner.worktreePath}'`;
|
|
@@ -10438,7 +10697,7 @@ function claimWorkspaceIdentity(repoPath, input2) {
|
|
|
10438
10697
|
return withWorkspaceOwnershipTransaction(repoPath, (transaction) => {
|
|
10439
10698
|
const records = transaction.list();
|
|
10440
10699
|
const exactRecords = records.filter(
|
|
10441
|
-
(
|
|
10700
|
+
(record4) => sameWorkspacePath(record4.worktreePath, worktreePath)
|
|
10442
10701
|
);
|
|
10443
10702
|
if (exactRecords.length > 1) {
|
|
10444
10703
|
throw new Error(
|
|
@@ -10552,11 +10811,11 @@ function removeWorkspaceOwnership(repoPath, workspace) {
|
|
|
10552
10811
|
(transaction) => transaction.remove(workspace)
|
|
10553
10812
|
);
|
|
10554
10813
|
}
|
|
10555
|
-
function inspectWorkspaceOwnership(
|
|
10814
|
+
function inspectWorkspaceOwnership(record4, worktrees, devpods) {
|
|
10556
10815
|
const worktree = worktrees.find(
|
|
10557
|
-
(candidate) => sameWorkspacePath(candidate.path,
|
|
10816
|
+
(candidate) => sameWorkspacePath(candidate.path, record4.worktreePath)
|
|
10558
10817
|
);
|
|
10559
|
-
const devpodOwnership = devpods ? inspectDevpodWorkspaceOwnership(devpods,
|
|
10818
|
+
const devpodOwnership = devpods ? inspectDevpodWorkspaceOwnership(devpods, record4.devpodId, record4.worktreePath) : void 0;
|
|
10560
10819
|
const devpodStatus = devpodOwnership?.status ?? "unknown";
|
|
10561
10820
|
if (devpodStatus === "conflict") {
|
|
10562
10821
|
return { ownerStatus: "conflict", devpodStatus, worktree };
|
|
@@ -10564,7 +10823,7 @@ function inspectWorkspaceOwnership(record3, worktrees, devpods) {
|
|
|
10564
10823
|
if (worktree?.locked) {
|
|
10565
10824
|
return { ownerStatus: "locked", devpodStatus, worktree };
|
|
10566
10825
|
}
|
|
10567
|
-
if ((!worktree || worktree.prunable) &&
|
|
10826
|
+
if ((!worktree || worktree.prunable) && import_node_fs26.default.existsSync(record4.worktreePath)) {
|
|
10568
10827
|
return { ownerStatus: "conflict", devpodStatus, worktree };
|
|
10569
10828
|
}
|
|
10570
10829
|
if (!worktree || worktree.prunable) {
|
|
@@ -10577,7 +10836,7 @@ function inspectWorkspaceOwnership(record3, worktrees, devpods) {
|
|
|
10577
10836
|
return { ownerStatus: "conflict", devpodStatus, worktree };
|
|
10578
10837
|
}
|
|
10579
10838
|
return {
|
|
10580
|
-
ownerStatus: persisted ===
|
|
10839
|
+
ownerStatus: persisted === record4.workspace ? "present" : "conflict",
|
|
10581
10840
|
devpodStatus,
|
|
10582
10841
|
worktree
|
|
10583
10842
|
};
|
|
@@ -10585,23 +10844,231 @@ function inspectWorkspaceOwnership(record3, worktrees, devpods) {
|
|
|
10585
10844
|
function listMissingWorkspaceOwnership(repoPath) {
|
|
10586
10845
|
const worktrees = listGitWorktrees(repoPath);
|
|
10587
10846
|
return listWorkspaceOwnership(repoPath).filter(
|
|
10588
|
-
(
|
|
10847
|
+
(record4) => inspectWorkspaceOwnership(record4, worktrees, void 0).ownerStatus === "missing"
|
|
10589
10848
|
);
|
|
10590
10849
|
}
|
|
10591
|
-
var import_node_child_process17,
|
|
10850
|
+
var import_node_child_process17, import_node_fs26, import_node_path30, READ_ONLY_GIT_ENV, OWNERSHIP_VERSION, OWNERSHIP_DIR;
|
|
10592
10851
|
var init_workspace_ownership = __esm({
|
|
10593
10852
|
"src/core/workspace-ownership.ts"() {
|
|
10594
10853
|
"use strict";
|
|
10595
10854
|
import_node_child_process17 = require("child_process");
|
|
10596
|
-
|
|
10597
|
-
|
|
10855
|
+
import_node_fs26 = __toESM(require("fs"));
|
|
10856
|
+
import_node_path30 = __toESM(require("path"));
|
|
10598
10857
|
init_atomic_file();
|
|
10599
10858
|
init_devpod_workspaces();
|
|
10600
10859
|
init_file_lock();
|
|
10601
10860
|
init_workspace();
|
|
10602
10861
|
READ_ONLY_GIT_ENV = { ...process.env, GIT_OPTIONAL_LOCKS: "0" };
|
|
10603
10862
|
OWNERSHIP_VERSION = 1;
|
|
10604
|
-
OWNERSHIP_DIR =
|
|
10863
|
+
OWNERSHIP_DIR = import_node_path30.default.join("devrouter", "workspaces");
|
|
10864
|
+
}
|
|
10865
|
+
});
|
|
10866
|
+
|
|
10867
|
+
// src/core/managed-stop-recovery.ts
|
|
10868
|
+
function registration(state) {
|
|
10869
|
+
resetWorkspaceRuntimeCaches();
|
|
10870
|
+
if (resolveWorkspaceRuntimeOrDefault(state.repoPath) !== "devsy")
|
|
10871
|
+
throw new Error("Stop provider changed.");
|
|
10872
|
+
const owner = inspectDevsyWorkspaceOwnership(
|
|
10873
|
+
listDevsyWorkspaces(),
|
|
10874
|
+
state.devpodId,
|
|
10875
|
+
state.repoPath
|
|
10876
|
+
);
|
|
10877
|
+
if (owner.status !== "owned") throw new Error("Stop requires one exact provider registration.");
|
|
10878
|
+
if (isLinkedWorktree(state.repoPath)) {
|
|
10879
|
+
const workspace = resolveWorktreeWorkspace(state.repoPath);
|
|
10880
|
+
const record4 = workspace ? readWorkspaceOwnership(state.repoPath, workspace) : void 0;
|
|
10881
|
+
if (!workspace || workspace !== state.workspace || !record4 || record4.devpodId !== state.devpodId || !sameWorkspacePath(record4.worktreePath, state.repoPath))
|
|
10882
|
+
throw new Error("Stop workspace identity changed.");
|
|
10883
|
+
resolveGitCommonDir(state.repoPath);
|
|
10884
|
+
} else if (state.workspace !== void 0) throw new Error("Stop checkout identity changed.");
|
|
10885
|
+
const value = owner.workspace;
|
|
10886
|
+
return {
|
|
10887
|
+
context: value.context ?? "",
|
|
10888
|
+
providerId: value.id,
|
|
10889
|
+
uid: value.uid ?? "",
|
|
10890
|
+
sourcePath: value.source.localFolder,
|
|
10891
|
+
sourceContainer: value.source.container ?? ""
|
|
10892
|
+
};
|
|
10893
|
+
}
|
|
10894
|
+
function compareText(left, right) {
|
|
10895
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
10896
|
+
}
|
|
10897
|
+
function orderedMounts(mounts) {
|
|
10898
|
+
return mounts.map((mount) => ({ ...mount })).sort((a, b) => compareText(JSON.stringify(a), JSON.stringify(b)));
|
|
10899
|
+
}
|
|
10900
|
+
function identity(container) {
|
|
10901
|
+
return {
|
|
10902
|
+
id: container.id,
|
|
10903
|
+
service: container.labels["com.docker.compose.service"] ?? "",
|
|
10904
|
+
configFiles: (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").map((file) => file.trim()),
|
|
10905
|
+
mounts: orderedMounts(container.mounts)
|
|
10906
|
+
};
|
|
10907
|
+
}
|
|
10908
|
+
function prove(state, baseline) {
|
|
10909
|
+
const owner = registration(state);
|
|
10910
|
+
if (Object.entries(owner).some(
|
|
10911
|
+
([key, value]) => baseline[key] !== value
|
|
10912
|
+
)) {
|
|
10913
|
+
throw new Error("Retained provider identity changed.");
|
|
10914
|
+
}
|
|
10915
|
+
if (inspectManagedStopDaemon(baseline.endpoint) !== baseline.daemonId)
|
|
10916
|
+
throw new Error("Stop daemon changed.");
|
|
10917
|
+
const containers2 = inspectManagedStopContainers(baseline.project, baseline.endpoint);
|
|
10918
|
+
const expected = baseline.containers.map((container) => ({ ...container, mounts: orderedMounts(container.mounts) })).sort((a, b) => compareText(a.id, b.id));
|
|
10919
|
+
const observed = containers2.map(identity).sort((a, b) => compareText(a.id, b.id));
|
|
10920
|
+
if (!(0, import_node_util2.isDeepStrictEqual)(expected, observed) || containers2.some(
|
|
10921
|
+
(container) => container.labels["com.docker.compose.project.working_dir"] !== baseline.composeDirectory
|
|
10922
|
+
)) {
|
|
10923
|
+
throw new Error("Retained container population or immutable identity changed.");
|
|
10924
|
+
}
|
|
10925
|
+
const workspaceIds = inspectManagedStopWorkspaceIds(
|
|
10926
|
+
baseline.endpoint,
|
|
10927
|
+
baseline.composeDirectory
|
|
10928
|
+
).sort();
|
|
10929
|
+
if (!(0, import_node_util2.isDeepStrictEqual)(workspaceIds, expected.map((container) => container.id).sort())) {
|
|
10930
|
+
throw new Error("Workspace contains an unrecorded container population.");
|
|
10931
|
+
}
|
|
10932
|
+
const primary = baseline.containers.find(
|
|
10933
|
+
(container) => container.service === baseline.primaryService
|
|
10934
|
+
);
|
|
10935
|
+
if (!primary) throw new Error("Retained primary is missing.");
|
|
10936
|
+
const uidBytes = Buffer.byteLength(baseline.uid);
|
|
10937
|
+
const runnerId = uidBytes === 16 || uidBytes === 40 ? baseline.uid : baseline.providerId;
|
|
10938
|
+
if (!baseline.sourceContainer && inspectManagedStopRunnerId(baseline.endpoint, primary.id) !== runnerId) {
|
|
10939
|
+
throw new Error("Provider-to-container binding changed.");
|
|
10940
|
+
}
|
|
10941
|
+
if (!(0, import_node_util2.isDeepStrictEqual)(registration(state), owner) || inspectManagedStopDaemon(baseline.endpoint) !== baseline.daemonId) {
|
|
10942
|
+
throw new Error("Stop authority changed during inspection.");
|
|
10943
|
+
}
|
|
10944
|
+
return containers2;
|
|
10945
|
+
}
|
|
10946
|
+
function proveRetainedManagedStop(state) {
|
|
10947
|
+
const baseline = validateManagedStopBaseline(state.stopBaseline, state);
|
|
10948
|
+
if (!(0, import_node_util2.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state)) {
|
|
10949
|
+
throw new Error("Retained stop generation changed.");
|
|
10950
|
+
}
|
|
10951
|
+
const result = prove(state, baseline);
|
|
10952
|
+
if (!(0, import_node_util2.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state)) {
|
|
10953
|
+
throw new Error("Retained stop generation changed during inspection.");
|
|
10954
|
+
}
|
|
10955
|
+
return result;
|
|
10956
|
+
}
|
|
10957
|
+
function stopFromManagedBaseline(state) {
|
|
10958
|
+
const baseline = validateManagedStopBaseline(state.stopBaseline, state);
|
|
10959
|
+
const stopped = /* @__PURE__ */ new Set();
|
|
10960
|
+
for (const initial of proveRetainedManagedStop(state))
|
|
10961
|
+
if (!initial.state.Running) stopped.add(initial.id);
|
|
10962
|
+
const inspect = () => {
|
|
10963
|
+
const current = proveRetainedManagedStop(state);
|
|
10964
|
+
if (current.some((container) => stopped.has(container.id) && container.state.Running)) {
|
|
10965
|
+
throw new Error("Retained container restarted during stop.");
|
|
10966
|
+
}
|
|
10967
|
+
for (const container of current) if (!container.state.Running) stopped.add(container.id);
|
|
10968
|
+
return current;
|
|
10969
|
+
};
|
|
10970
|
+
for (const retained of baseline.containers) {
|
|
10971
|
+
if (inspect().find((container) => container.id === retained.id)?.state.Running) {
|
|
10972
|
+
claimLifecycleEffect();
|
|
10973
|
+
stopPinnedManagedContainer(baseline.endpoint, retained.id);
|
|
10974
|
+
if (inspect().find((container) => container.id === retained.id)?.state.Running)
|
|
10975
|
+
throw new Error("Container cessation is not proven.");
|
|
10976
|
+
}
|
|
10977
|
+
}
|
|
10978
|
+
if (inspect().some((container) => container.state.Running))
|
|
10979
|
+
throw new Error("Retained workloads remain running.");
|
|
10980
|
+
}
|
|
10981
|
+
var import_node_os2, import_node_path31, import_node_util2;
|
|
10982
|
+
var init_managed_stop_recovery = __esm({
|
|
10983
|
+
"src/core/managed-stop-recovery.ts"() {
|
|
10984
|
+
"use strict";
|
|
10985
|
+
import_node_os2 = __toESM(require("os"));
|
|
10986
|
+
import_node_path31 = __toESM(require("path"));
|
|
10987
|
+
import_node_util2 = require("util");
|
|
10988
|
+
init_devpod_environment();
|
|
10989
|
+
init_devsy_workspaces();
|
|
10990
|
+
init_managed_compose_population();
|
|
10991
|
+
init_managed_runtime_state();
|
|
10992
|
+
init_managed_stop_baseline();
|
|
10993
|
+
init_reliability_context();
|
|
10994
|
+
init_workspace();
|
|
10995
|
+
init_workspace_ownership();
|
|
10996
|
+
init_workspace_runtime();
|
|
10997
|
+
}
|
|
10998
|
+
});
|
|
10999
|
+
|
|
11000
|
+
// src/core/route-publication.ts
|
|
11001
|
+
function routedAppsFromConfig(config) {
|
|
11002
|
+
return config.apps.filter((app) => app.kind !== "dependency");
|
|
11003
|
+
}
|
|
11004
|
+
function configuredProxyAppsFromConfig(config) {
|
|
11005
|
+
return routedAppsFromConfig(config).filter(
|
|
11006
|
+
(app) => app.runtime === "proxy"
|
|
11007
|
+
);
|
|
11008
|
+
}
|
|
11009
|
+
function proxyAppsFromConfig(config) {
|
|
11010
|
+
const routedApps = routedAppsFromConfig(config);
|
|
11011
|
+
const proxyApps = configuredProxyAppsFromConfig(config);
|
|
11012
|
+
const unsupported = routedApps.filter((app) => app.runtime !== "proxy");
|
|
11013
|
+
if (unsupported.length > 0) {
|
|
11014
|
+
throw new Error(
|
|
11015
|
+
`Environment reconciliation supports proxy runtime apps only; unsupported: ${unsupported.map((app) => `${app.name} (${app.runtime})`).join(", ")}`
|
|
11016
|
+
);
|
|
11017
|
+
}
|
|
11018
|
+
return proxyApps;
|
|
11019
|
+
}
|
|
11020
|
+
async function ensureRouteInfrastructure(apps, options = {}) {
|
|
11021
|
+
claimLifecycleEffect();
|
|
11022
|
+
ensureRouterFiles();
|
|
11023
|
+
await ensureNetwork(DEVNET_NAME);
|
|
11024
|
+
claimLifecycleEffect();
|
|
11025
|
+
const tlsCoverage = await ensureTLSHostsCovered(
|
|
11026
|
+
apps.map((app) => app.host),
|
|
11027
|
+
options
|
|
11028
|
+
);
|
|
11029
|
+
for (const app of apps) {
|
|
11030
|
+
if (app.protocol === "tcp") {
|
|
11031
|
+
if (!isTLSEnabled()) {
|
|
11032
|
+
throw new Error(
|
|
11033
|
+
`TCP route '${app.name}' requires TLS. Run: ${tlsSetupCommand(options.repoPath)}`
|
|
11034
|
+
);
|
|
11035
|
+
}
|
|
11036
|
+
activateTcpProtocol(app.tcpProtocol);
|
|
11037
|
+
}
|
|
11038
|
+
}
|
|
11039
|
+
claimLifecycleEffect();
|
|
11040
|
+
startRouterStack();
|
|
11041
|
+
return tlsCoverage;
|
|
11042
|
+
}
|
|
11043
|
+
async function replacePublishedProxyRoutes(repoPath, config, workspace, options = {}) {
|
|
11044
|
+
const apps = proxyAppsFromConfig(config);
|
|
11045
|
+
const tlsCoverage = options.prepareInfrastructure === false ? { refreshed: false } : await ensureRouteInfrastructure(apps, { repoPath });
|
|
11046
|
+
const routes = apps.map((app) => {
|
|
11047
|
+
const upstream = parseUpstream(app.upstream);
|
|
11048
|
+
return {
|
|
11049
|
+
name: app.name,
|
|
11050
|
+
host: app.host,
|
|
11051
|
+
protocol: app.protocol,
|
|
11052
|
+
tcpProtocol: app.protocol === "tcp" ? app.tcpProtocol : void 0,
|
|
11053
|
+
repoPath,
|
|
11054
|
+
port: upstream.port,
|
|
11055
|
+
mode: "proxy",
|
|
11056
|
+
upstreamHost: upstream.upstreamHost,
|
|
11057
|
+
workspace
|
|
11058
|
+
};
|
|
11059
|
+
});
|
|
11060
|
+
claimLifecycleEffect();
|
|
11061
|
+
replaceHostRoutesForRepo(repoPath, routes);
|
|
11062
|
+
return { routes, tlsRefreshed: tlsCoverage.refreshed };
|
|
11063
|
+
}
|
|
11064
|
+
var init_route_publication = __esm({
|
|
11065
|
+
"src/core/route-publication.ts"() {
|
|
11066
|
+
"use strict";
|
|
11067
|
+
init_docker();
|
|
11068
|
+
init_host_routes();
|
|
11069
|
+
init_reliability_context();
|
|
11070
|
+
init_router();
|
|
11071
|
+
init_tls();
|
|
10605
11072
|
}
|
|
10606
11073
|
});
|
|
10607
11074
|
|
|
@@ -10627,8 +11094,12 @@ function stopRetainedManagedDevsyWorkspace(options) {
|
|
|
10627
11094
|
if (state.devpodId !== devsyId || linked && !workspace) {
|
|
10628
11095
|
throw new Error("Managed stop requires the exact retained workspace identity.");
|
|
10629
11096
|
}
|
|
11097
|
+
if (state.stopBaseline) {
|
|
11098
|
+
stopFromManagedBaseline(state);
|
|
11099
|
+
return true;
|
|
11100
|
+
}
|
|
10630
11101
|
const workspaceEnv = workspace ? { token: workspace, gitCommonDir: resolveGitCommonDir(repoPath) } : void 0;
|
|
10631
|
-
function
|
|
11102
|
+
function registration2() {
|
|
10632
11103
|
resetWorkspaceRuntimeCaches();
|
|
10633
11104
|
if (resolveWorkspaceRuntimeOrDefault(repoPath) !== "devsy") {
|
|
10634
11105
|
throw new Error("Managed stop provider selection changed.");
|
|
@@ -10638,19 +11109,19 @@ function stopRetainedManagedDevsyWorkspace(options) {
|
|
|
10638
11109
|
throw new Error("Managed stop requires one exact retained Devsy registration.");
|
|
10639
11110
|
}
|
|
10640
11111
|
if (workspace) {
|
|
10641
|
-
const
|
|
10642
|
-
if (!
|
|
11112
|
+
const record4 = readWorkspaceOwnership(repoPath, workspace);
|
|
11113
|
+
if (!record4 || record4.devpodId !== devsyId || !sameWorkspacePath(record4.worktreePath, repoPath) || resolveWorktreeWorkspace(repoPath) !== workspace || resolveGitCommonDir(repoPath) !== workspaceEnv?.gitCommonDir) {
|
|
10643
11114
|
throw new Error("Managed stop workspace ownership changed.");
|
|
10644
11115
|
}
|
|
10645
11116
|
}
|
|
10646
11117
|
return owner.workspace;
|
|
10647
11118
|
}
|
|
10648
|
-
const context =
|
|
11119
|
+
const context = registration2().context;
|
|
10649
11120
|
if (!context || !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(context) || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(devsyId)) {
|
|
10650
11121
|
throw new Error("Managed stop requires a valid exact Devsy context and workspace ID.");
|
|
10651
11122
|
}
|
|
10652
|
-
const devsyRoot =
|
|
10653
|
-
const featureDirectory =
|
|
11123
|
+
const devsyRoot = import_node_path32.default.resolve(process.env.DEVSY_HOME || import_node_path32.default.join(import_node_os3.default.homedir(), ".devsy"));
|
|
11124
|
+
const featureDirectory = import_node_path32.default.join(
|
|
10654
11125
|
devsyRoot,
|
|
10655
11126
|
"contexts",
|
|
10656
11127
|
context,
|
|
@@ -10659,8 +11130,8 @@ function stopRetainedManagedDevsyWorkspace(options) {
|
|
|
10659
11130
|
"agent",
|
|
10660
11131
|
".docker-compose"
|
|
10661
11132
|
);
|
|
10662
|
-
function
|
|
10663
|
-
if (
|
|
11133
|
+
function prove2(previous) {
|
|
11134
|
+
if (registration2().context !== context || JSON.stringify(readManagedRuntimeState(repoPath, workspace)) !== JSON.stringify(state)) {
|
|
10664
11135
|
throw new Error("Managed stop retained context or runtime record changed.");
|
|
10665
11136
|
}
|
|
10666
11137
|
const runtime = loadRuntimeConfig(repoPath, workspace ?? "", state.profile);
|
|
@@ -10683,49 +11154,24 @@ function stopRetainedManagedDevsyWorkspace(options) {
|
|
|
10683
11154
|
throw new Error("Managed stop requires unchanged recorded resources and configuration.");
|
|
10684
11155
|
}
|
|
10685
11156
|
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(
|
|
11157
|
+
const primary = proveManagedComposePopulation({
|
|
11158
|
+
plan,
|
|
11159
|
+
repoPath,
|
|
11160
|
+
composeProject: state.composeProject,
|
|
11161
|
+
featureDirectory,
|
|
11162
|
+
devsyRoot,
|
|
11163
|
+
containers: containers2
|
|
11164
|
+
});
|
|
11165
|
+
if (previous && (!sameSet(
|
|
10715
11166
|
previous.map((c) => c.id),
|
|
10716
11167
|
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.");
|
|
11168
|
+
) || containers2.some((container) => {
|
|
11169
|
+
const retained = previous.find((entry) => entry.id === container.id);
|
|
11170
|
+
return !retained || containerIdentity(retained) !== containerIdentity(container) || !retained.state.Running && container.state.Running;
|
|
11171
|
+
}))) {
|
|
11172
|
+
throw new Error("Managed stop container identity or quiescent state changed.");
|
|
10727
11173
|
}
|
|
10728
|
-
if (
|
|
11174
|
+
if (registration2().context !== context) {
|
|
10729
11175
|
throw new Error("Managed stop provider context changed during inspection.");
|
|
10730
11176
|
}
|
|
10731
11177
|
const status = inspectDevsyRuntimeStatus(devsyId);
|
|
@@ -10734,7 +11180,7 @@ function stopRetainedManagedDevsyWorkspace(options) {
|
|
|
10734
11180
|
}
|
|
10735
11181
|
return { containers: containers2, primary, status };
|
|
10736
11182
|
}
|
|
10737
|
-
const initial =
|
|
11183
|
+
const initial = prove2();
|
|
10738
11184
|
let providerError;
|
|
10739
11185
|
let providerFailed = false;
|
|
10740
11186
|
if (initial.status === "running") {
|
|
@@ -10747,7 +11193,7 @@ function stopRetainedManagedDevsyWorkspace(options) {
|
|
|
10747
11193
|
}
|
|
10748
11194
|
try {
|
|
10749
11195
|
const stopped = () => {
|
|
10750
|
-
const current =
|
|
11196
|
+
const current = prove2(initial.containers);
|
|
10751
11197
|
if (current.status !== "stopped" || current.primary.state.Running) {
|
|
10752
11198
|
throw new Error("Managed stop has not stopped the exact primary container.");
|
|
10753
11199
|
}
|
|
@@ -10779,17 +11225,18 @@ function stopRetainedManagedDevsyWorkspace(options) {
|
|
|
10779
11225
|
if (providerFailed) throw providerError;
|
|
10780
11226
|
return true;
|
|
10781
11227
|
}
|
|
10782
|
-
var
|
|
11228
|
+
var import_node_os3, import_node_path32;
|
|
10783
11229
|
var init_managed_devsy_stop = __esm({
|
|
10784
11230
|
"src/core/managed-devsy-stop.ts"() {
|
|
10785
11231
|
"use strict";
|
|
10786
|
-
|
|
10787
|
-
|
|
10788
|
-
import_node_path29 = __toESM(require("path"));
|
|
11232
|
+
import_node_os3 = __toESM(require("os"));
|
|
11233
|
+
import_node_path32 = __toESM(require("path"));
|
|
10789
11234
|
init_devcontainer_profile();
|
|
10790
11235
|
init_devpod_environment();
|
|
10791
11236
|
init_devsy_workspaces();
|
|
11237
|
+
init_managed_compose_population();
|
|
10792
11238
|
init_managed_runtime_state();
|
|
11239
|
+
init_managed_stop_recovery();
|
|
10793
11240
|
init_repo_config();
|
|
10794
11241
|
init_route_publication();
|
|
10795
11242
|
init_workspace();
|
|
@@ -10875,20 +11322,20 @@ function stopOwnedDevsyWorkspace(devsyId, worktreePath) {
|
|
|
10875
11322
|
function deleteOwnedDevsyWorkspace(devsyId, worktreePath) {
|
|
10876
11323
|
return mutateOwnedDevsyWorkspace("delete", devsyId, worktreePath);
|
|
10877
11324
|
}
|
|
10878
|
-
var import_node_child_process18, import_node_fs27,
|
|
11325
|
+
var import_node_child_process18, import_node_fs27, import_node_path33, DEVSY_MUTATION_LOCK_FILE, DEVSY_MUTATION_WAIT_MS;
|
|
10879
11326
|
var init_devsy_mutation = __esm({
|
|
10880
11327
|
"src/core/devsy-mutation.ts"() {
|
|
10881
11328
|
"use strict";
|
|
10882
11329
|
import_node_child_process18 = require("child_process");
|
|
10883
11330
|
import_node_fs27 = __toESM(require("fs"));
|
|
10884
|
-
|
|
11331
|
+
import_node_path33 = __toESM(require("path"));
|
|
10885
11332
|
init_devsy_agent();
|
|
10886
11333
|
init_devsy_workspaces();
|
|
10887
11334
|
init_file_lock();
|
|
10888
11335
|
init_managed_devsy_stop();
|
|
10889
11336
|
init_reliability_context();
|
|
10890
11337
|
init_router();
|
|
10891
|
-
DEVSY_MUTATION_LOCK_FILE =
|
|
11338
|
+
DEVSY_MUTATION_LOCK_FILE = import_node_path33.default.join(DEVROUTER_HOME, "devsy-mutation.lock");
|
|
10892
11339
|
DEVSY_MUTATION_WAIT_MS = 18e5;
|
|
10893
11340
|
}
|
|
10894
11341
|
});
|
|
@@ -10981,20 +11428,20 @@ function deleteOwnedDevpodWorkspace(devpodId, worktreePath) {
|
|
|
10981
11428
|
resetWorkspaceRuntimeCaches();
|
|
10982
11429
|
return result;
|
|
10983
11430
|
}
|
|
10984
|
-
var import_node_child_process19, import_node_fs28,
|
|
11431
|
+
var import_node_child_process19, import_node_fs28, import_node_path34, DEVPOD_MUTATION_LOCK_FILE, DEVPOD_MUTATION_WAIT_MS;
|
|
10985
11432
|
var init_devpod_mutation = __esm({
|
|
10986
11433
|
"src/core/devpod-mutation.ts"() {
|
|
10987
11434
|
"use strict";
|
|
10988
11435
|
import_node_child_process19 = require("child_process");
|
|
10989
11436
|
import_node_fs28 = __toESM(require("fs"));
|
|
10990
|
-
|
|
11437
|
+
import_node_path34 = __toESM(require("path"));
|
|
10991
11438
|
init_devpod_workspaces();
|
|
10992
11439
|
init_devsy_mutation();
|
|
10993
11440
|
init_file_lock();
|
|
10994
11441
|
init_reliability_context();
|
|
10995
11442
|
init_router();
|
|
10996
11443
|
init_workspace_runtime();
|
|
10997
|
-
DEVPOD_MUTATION_LOCK_FILE =
|
|
11444
|
+
DEVPOD_MUTATION_LOCK_FILE = import_node_path34.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
|
|
10998
11445
|
DEVPOD_MUTATION_WAIT_MS = 18e5;
|
|
10999
11446
|
}
|
|
11000
11447
|
});
|
|
@@ -11009,10 +11456,10 @@ function inRepositoryWorkspaceScope(repoPath, worktreePath, livePaths) {
|
|
|
11009
11456
|
if (livePaths.some((candidate) => sameWorkspacePath(candidate, worktreePath))) return true;
|
|
11010
11457
|
const comparableRepo = comparableWorkspacePath(repoPath);
|
|
11011
11458
|
const comparableWorktree = comparableWorkspacePath(worktreePath);
|
|
11012
|
-
const localRoot =
|
|
11459
|
+
const localRoot = import_node_path35.default.join(comparableRepo, "trees") + import_node_path35.default.sep;
|
|
11013
11460
|
if (comparableWorktree.startsWith(localRoot)) return true;
|
|
11014
|
-
const legacyPrefix = `${
|
|
11015
|
-
return
|
|
11461
|
+
const legacyPrefix = `${import_node_path35.default.basename(comparableRepo)}-`;
|
|
11462
|
+
return import_node_path35.default.dirname(comparableWorktree) === import_node_path35.default.dirname(comparableRepo) && import_node_path35.default.basename(comparableWorktree).startsWith(legacyPrefix);
|
|
11016
11463
|
}
|
|
11017
11464
|
function previewActions(devpodStatus, routeCount, includeRecord) {
|
|
11018
11465
|
const actions = [
|
|
@@ -11041,15 +11488,15 @@ function ownedReason(ownerStatus) {
|
|
|
11041
11488
|
return "Ownership conflicts with live Git, persisted token, or DevPod source evidence.";
|
|
11042
11489
|
}
|
|
11043
11490
|
}
|
|
11044
|
-
function ownedCandidate(
|
|
11045
|
-
const status = inspectWorkspaceOwnership(
|
|
11046
|
-
const routeCount = routesForOwner(routes,
|
|
11491
|
+
function ownedCandidate(record4, worktrees, devpods, routes) {
|
|
11492
|
+
const status = inspectWorkspaceOwnership(record4, worktrees, devpods);
|
|
11493
|
+
const routeCount = routesForOwner(routes, record4.workspace, record4.worktreePath).length;
|
|
11047
11494
|
const eligible = status.ownerStatus === "missing" && status.devpodStatus !== "conflict";
|
|
11048
11495
|
return {
|
|
11049
11496
|
kind: "owned",
|
|
11050
|
-
workspace:
|
|
11051
|
-
devpodId:
|
|
11052
|
-
worktreePath:
|
|
11497
|
+
workspace: record4.workspace,
|
|
11498
|
+
devpodId: record4.devpodId,
|
|
11499
|
+
worktreePath: record4.worktreePath,
|
|
11053
11500
|
ownerStatus: status.ownerStatus,
|
|
11054
11501
|
devpodStatus: status.devpodStatus,
|
|
11055
11502
|
routeCount,
|
|
@@ -11067,10 +11514,10 @@ function legacyCandidates(repoPath, records, worktrees, devpods, routes) {
|
|
|
11067
11514
|
const primaryPath = worktrees[0]?.path;
|
|
11068
11515
|
const evidence = /* @__PURE__ */ new Map();
|
|
11069
11516
|
const isRecordedRoute = (workspace, worktreePath) => records.some(
|
|
11070
|
-
(
|
|
11517
|
+
(record4) => record4.workspace === workspace && sameWorkspacePath(record4.worktreePath, worktreePath)
|
|
11071
11518
|
);
|
|
11072
11519
|
const isRecordedDevpod = (devpodId, worktreePath) => records.some(
|
|
11073
|
-
(
|
|
11520
|
+
(record4) => record4.devpodId === devpodId && sameWorkspacePath(record4.worktreePath, worktreePath)
|
|
11074
11521
|
);
|
|
11075
11522
|
const key = (workspace, worktreePath) => `${workspace}\0${comparableWorkspacePath(worktreePath)}`;
|
|
11076
11523
|
for (const devpod of devpods) {
|
|
@@ -11109,8 +11556,8 @@ function legacyCandidates(repoPath, records, worktrees, devpods, routes) {
|
|
|
11109
11556
|
}));
|
|
11110
11557
|
}
|
|
11111
11558
|
function revalidateCandidate(candidate, repoPath, transaction) {
|
|
11112
|
-
const
|
|
11113
|
-
if (!
|
|
11559
|
+
const record4 = transaction.list().find((entry) => entry.workspace === candidate.workspace);
|
|
11560
|
+
if (!record4 || !sameWorkspacePath(record4.worktreePath, candidate.worktreePath) || record4.devpodId !== candidate.devpodId) {
|
|
11114
11561
|
return {
|
|
11115
11562
|
status: "blocked",
|
|
11116
11563
|
candidate: {
|
|
@@ -11126,12 +11573,12 @@ function revalidateCandidate(candidate, repoPath, transaction) {
|
|
|
11126
11573
|
};
|
|
11127
11574
|
}
|
|
11128
11575
|
const fresh2 = ownedCandidate(
|
|
11129
|
-
|
|
11576
|
+
record4,
|
|
11130
11577
|
listGitWorktrees(repoPath),
|
|
11131
|
-
listDevpodWorkspaces(
|
|
11578
|
+
listDevpodWorkspaces(record4.worktreePath),
|
|
11132
11579
|
listHostRouteState()
|
|
11133
11580
|
);
|
|
11134
|
-
if (fresh2.eligible) return { status: "ready", candidate: fresh2, record:
|
|
11581
|
+
if (fresh2.eligible) return { status: "ready", candidate: fresh2, record: record4 };
|
|
11135
11582
|
return {
|
|
11136
11583
|
status: "blocked",
|
|
11137
11584
|
candidate: {
|
|
@@ -11165,11 +11612,11 @@ function applyCandidate(candidate, repoPath) {
|
|
|
11165
11612
|
};
|
|
11166
11613
|
}
|
|
11167
11614
|
if (revalidated.status === "blocked") return revalidated.candidate;
|
|
11168
|
-
const { candidate: fresh2, record:
|
|
11615
|
+
const { candidate: fresh2, record: record4 } = revalidated;
|
|
11169
11616
|
const actions = [];
|
|
11170
11617
|
let devpodStatus;
|
|
11171
11618
|
try {
|
|
11172
|
-
const mutation = deleteOwnedDevpodWorkspace(
|
|
11619
|
+
const mutation = deleteOwnedDevpodWorkspace(record4.devpodId, record4.worktreePath);
|
|
11173
11620
|
devpodStatus = mutation.status === "changed" ? "owned" : "absent";
|
|
11174
11621
|
} catch (error) {
|
|
11175
11622
|
return { ...fresh2, actions: [failedAction("devpod", error)] };
|
|
@@ -11189,7 +11636,7 @@ function applyCandidate(candidate, repoPath) {
|
|
|
11189
11636
|
return { ...fresh2, actions: [...actions, failedAction("routes", error)] };
|
|
11190
11637
|
}
|
|
11191
11638
|
try {
|
|
11192
|
-
const removal = transaction.removeIfMatches(
|
|
11639
|
+
const removal = transaction.removeIfMatches(record4);
|
|
11193
11640
|
if (removal === "changed") {
|
|
11194
11641
|
throw new Error("Ownership record changed during cleanup; record was retained.");
|
|
11195
11642
|
}
|
|
@@ -11235,10 +11682,10 @@ function inspectWorkspaceGc(repo) {
|
|
|
11235
11682
|
// that owns each record (snapshot reads: the apply path revalidates
|
|
11236
11683
|
// live); legacy detection scans both registries.
|
|
11237
11684
|
...records.map(
|
|
11238
|
-
(
|
|
11239
|
-
|
|
11685
|
+
(record4) => ownedCandidate(
|
|
11686
|
+
record4,
|
|
11240
11687
|
worktrees,
|
|
11241
|
-
listDevpodWorkspacesFromSnapshots(
|
|
11688
|
+
listDevpodWorkspacesFromSnapshots(record4.worktreePath),
|
|
11242
11689
|
routes
|
|
11243
11690
|
)
|
|
11244
11691
|
),
|
|
@@ -11264,11 +11711,11 @@ function applyWorkspaceGc(plan) {
|
|
|
11264
11711
|
candidates
|
|
11265
11712
|
};
|
|
11266
11713
|
}
|
|
11267
|
-
var
|
|
11714
|
+
var import_node_path35;
|
|
11268
11715
|
var init_workspace_gc = __esm({
|
|
11269
11716
|
"src/core/workspace-gc.ts"() {
|
|
11270
11717
|
"use strict";
|
|
11271
|
-
|
|
11718
|
+
import_node_path35 = __toESM(require("path"));
|
|
11272
11719
|
init_devpod_mutation();
|
|
11273
11720
|
init_devpod_workspaces();
|
|
11274
11721
|
init_host_routes();
|
|
@@ -11621,7 +12068,7 @@ async function buildDoctorReport(options = {}) {
|
|
|
11621
12068
|
const config = runtimeConfig.config;
|
|
11622
12069
|
loadedConfig = config;
|
|
11623
12070
|
loadedWorkspace = runtimeConfig.workspace;
|
|
11624
|
-
const cliVersion = true ? "0.0.
|
|
12071
|
+
const cliVersion = true ? "0.0.62" : "0.0.0-dev";
|
|
11625
12072
|
const configVersion = config.devrouter?.version;
|
|
11626
12073
|
if (configVersion && cliVersion !== "0.0.0-dev" && compareSemver(configVersion, cliVersion) > 0) {
|
|
11627
12074
|
addCheck(checks, {
|
|
@@ -11651,7 +12098,7 @@ async function buildDoctorReport(options = {}) {
|
|
|
11651
12098
|
(app) => app.docker.composeFiles.map((filePath) => ({
|
|
11652
12099
|
app: app.name,
|
|
11653
12100
|
filePath,
|
|
11654
|
-
absolutePath:
|
|
12101
|
+
absolutePath: import_node_path36.default.resolve(repo.path, filePath)
|
|
11655
12102
|
}))
|
|
11656
12103
|
).filter((entry) => !import_node_fs29.default.existsSync(entry.absolutePath));
|
|
11657
12104
|
addCheck(checks, {
|
|
@@ -11696,7 +12143,7 @@ async function buildDoctorReport(options = {}) {
|
|
|
11696
12143
|
const missingHostCwds = config.apps.filter((app) => app.runtime === "host").map((app) => ({
|
|
11697
12144
|
app: app.name,
|
|
11698
12145
|
cwd: app.hostRun.cwd,
|
|
11699
|
-
absolutePath:
|
|
12146
|
+
absolutePath: import_node_path36.default.resolve(repo.path, app.hostRun.cwd)
|
|
11700
12147
|
})).filter((entry) => !import_node_fs29.default.existsSync(entry.absolutePath));
|
|
11701
12148
|
addCheck(checks, {
|
|
11702
12149
|
id: "repo.host-cwd",
|
|
@@ -11841,12 +12288,12 @@ async function buildDoctorReport(options = {}) {
|
|
|
11841
12288
|
nextSteps
|
|
11842
12289
|
};
|
|
11843
12290
|
}
|
|
11844
|
-
var import_node_fs29,
|
|
12291
|
+
var import_node_fs29, import_node_path36, import_yaml5, POSTGRES_DEFAULTS;
|
|
11845
12292
|
var init_doctor = __esm({
|
|
11846
12293
|
"src/core/doctor.ts"() {
|
|
11847
12294
|
"use strict";
|
|
11848
12295
|
import_node_fs29 = __toESM(require("fs"));
|
|
11849
|
-
|
|
12296
|
+
import_node_path36 = __toESM(require("path"));
|
|
11850
12297
|
import_yaml5 = __toESM(require("yaml"));
|
|
11851
12298
|
init_docker();
|
|
11852
12299
|
init_host_routes();
|
|
@@ -12254,8 +12701,98 @@ var init_up = __esm({
|
|
|
12254
12701
|
}
|
|
12255
12702
|
});
|
|
12256
12703
|
|
|
12704
|
+
// src/core/devsy-exec-proof.ts
|
|
12705
|
+
function proveLocalDockerSelection(workspace) {
|
|
12706
|
+
const name = workspace.providerName;
|
|
12707
|
+
const context = workspace.context;
|
|
12708
|
+
if (!name || !context || !/^[a-z0-9][a-z0-9-]{0,31}$/.test(name) || !/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(context))
|
|
12709
|
+
throw new Error("Retained exec provider configuration identity is unavailable.");
|
|
12710
|
+
const file = import_node_path37.default.join(
|
|
12711
|
+
process.env.DEVSY_HOME || import_node_path37.default.join(import_node_os4.default.homedir(), ".devsy"),
|
|
12712
|
+
"contexts",
|
|
12713
|
+
context,
|
|
12714
|
+
"providers",
|
|
12715
|
+
name,
|
|
12716
|
+
"provider.json"
|
|
12717
|
+
);
|
|
12718
|
+
try {
|
|
12719
|
+
if (import_node_fs30.default.statSync(file).size > 1024 * 1024) throw new Error();
|
|
12720
|
+
const config = JSON.parse(import_node_fs30.default.readFileSync(file, "utf8"));
|
|
12721
|
+
const driver = config.agent?.driver ?? "docker";
|
|
12722
|
+
const configuredPath = config.agent?.docker?.path ?? "";
|
|
12723
|
+
if (typeof configuredPath !== "string") throw new Error();
|
|
12724
|
+
const command = configuredPath === "${DOCKER_PATH}" || configuredPath === "$DOCKER_PATH" ? workspace.dockerPathOption || process.env.DOCKER_PATH || "docker" : configuredPath || "docker";
|
|
12725
|
+
if (config.name !== name || driver !== "docker" || command !== "docker") throw new Error();
|
|
12726
|
+
} catch {
|
|
12727
|
+
throw new Error("Retained exec requires a provider configured for the local Docker command.");
|
|
12728
|
+
}
|
|
12729
|
+
return name;
|
|
12730
|
+
}
|
|
12731
|
+
function captureDevsyExecProof(repoPath) {
|
|
12732
|
+
repoPath = comparableWorkspacePath(repoPath);
|
|
12733
|
+
const registry = listDevsyWorkspaces();
|
|
12734
|
+
const workspace = selectDevsyWorkspace(registry, repoPath);
|
|
12735
|
+
if (!workspace?.uid || !workspace.context || workspace.source.container || inspectDevsyWorkspaceOwnership(registry, workspace.id, repoPath).status !== "owned" || inspectDevsyRuntimeStatus(workspace.id) !== "running")
|
|
12736
|
+
throw new Error("Retained exec requires one exact running Devsy registration.");
|
|
12737
|
+
const providerName = proveLocalDockerSelection(workspace);
|
|
12738
|
+
const endpoint = resolveManagedStopEndpoint();
|
|
12739
|
+
if (!supportsManagedStopBaseline(endpoint))
|
|
12740
|
+
throw new Error("Retained exec requires an exact local Docker endpoint.");
|
|
12741
|
+
const daemon = inspectManagedStopDaemon(endpoint);
|
|
12742
|
+
const candidates = workspaceAppContainers(inspectWorkspaceContainers(), repoPath).filter(
|
|
12743
|
+
(container) => container.state.Running
|
|
12744
|
+
);
|
|
12745
|
+
if (candidates.length !== 1)
|
|
12746
|
+
throw new Error("Retained exec requires one exact running primary container.");
|
|
12747
|
+
const candidate = candidates[0];
|
|
12748
|
+
const project = candidate.labels["com.docker.compose.project"];
|
|
12749
|
+
if (!project) throw new Error("Retained exec primary has no Compose identity.");
|
|
12750
|
+
const pinned = workspaceAppContainers(
|
|
12751
|
+
inspectManagedStopContainers(project, endpoint),
|
|
12752
|
+
repoPath
|
|
12753
|
+
).filter((container) => container.state.Running);
|
|
12754
|
+
if (pinned.length !== 1 || pinned[0].id !== candidate.id)
|
|
12755
|
+
throw new Error("Retained exec primary changed during inspection.");
|
|
12756
|
+
const mounts = pinned[0].mounts.filter(
|
|
12757
|
+
(mount) => mount.Type === "bind" && sameWorkspacePath(mount.Source, repoPath)
|
|
12758
|
+
);
|
|
12759
|
+
if (mounts.length !== 1 || !mounts[0].Destination.startsWith("/"))
|
|
12760
|
+
throw new Error("Retained exec requires one exact source mount.");
|
|
12761
|
+
const uidBytes = Buffer.byteLength(workspace.uid);
|
|
12762
|
+
const runner = uidBytes === 16 || uidBytes === 40 ? workspace.uid : workspace.id;
|
|
12763
|
+
const runnerContainers = inspectProviderRunnerContainers(endpoint, runner);
|
|
12764
|
+
if (runnerContainers.length !== 1 || runnerContainers[0] !== candidate.id)
|
|
12765
|
+
throw new Error("Retained exec runner does not select the exact primary container.");
|
|
12766
|
+
if (inspectManagedStopRunnerId(endpoint, candidate.id) !== runner || inspectManagedStopDaemon(endpoint) !== daemon)
|
|
12767
|
+
throw new Error("Retained exec provider or Docker identity changed.");
|
|
12768
|
+
return {
|
|
12769
|
+
repoPath,
|
|
12770
|
+
id: workspace.id,
|
|
12771
|
+
uid: workspace.uid,
|
|
12772
|
+
context: workspace.context,
|
|
12773
|
+
providerName,
|
|
12774
|
+
endpoint,
|
|
12775
|
+
daemon,
|
|
12776
|
+
containerId: candidate.id,
|
|
12777
|
+
workspacePath: mounts[0].Destination
|
|
12778
|
+
};
|
|
12779
|
+
}
|
|
12780
|
+
var import_node_fs30, import_node_os4, import_node_path37, import_node_util3;
|
|
12781
|
+
var init_devsy_exec_proof = __esm({
|
|
12782
|
+
"src/core/devsy-exec-proof.ts"() {
|
|
12783
|
+
"use strict";
|
|
12784
|
+
import_node_fs30 = __toESM(require("fs"));
|
|
12785
|
+
import_node_os4 = __toESM(require("os"));
|
|
12786
|
+
import_node_path37 = __toESM(require("path"));
|
|
12787
|
+
import_node_util3 = require("util");
|
|
12788
|
+
init_devpod_environment();
|
|
12789
|
+
init_devsy_workspaces();
|
|
12790
|
+
init_workspace();
|
|
12791
|
+
}
|
|
12792
|
+
});
|
|
12793
|
+
|
|
12257
12794
|
// src/core/reliability-model.ts
|
|
12258
|
-
function
|
|
12795
|
+
function record3(value) {
|
|
12259
12796
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12260
12797
|
}
|
|
12261
12798
|
function oneOf2(value, values) {
|
|
@@ -12265,16 +12802,16 @@ function uniqueIds2(values) {
|
|
|
12265
12802
|
return new Set(values).size === values.length && values.every(isReliabilityId);
|
|
12266
12803
|
}
|
|
12267
12804
|
function isFence(value) {
|
|
12268
|
-
return
|
|
12805
|
+
return record3(value) && isReliabilityId(value.environmentId) && isReliabilityCounter(value.intentRevision) && isReliabilityCounter(value.runtimeGeneration) && isReliabilityCounter(value.controllerEpoch);
|
|
12269
12806
|
}
|
|
12270
12807
|
function isConsumer(value) {
|
|
12271
|
-
return
|
|
12808
|
+
return record3(value) && isReliabilityId(value.id) && typeof value.pinned === "boolean" && Array.isArray(value.requiredCapabilities) && value.requiredCapabilities.length <= 128 && uniqueIds2(value.requiredCapabilities);
|
|
12272
12809
|
}
|
|
12273
12810
|
function isObservation(value) {
|
|
12274
|
-
return
|
|
12811
|
+
return record3(value) && isReliabilityId(value.capability) && oneOf2(value.infrastructure, INFRASTRUCTURE_VALUES) && oneOf2(value.application, APPLICATION_VALUES) && isReliabilityCounter(value.observedAtMs) && isReliabilityCounter(value.validForMs);
|
|
12275
12812
|
}
|
|
12276
12813
|
function assertReliabilityEvent(value) {
|
|
12277
|
-
if (!
|
|
12814
|
+
if (!record3(value) || !isFence(value) || typeof value.type !== "string") {
|
|
12278
12815
|
throw new Error("Invalid reliability event.");
|
|
12279
12816
|
}
|
|
12280
12817
|
let valid = false;
|
|
@@ -12283,7 +12820,7 @@ function assertReliabilityEvent(value) {
|
|
|
12283
12820
|
valid = oneOf2(value.mode, ["start", "attach"]) && isReliabilityId(value.key) && isReliabilityId(value.operationId) && isReliabilityProfile(value.profile) && isConsumer(value.consumer);
|
|
12284
12821
|
break;
|
|
12285
12822
|
case "operation-request":
|
|
12286
|
-
valid = oneOf2(value.kind, ["ensure", "exec"]) && isReliabilityId(value.key) && isReliabilityId(value.operationId) && isReliabilityProfile(value.profile) && isConsumer(value.consumer) && typeof value.runtimeRunning === "boolean";
|
|
12823
|
+
valid = oneOf2(value.kind, ["ensure", "exec"]) && isReliabilityId(value.key) && isReliabilityId(value.operationId) && isReliabilityProfile(value.profile) && isConsumer(value.consumer) && typeof value.runtimeRunning === "boolean" && (value.recoverInterruptedEnsure === void 0 || typeof value.recoverInterruptedEnsure === "boolean");
|
|
12287
12824
|
break;
|
|
12288
12825
|
case "drained":
|
|
12289
12826
|
valid = isReliabilityId(value.operationId);
|
|
@@ -12466,6 +13003,10 @@ function handleRequest(state, event) {
|
|
|
12466
13003
|
});
|
|
12467
13004
|
return transition(state, "accepted");
|
|
12468
13005
|
}
|
|
13006
|
+
function canExecAfterInterruptedEnsure(state) {
|
|
13007
|
+
const latestEnsure = [...state.operationHistory].reverse().find((operation) => operation.kind === "ensure");
|
|
13008
|
+
return state.executionPolicy === "manual" && state.desired === "running" && latestEnsure?.status === "INTERRUPTED" && latestEnsure.drained && state.operation?.drained === true && (state.operation.kind === "ensure" && state.phase === "recovering" || state.operation.kind === "exec" && ["COMPLETED", "NOT_LAUNCHED", "NOT_STARTED"].includes(state.operation.status));
|
|
13009
|
+
}
|
|
12469
13010
|
function handleOperationRequest(state, event) {
|
|
12470
13011
|
if (state.executionPolicy !== "manual") return unchanged(state, "blocked");
|
|
12471
13012
|
const previous = state.operationHistory.find((entry) => entry.key === event.key);
|
|
@@ -12476,20 +13017,33 @@ function handleOperationRequest(state, event) {
|
|
|
12476
13017
|
);
|
|
12477
13018
|
if (state.operationHistory.some((entry) => entry.id === event.operationId))
|
|
12478
13019
|
return unchanged(state, "conflict");
|
|
12479
|
-
if (
|
|
13020
|
+
if (event.kind === "exec" && canExecAfterInterruptedEnsure(state) && event.recoverInterruptedEnsure !== true)
|
|
13021
|
+
return unchanged(state, "blocked");
|
|
12480
13022
|
const fullyStopped = state.stopProof.workloadsStopped && state.stopProof.routesRemoved;
|
|
12481
13023
|
const reconcileEnsure = event.kind === "ensure" && state.operation?.kind === "ensure" && ["NOT_STARTED", "INTERRUPTED"].includes(state.operation.status);
|
|
12482
|
-
if (state.operation && (!state.operation.drained || !["COMPLETED", "NOT_LAUNCHED", "NOT_STARTED"].includes(state.operation.status) && !fullyStopped && !reconcileEnsure))
|
|
13024
|
+
if (state.operation && (!state.operation.drained || !["COMPLETED", "NOT_LAUNCHED", "NOT_STARTED"].includes(state.operation.status) && !fullyStopped && !reconcileEnsure && !(event.kind === "exec" && event.recoverInterruptedEnsure === true && canExecAfterInterruptedEnsure(state))))
|
|
12483
13025
|
return unchanged(state, "blocked");
|
|
12484
13026
|
if (state.phase === "stopping" || state.desired === "parked-for-capacity")
|
|
12485
13027
|
return unchanged(state, "blocked");
|
|
12486
13028
|
if (event.kind === "exec" && (!event.runtimeRunning || state.desired !== "running" && state.intentRevision !== 0))
|
|
12487
13029
|
return unchanged(state, "blocked");
|
|
12488
|
-
|
|
13030
|
+
const rollover = state.operationHistory.length >= RELIABILITY_MAX_ITEMS;
|
|
13031
|
+
let retired = -1;
|
|
13032
|
+
if (rollover) {
|
|
13033
|
+
if (state.operation && (!state.operation.drained || !["COMPLETED", "NOT_LAUNCHED"].includes(state.operation.status)))
|
|
13034
|
+
return unchanged(state, "blocked");
|
|
13035
|
+
const latestEnsureId = [...state.operationHistory].reverse().find((entry) => entry.kind === "ensure")?.id;
|
|
13036
|
+
retired = state.operationHistory.findIndex(
|
|
13037
|
+
(entry) => entry.id !== state.operation?.id && entry.id !== latestEnsureId && entry.drained && ["COMPLETED", "NOT_LAUNCHED"].includes(entry.status)
|
|
13038
|
+
);
|
|
13039
|
+
if (retired === -1) return unchanged(state, "blocked");
|
|
13040
|
+
}
|
|
13041
|
+
if (state.desired !== "running" || rollover) {
|
|
12489
13042
|
const revision = advance(state.intentRevision);
|
|
12490
13043
|
if (revision === null) return unchanged(state, "blocked");
|
|
12491
13044
|
state.intentRevision = revision;
|
|
12492
13045
|
}
|
|
13046
|
+
if (rollover) state.operationHistory.splice(retired, 1);
|
|
12493
13047
|
state.desired = "running";
|
|
12494
13048
|
state.phase = "queued";
|
|
12495
13049
|
state.profile = event.profile;
|
|
@@ -12858,8 +13412,8 @@ var init_reliability_model = __esm({
|
|
|
12858
13412
|
});
|
|
12859
13413
|
|
|
12860
13414
|
// src/core/reliability-worker.ts
|
|
12861
|
-
function sameFence2(
|
|
12862
|
-
return Object.entries(reliabilityFence(
|
|
13415
|
+
function sameFence2(record4, fence) {
|
|
13416
|
+
return Object.entries(reliabilityFence(record4.state)).every(
|
|
12863
13417
|
([key, value]) => value === fence[key]
|
|
12864
13418
|
);
|
|
12865
13419
|
}
|
|
@@ -12882,8 +13436,8 @@ function signalOwnedGroup(child, signal) {
|
|
|
12882
13436
|
async function runLifecycleWorker(request) {
|
|
12883
13437
|
if (process.platform === "win32")
|
|
12884
13438
|
throw new Error("Lifecycle workers require POSIX process-group ownership.");
|
|
12885
|
-
const workerPath =
|
|
12886
|
-
if (!
|
|
13439
|
+
const workerPath = import_node_path38.default.join(__dirname, "devrouter-lifecycle-worker.js");
|
|
13440
|
+
if (!import_node_fs31.default.existsSync(workerPath))
|
|
12887
13441
|
throw new Error("The packaged lifecycle worker is missing; rebuild the CLI.");
|
|
12888
13442
|
const child = (0, import_node_child_process21.fork)(workerPath, [], {
|
|
12889
13443
|
detached: true,
|
|
@@ -12930,28 +13484,28 @@ async function runLifecycleWorker(request) {
|
|
|
12930
13484
|
if (request.kind !== "stop") {
|
|
12931
13485
|
const birth = processBirthIdentity(child.pid);
|
|
12932
13486
|
if (!birth) throw new Error("Could not prove lifecycle worker incarnation.");
|
|
12933
|
-
updateReliabilityOperation(request.identity, (
|
|
12934
|
-
if (!sameFence2(
|
|
13487
|
+
updateReliabilityOperation(request.identity, (record4) => {
|
|
13488
|
+
if (!sameFence2(record4, request.fence) || record4.worker || record4.state.operation?.id !== request.operationId) {
|
|
12935
13489
|
throw new Error("Lifecycle intent changed before dispatch.");
|
|
12936
13490
|
}
|
|
12937
|
-
|
|
13491
|
+
record4.worker = {
|
|
12938
13492
|
id: request.workerId,
|
|
12939
13493
|
operationId: request.operationId,
|
|
12940
13494
|
pid: child.pid,
|
|
12941
13495
|
birth
|
|
12942
13496
|
};
|
|
12943
13497
|
const transition2 = stepReliability(
|
|
12944
|
-
|
|
13498
|
+
record4.state,
|
|
12945
13499
|
{ ...request.fence, type: "dispatch" },
|
|
12946
13500
|
Date.now()
|
|
12947
13501
|
);
|
|
12948
13502
|
if (transition2.outcome !== "accepted")
|
|
12949
13503
|
throw new Error("Lifecycle dispatch is blocked.");
|
|
12950
|
-
|
|
13504
|
+
record4.state = transition2.state;
|
|
12951
13505
|
});
|
|
12952
|
-
updateReliabilityOperation(request.identity, (
|
|
13506
|
+
updateReliabilityOperation(request.identity, (record4) => {
|
|
12953
13507
|
const transition2 = stepReliability(
|
|
12954
|
-
|
|
13508
|
+
record4.state,
|
|
12955
13509
|
{
|
|
12956
13510
|
...request.fence,
|
|
12957
13511
|
type: "dispatch-persisted",
|
|
@@ -12959,7 +13513,7 @@ async function runLifecycleWorker(request) {
|
|
|
12959
13513
|
},
|
|
12960
13514
|
Date.now()
|
|
12961
13515
|
);
|
|
12962
|
-
|
|
13516
|
+
record4.state = transition2.state;
|
|
12963
13517
|
if (!transition2.effects.some((effect2) => effect2.kind === "launch")) {
|
|
12964
13518
|
throw new Error("Lifecycle dispatch persistence was not acknowledged.");
|
|
12965
13519
|
}
|
|
@@ -12973,8 +13527,8 @@ async function runLifecycleWorker(request) {
|
|
|
12973
13527
|
});
|
|
12974
13528
|
monitor = setInterval(() => {
|
|
12975
13529
|
try {
|
|
12976
|
-
const
|
|
12977
|
-
if (!
|
|
13530
|
+
const record4 = readReliabilityOperation(request.identity);
|
|
13531
|
+
if (!record4 || !sameFence2(record4, request.fence)) cancel();
|
|
12978
13532
|
} catch (error) {
|
|
12979
13533
|
failure = error instanceof Error ? error : new Error(String(error));
|
|
12980
13534
|
cancel();
|
|
@@ -12991,13 +13545,13 @@ async function runLifecycleWorker(request) {
|
|
|
12991
13545
|
child.once("close", () => {
|
|
12992
13546
|
try {
|
|
12993
13547
|
if (request.kind !== "stop") {
|
|
12994
|
-
updateReliabilityOperation(request.identity, (
|
|
12995
|
-
if (
|
|
13548
|
+
updateReliabilityOperation(request.identity, (record4) => {
|
|
13549
|
+
if (record4.worker?.id !== request.workerId || record4.worker.pid !== child.pid) return;
|
|
12996
13550
|
if (!result) {
|
|
12997
|
-
|
|
12998
|
-
|
|
13551
|
+
record4.state = stepReliability(
|
|
13552
|
+
record4.state,
|
|
12999
13553
|
{
|
|
13000
|
-
...reliabilityFence(
|
|
13554
|
+
...reliabilityFence(record4.state),
|
|
13001
13555
|
type: "interrupted",
|
|
13002
13556
|
operationId: request.operationId
|
|
13003
13557
|
},
|
|
@@ -13005,16 +13559,16 @@ async function runLifecycleWorker(request) {
|
|
|
13005
13559
|
).state;
|
|
13006
13560
|
}
|
|
13007
13561
|
if (child.pid && workerGroupAbsent(child.pid)) {
|
|
13008
|
-
|
|
13009
|
-
|
|
13562
|
+
record4.state = stepReliability(
|
|
13563
|
+
record4.state,
|
|
13010
13564
|
{
|
|
13011
|
-
...reliabilityFence(
|
|
13565
|
+
...reliabilityFence(record4.state),
|
|
13012
13566
|
type: "drained",
|
|
13013
13567
|
operationId: request.operationId
|
|
13014
13568
|
},
|
|
13015
13569
|
Date.now()
|
|
13016
13570
|
).state;
|
|
13017
|
-
|
|
13571
|
+
record4.worker = null;
|
|
13018
13572
|
}
|
|
13019
13573
|
});
|
|
13020
13574
|
}
|
|
@@ -13041,14 +13595,14 @@ async function runLifecycleWorker(request) {
|
|
|
13041
13595
|
function newLifecycleIds() {
|
|
13042
13596
|
return { requestId: (0, import_node_crypto16.randomUUID)(), operationId: (0, import_node_crypto16.randomUUID)(), workerId: (0, import_node_crypto16.randomUUID)() };
|
|
13043
13597
|
}
|
|
13044
|
-
var import_node_child_process21, import_node_crypto16,
|
|
13598
|
+
var import_node_child_process21, import_node_crypto16, import_node_fs31, import_node_path38;
|
|
13045
13599
|
var init_reliability_worker = __esm({
|
|
13046
13600
|
"src/core/reliability-worker.ts"() {
|
|
13047
13601
|
"use strict";
|
|
13048
13602
|
import_node_child_process21 = require("child_process");
|
|
13049
13603
|
import_node_crypto16 = require("crypto");
|
|
13050
|
-
|
|
13051
|
-
|
|
13604
|
+
import_node_fs31 = __toESM(require("fs"));
|
|
13605
|
+
import_node_path38 = __toESM(require("path"));
|
|
13052
13606
|
init_file_lock();
|
|
13053
13607
|
init_reliability_contract();
|
|
13054
13608
|
init_reliability_model();
|
|
@@ -13057,12 +13611,12 @@ var init_reliability_worker = __esm({
|
|
|
13057
13611
|
});
|
|
13058
13612
|
|
|
13059
13613
|
// src/core/managed-host-preparation.ts
|
|
13060
|
-
var import_node_child_process22,
|
|
13614
|
+
var import_node_child_process22, import_node_fs32;
|
|
13061
13615
|
var init_managed_host_preparation = __esm({
|
|
13062
13616
|
"src/core/managed-host-preparation.ts"() {
|
|
13063
13617
|
"use strict";
|
|
13064
13618
|
import_node_child_process22 = require("child_process");
|
|
13065
|
-
|
|
13619
|
+
import_node_fs32 = __toESM(require("fs"));
|
|
13066
13620
|
init_repo_config();
|
|
13067
13621
|
}
|
|
13068
13622
|
});
|
|
@@ -13171,7 +13725,7 @@ async function ensureTraefikRouteExpectation(routes, expectation, options) {
|
|
|
13171
13725
|
throw new Error(
|
|
13172
13726
|
"Traefik routes did not reload; shared router restart is disabled for this operation."
|
|
13173
13727
|
);
|
|
13174
|
-
|
|
13728
|
+
import_node_fs33.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
13175
13729
|
return withFileLock(
|
|
13176
13730
|
ROUTER_RELOAD_LOCK_FILE,
|
|
13177
13731
|
{
|
|
@@ -13202,19 +13756,19 @@ async function ensureTraefikRouteExpectation(routes, expectation, options) {
|
|
|
13202
13756
|
async function ensureTraefikRoutesRemoved(routes, options = {}) {
|
|
13203
13757
|
return ensureTraefikRouteExpectation(routes, "removed", options);
|
|
13204
13758
|
}
|
|
13205
|
-
var import_node_child_process23,
|
|
13759
|
+
var import_node_child_process23, import_node_fs33, import_node_path39, 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
13760
|
var init_traefik_route_health = __esm({
|
|
13207
13761
|
"src/core/traefik-route-health.ts"() {
|
|
13208
13762
|
"use strict";
|
|
13209
13763
|
import_node_child_process23 = require("child_process");
|
|
13210
|
-
|
|
13211
|
-
|
|
13764
|
+
import_node_fs33 = __toESM(require("fs"));
|
|
13765
|
+
import_node_path39 = __toESM(require("path"));
|
|
13212
13766
|
init_file_lock();
|
|
13213
13767
|
init_host_routes();
|
|
13214
13768
|
init_reliability_context();
|
|
13215
13769
|
init_router();
|
|
13216
13770
|
ROUTER_API_BASE = "http://127.0.0.1:8080/api";
|
|
13217
|
-
ROUTER_RELOAD_LOCK_FILE =
|
|
13771
|
+
ROUTER_RELOAD_LOCK_FILE = import_node_path39.default.join(DEVROUTER_HOME, "router-reload.lock");
|
|
13218
13772
|
ROUTER_API_REQUEST_TIMEOUT_SECONDS = "2";
|
|
13219
13773
|
DEFAULT_INITIAL_TIMEOUT_MS = 3e3;
|
|
13220
13774
|
DEFAULT_RECOVERY_TIMEOUT_MS = 1e4;
|
|
@@ -13253,17 +13807,18 @@ function resolveLinkedTarget(repoPath) {
|
|
|
13253
13807
|
gitCommonDir: resolveGitCommonDir(repoPath)
|
|
13254
13808
|
};
|
|
13255
13809
|
}
|
|
13256
|
-
var import_node_child_process24,
|
|
13810
|
+
var import_node_child_process24, import_node_fs34, import_node_path40;
|
|
13257
13811
|
var init_workspace_ensure = __esm({
|
|
13258
13812
|
"src/core/workspace-ensure.ts"() {
|
|
13259
13813
|
"use strict";
|
|
13260
13814
|
import_node_child_process24 = require("child_process");
|
|
13261
|
-
|
|
13262
|
-
|
|
13815
|
+
import_node_fs34 = __toESM(require("fs"));
|
|
13816
|
+
import_node_path40 = __toESM(require("path"));
|
|
13263
13817
|
init_devcontainer_profile();
|
|
13264
13818
|
init_devpod_environment();
|
|
13265
13819
|
init_devpod_mutation();
|
|
13266
13820
|
init_devpod_workspaces();
|
|
13821
|
+
init_devsy_mutation();
|
|
13267
13822
|
init_file_lock();
|
|
13268
13823
|
init_host_routes();
|
|
13269
13824
|
init_http_route_probe();
|
|
@@ -13271,6 +13826,7 @@ var init_workspace_ensure = __esm({
|
|
|
13271
13826
|
init_managed_post_start();
|
|
13272
13827
|
init_managed_runtime_state();
|
|
13273
13828
|
init_managed_runtime_status();
|
|
13829
|
+
init_managed_stop_recovery();
|
|
13274
13830
|
init_reliability_lifecycle();
|
|
13275
13831
|
init_repo_config();
|
|
13276
13832
|
init_route_publication();
|
|
@@ -13292,50 +13848,51 @@ async function withLifecycleOperationLock(repoPath, operation) {
|
|
|
13292
13848
|
}
|
|
13293
13849
|
return withWorkspaceLifecycleLock(repoPath, operation);
|
|
13294
13850
|
}
|
|
13295
|
-
function stepRecord(
|
|
13296
|
-
const transition2 = stepReliability(
|
|
13851
|
+
function stepRecord(record4, event) {
|
|
13852
|
+
const transition2 = stepReliability(record4.state, event, Date.now());
|
|
13297
13853
|
if (!["accepted", "joined"].includes(transition2.outcome))
|
|
13298
13854
|
throw new Error(`Lifecycle transition is ${transition2.outcome}.`);
|
|
13299
|
-
|
|
13855
|
+
record4.state = transition2.state;
|
|
13300
13856
|
}
|
|
13301
|
-
function reconcileDrained(
|
|
13302
|
-
const worker =
|
|
13857
|
+
function reconcileDrained(record4) {
|
|
13858
|
+
const worker = record4.worker;
|
|
13303
13859
|
if (!worker) {
|
|
13304
|
-
if (
|
|
13305
|
-
stepRecord(
|
|
13306
|
-
...reliabilityFence(
|
|
13860
|
+
if (record4.state.operation?.status === "NOT_STARTED" && !record4.state.operation.drained) {
|
|
13861
|
+
stepRecord(record4, {
|
|
13862
|
+
...reliabilityFence(record4.state),
|
|
13307
13863
|
type: "drained",
|
|
13308
|
-
operationId:
|
|
13864
|
+
operationId: record4.state.operation.id
|
|
13309
13865
|
});
|
|
13310
13866
|
}
|
|
13311
13867
|
return;
|
|
13312
13868
|
}
|
|
13313
13869
|
if (processBirthIdentity(worker.pid) === worker.birth || !workerGroupAbsent(worker.pid)) return;
|
|
13314
|
-
if (
|
|
13870
|
+
if (record4.state.operation) {
|
|
13315
13871
|
const interrupted = stepReliability(
|
|
13316
|
-
|
|
13872
|
+
record4.state,
|
|
13317
13873
|
{
|
|
13318
|
-
...reliabilityFence(
|
|
13874
|
+
...reliabilityFence(record4.state),
|
|
13319
13875
|
type: "interrupted",
|
|
13320
13876
|
operationId: worker.operationId
|
|
13321
13877
|
},
|
|
13322
13878
|
Date.now()
|
|
13323
13879
|
);
|
|
13324
|
-
|
|
13325
|
-
stepRecord(
|
|
13326
|
-
...reliabilityFence(
|
|
13880
|
+
record4.state = interrupted.state;
|
|
13881
|
+
stepRecord(record4, {
|
|
13882
|
+
...reliabilityFence(record4.state),
|
|
13327
13883
|
type: "drained",
|
|
13328
13884
|
operationId: worker.operationId
|
|
13329
13885
|
});
|
|
13330
|
-
|
|
13886
|
+
record4.worker = null;
|
|
13331
13887
|
}
|
|
13332
13888
|
}
|
|
13333
13889
|
async function superviseLifecycle(kind, repoPath, options = {}, command) {
|
|
13334
13890
|
repoPath = comparableWorkspacePath(repoPath);
|
|
13335
13891
|
if (isLinkedWorktree(repoPath) && !readPersistedWorkspace(repoPath)) {
|
|
13892
|
+
if (kind === "stop") throw new Error("Stop requires the existing linked workspace identity.");
|
|
13336
13893
|
resolveLinkedTarget(repoPath);
|
|
13337
13894
|
}
|
|
13338
|
-
const
|
|
13895
|
+
const identity2 = {
|
|
13339
13896
|
repoPath,
|
|
13340
13897
|
workspace: resolveWorktreeWorkspace(repoPath) ?? null,
|
|
13341
13898
|
provider: resolveWorkspaceRuntimeOrDefault(repoPath)
|
|
@@ -13343,48 +13900,67 @@ async function superviseLifecycle(kind, repoPath, options = {}, command) {
|
|
|
13343
13900
|
const ids = newLifecycleIds();
|
|
13344
13901
|
let fence;
|
|
13345
13902
|
const runtimeRunning = kind === "exec" ? Boolean(resolveRunningWorkspaceContainer(repoPath)) : false;
|
|
13346
|
-
updateReliabilityOperation(
|
|
13347
|
-
reconcileDrained(
|
|
13903
|
+
const previous = kind === "exec" && identity2.provider === "devsy" ? updateReliabilityOperation(identity2, (record4) => {
|
|
13904
|
+
reconcileDrained(record4);
|
|
13905
|
+
return record4;
|
|
13906
|
+
}) : void 0;
|
|
13907
|
+
let retainedExecProof;
|
|
13908
|
+
if (previous && !previous.worker && canExecAfterInterruptedEnsure(previous.state)) {
|
|
13909
|
+
retainedExecProof = withMutationLock(
|
|
13910
|
+
"Prove retained exec",
|
|
13911
|
+
repoPath,
|
|
13912
|
+
() => captureDevsyExecProof(repoPath)
|
|
13913
|
+
);
|
|
13914
|
+
}
|
|
13915
|
+
updateReliabilityOperation(identity2, (record4) => {
|
|
13916
|
+
reconcileDrained(record4);
|
|
13917
|
+
if (retainedExecProof && record4.state.operation?.id !== previous?.state.operation?.id)
|
|
13918
|
+
throw new Error("Lifecycle operation changed during retained exec proof.");
|
|
13348
13919
|
if (kind === "stop") {
|
|
13349
|
-
stepRecord(
|
|
13920
|
+
stepRecord(record4, { ...reliabilityFence(record4.state), type: "stop" });
|
|
13350
13921
|
} else {
|
|
13351
|
-
if (
|
|
13922
|
+
if (record4.worker)
|
|
13352
13923
|
throw new Error(
|
|
13353
13924
|
"An earlier lifecycle worker may still be active; use explicit stop to reconcile it."
|
|
13354
13925
|
);
|
|
13355
|
-
|
|
13356
|
-
stepRecord(
|
|
13357
|
-
...reliabilityFence(
|
|
13926
|
+
record4.outcome = null;
|
|
13927
|
+
stepRecord(record4, {
|
|
13928
|
+
...reliabilityFence(record4.state),
|
|
13358
13929
|
type: "operation-request",
|
|
13359
13930
|
kind,
|
|
13360
13931
|
key: ids.requestId,
|
|
13361
13932
|
operationId: ids.operationId,
|
|
13362
|
-
profile: options.profile ??
|
|
13933
|
+
profile: options.profile ?? record4.state.profile ?? "full",
|
|
13363
13934
|
consumer: { id: "manual-cli", requiredCapabilities: [], pinned: false },
|
|
13364
|
-
runtimeRunning
|
|
13935
|
+
runtimeRunning,
|
|
13936
|
+
...retainedExecProof ? { recoverInterruptedEnsure: true } : {}
|
|
13365
13937
|
});
|
|
13366
13938
|
}
|
|
13367
|
-
fence = reliabilityFence(
|
|
13939
|
+
fence = reliabilityFence(record4.state);
|
|
13368
13940
|
});
|
|
13369
13941
|
return runLifecycleWorker({
|
|
13370
13942
|
kind,
|
|
13371
13943
|
repoPath,
|
|
13372
|
-
identity,
|
|
13944
|
+
identity: identity2,
|
|
13373
13945
|
...ids,
|
|
13374
13946
|
fence,
|
|
13375
13947
|
options,
|
|
13376
|
-
...command ? { command } : {}
|
|
13948
|
+
...command ? { command } : {},
|
|
13949
|
+
...retainedExecProof ? { retainedExecProof } : {}
|
|
13377
13950
|
});
|
|
13378
13951
|
}
|
|
13379
|
-
var
|
|
13952
|
+
var import_node_path41, activeWorker, lockHeld;
|
|
13380
13953
|
var init_reliability_lifecycle = __esm({
|
|
13381
13954
|
"src/core/reliability-lifecycle.ts"() {
|
|
13382
13955
|
"use strict";
|
|
13383
|
-
|
|
13956
|
+
import_node_path41 = __toESM(require("path"));
|
|
13384
13957
|
init_devpod_environment();
|
|
13958
|
+
init_devsy_exec_proof();
|
|
13959
|
+
init_devsy_mutation();
|
|
13385
13960
|
init_file_lock();
|
|
13386
13961
|
init_host_routes();
|
|
13387
13962
|
init_managed_runtime_state();
|
|
13963
|
+
init_managed_stop_recovery();
|
|
13388
13964
|
init_reliability_context();
|
|
13389
13965
|
init_reliability_contract();
|
|
13390
13966
|
init_reliability_model();
|
|
@@ -13788,17 +14364,17 @@ function asRecord3(value) {
|
|
|
13788
14364
|
return value;
|
|
13789
14365
|
}
|
|
13790
14366
|
function readJson(filePath) {
|
|
13791
|
-
if (!
|
|
14367
|
+
if (!import_node_fs35.default.existsSync(filePath)) {
|
|
13792
14368
|
return void 0;
|
|
13793
14369
|
}
|
|
13794
14370
|
try {
|
|
13795
|
-
return JSON.parse(
|
|
14371
|
+
return JSON.parse(import_node_fs35.default.readFileSync(filePath, "utf-8"));
|
|
13796
14372
|
} catch {
|
|
13797
14373
|
return void 0;
|
|
13798
14374
|
}
|
|
13799
14375
|
}
|
|
13800
14376
|
function relative(repoPath, filePath) {
|
|
13801
|
-
return
|
|
14377
|
+
return import_node_path42.default.relative(repoPath, filePath) || ".";
|
|
13802
14378
|
}
|
|
13803
14379
|
function redactEnvAssignments(value) {
|
|
13804
14380
|
return value.replace(
|
|
@@ -13839,7 +14415,7 @@ function inspectPackageManager(repoPath, pkg) {
|
|
|
13839
14415
|
["bun.lock", "bun"]
|
|
13840
14416
|
];
|
|
13841
14417
|
for (const [fileName, name] of lockfiles) {
|
|
13842
|
-
if (
|
|
14418
|
+
if (import_node_fs35.default.existsSync(import_node_path42.default.join(repoPath, fileName))) {
|
|
13843
14419
|
return { name, source: fileName };
|
|
13844
14420
|
}
|
|
13845
14421
|
}
|
|
@@ -13854,9 +14430,9 @@ function inspectNode(repoPath, pkg) {
|
|
|
13854
14430
|
if (typeof engines?.node === "string") {
|
|
13855
14431
|
return { version: engines.node, source: "package.json:engines.node" };
|
|
13856
14432
|
}
|
|
13857
|
-
const nvmrc =
|
|
13858
|
-
if (
|
|
13859
|
-
const version =
|
|
14433
|
+
const nvmrc = import_node_path42.default.join(repoPath, ".nvmrc");
|
|
14434
|
+
if (import_node_fs35.default.existsSync(nvmrc)) {
|
|
14435
|
+
const version = import_node_fs35.default.readFileSync(nvmrc, "utf-8").trim();
|
|
13860
14436
|
return { version, source: ".nvmrc" };
|
|
13861
14437
|
}
|
|
13862
14438
|
return void 0;
|
|
@@ -13917,7 +14493,7 @@ function configuredComposeFiles(repoPath) {
|
|
|
13917
14493
|
const files = config.apps.filter(
|
|
13918
14494
|
(app) => app.runtime === "docker"
|
|
13919
14495
|
).flatMap((app) => app.docker.composeFiles).filter(
|
|
13920
|
-
(fileName) => !
|
|
14496
|
+
(fileName) => !import_node_path42.default.isAbsolute(fileName) && !import_node_path42.default.normalize(fileName).startsWith("..")
|
|
13921
14497
|
);
|
|
13922
14498
|
return Array.from(new Set(files));
|
|
13923
14499
|
} catch {
|
|
@@ -13935,7 +14511,7 @@ function composeFiles(repoPath) {
|
|
|
13935
14511
|
...configuredComposeFiles(repoPath)
|
|
13936
14512
|
];
|
|
13937
14513
|
return Array.from(new Set(candidates)).filter(
|
|
13938
|
-
(fileName) =>
|
|
14514
|
+
(fileName) => import_node_fs35.default.existsSync(import_node_path42.default.join(repoPath, fileName))
|
|
13939
14515
|
);
|
|
13940
14516
|
}
|
|
13941
14517
|
function stringArray2(value) {
|
|
@@ -13948,8 +14524,8 @@ function envNames(value) {
|
|
|
13948
14524
|
if (Array.isArray(value)) {
|
|
13949
14525
|
return value.filter((entry) => typeof entry === "string").map((entry) => entry.split("=")[0]?.trim()).filter((entry) => Boolean(entry));
|
|
13950
14526
|
}
|
|
13951
|
-
const
|
|
13952
|
-
return
|
|
14527
|
+
const record4 = asRecord3(value);
|
|
14528
|
+
return record4 ? Object.keys(record4).sort() : [];
|
|
13953
14529
|
}
|
|
13954
14530
|
function inferServiceKind(name, image) {
|
|
13955
14531
|
const source = `${name} ${image ?? ""}`.toLowerCase();
|
|
@@ -13973,7 +14549,7 @@ function inspectServices(repoPath) {
|
|
|
13973
14549
|
const services = [];
|
|
13974
14550
|
for (const fileName of composeFiles(repoPath)) {
|
|
13975
14551
|
try {
|
|
13976
|
-
const parsed = import_yaml6.default.parse(
|
|
14552
|
+
const parsed = import_yaml6.default.parse(import_node_fs35.default.readFileSync(import_node_path42.default.join(repoPath, fileName), "utf-8"));
|
|
13977
14553
|
const serviceMap = asRecord3(asRecord3(parsed)?.services);
|
|
13978
14554
|
for (const [name, value] of Object.entries(serviceMap ?? {})) {
|
|
13979
14555
|
const service = asRecord3(value);
|
|
@@ -14008,8 +14584,8 @@ function inspectServices(repoPath) {
|
|
|
14008
14584
|
return services;
|
|
14009
14585
|
}
|
|
14010
14586
|
function inspectEnvFiles(repoPath) {
|
|
14011
|
-
const files =
|
|
14012
|
-
const content =
|
|
14587
|
+
const files = import_node_fs35.default.readdirSync(repoPath).filter((fileName) => /^\.env(\.|$)/.test(fileName)).sort().map((fileName) => {
|
|
14588
|
+
const content = import_node_fs35.default.readFileSync(import_node_path42.default.join(repoPath, fileName), "utf-8");
|
|
14013
14589
|
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
14590
|
return { path: fileName, names };
|
|
14015
14591
|
});
|
|
@@ -14023,18 +14599,18 @@ function inspectEnvFiles(repoPath) {
|
|
|
14023
14599
|
};
|
|
14024
14600
|
}
|
|
14025
14601
|
function inspectDevcontainer(repoPath) {
|
|
14026
|
-
const dir =
|
|
14027
|
-
if (!
|
|
14602
|
+
const dir = import_node_path42.default.join(repoPath, ".devcontainer");
|
|
14603
|
+
if (!import_node_fs35.default.existsSync(dir)) {
|
|
14028
14604
|
return { exists: false, files: [] };
|
|
14029
14605
|
}
|
|
14030
14606
|
return {
|
|
14031
14607
|
exists: true,
|
|
14032
|
-
files:
|
|
14608
|
+
files: import_node_fs35.default.readdirSync(dir).filter((fileName) => import_node_fs35.default.statSync(import_node_path42.default.join(dir, fileName)).isFile()).sort().map((fileName) => `.devcontainer/${fileName}`)
|
|
14033
14609
|
};
|
|
14034
14610
|
}
|
|
14035
14611
|
function inspectDevrouter(repoPath) {
|
|
14036
14612
|
const configPath = getRepoConfigPath(repoPath);
|
|
14037
|
-
if (!
|
|
14613
|
+
if (!import_node_fs35.default.existsSync(configPath)) {
|
|
14038
14614
|
return {
|
|
14039
14615
|
exists: false,
|
|
14040
14616
|
configPath,
|
|
@@ -14078,15 +14654,15 @@ function inspectAgentGuidance(repoPath) {
|
|
|
14078
14654
|
["AGENTS.md", "agents"],
|
|
14079
14655
|
["CLAUDE.md", "claude"]
|
|
14080
14656
|
]) {
|
|
14081
|
-
if (
|
|
14657
|
+
if (import_node_fs35.default.existsSync(import_node_path42.default.join(repoPath, fileName))) {
|
|
14082
14658
|
results.push({ path: fileName, kind });
|
|
14083
14659
|
}
|
|
14084
14660
|
}
|
|
14085
|
-
const skillsDir =
|
|
14086
|
-
if (
|
|
14087
|
-
for (const name of
|
|
14088
|
-
const skillPath =
|
|
14089
|
-
if (
|
|
14661
|
+
const skillsDir = import_node_path42.default.join(repoPath, ".agents", "skills");
|
|
14662
|
+
if (import_node_fs35.default.existsSync(skillsDir)) {
|
|
14663
|
+
for (const name of import_node_fs35.default.readdirSync(skillsDir).sort()) {
|
|
14664
|
+
const skillPath = import_node_path42.default.join(skillsDir, name, "SKILL.md");
|
|
14665
|
+
if (import_node_fs35.default.existsSync(skillPath)) {
|
|
14090
14666
|
results.push({ path: relative(repoPath, skillPath), kind: "skill" });
|
|
14091
14667
|
}
|
|
14092
14668
|
}
|
|
@@ -14129,7 +14705,7 @@ function buildIssues(report) {
|
|
|
14129
14705
|
}
|
|
14130
14706
|
function inspectRepo(options = {}) {
|
|
14131
14707
|
const repoPath = resolveRepoPath(options.repo);
|
|
14132
|
-
const pkg = readJson(
|
|
14708
|
+
const pkg = readJson(import_node_path42.default.join(repoPath, "package.json"));
|
|
14133
14709
|
const scripts = inspectScripts(pkg);
|
|
14134
14710
|
const reportWithoutIssues = {
|
|
14135
14711
|
repoPath,
|
|
@@ -14148,12 +14724,12 @@ function inspectRepo(options = {}) {
|
|
|
14148
14724
|
issues: buildIssues(reportWithoutIssues)
|
|
14149
14725
|
};
|
|
14150
14726
|
}
|
|
14151
|
-
var
|
|
14727
|
+
var import_node_fs35, import_node_path42, import_yaml6;
|
|
14152
14728
|
var init_repo_inspect = __esm({
|
|
14153
14729
|
"src/core/repo-inspect.ts"() {
|
|
14154
14730
|
"use strict";
|
|
14155
|
-
|
|
14156
|
-
|
|
14731
|
+
import_node_fs35 = __toESM(require("fs"));
|
|
14732
|
+
import_node_path42 = __toESM(require("path"));
|
|
14157
14733
|
import_yaml6 = __toESM(require("yaml"));
|
|
14158
14734
|
init_repo_config();
|
|
14159
14735
|
}
|
|
@@ -14258,7 +14834,7 @@ function requiredFileChecks(repoPath) {
|
|
|
14258
14834
|
".devcontainer/docker-compose.yml",
|
|
14259
14835
|
".devrouter.yml"
|
|
14260
14836
|
];
|
|
14261
|
-
const missing = required.filter((fileName) => !
|
|
14837
|
+
const missing = required.filter((fileName) => !import_node_fs36.default.existsSync(import_node_path43.default.join(repoPath, fileName)));
|
|
14262
14838
|
return {
|
|
14263
14839
|
id: "repo.devcontainer.verify-files",
|
|
14264
14840
|
level: missing.length === 0 ? "ok" : "error",
|
|
@@ -14483,12 +15059,12 @@ async function verifyDevcontainer(options = {}) {
|
|
|
14483
15059
|
nextSteps: collectNextSteps3(checks)
|
|
14484
15060
|
};
|
|
14485
15061
|
}
|
|
14486
|
-
var
|
|
15062
|
+
var import_node_fs36, import_node_path43;
|
|
14487
15063
|
var init_devcontainer_verify = __esm({
|
|
14488
15064
|
"src/core/devcontainer-verify.ts"() {
|
|
14489
15065
|
"use strict";
|
|
14490
|
-
|
|
14491
|
-
|
|
15066
|
+
import_node_fs36 = __toESM(require("fs"));
|
|
15067
|
+
import_node_path43 = __toESM(require("path"));
|
|
14492
15068
|
init_capabilities();
|
|
14493
15069
|
init_doctor();
|
|
14494
15070
|
init_host_routes();
|
|
@@ -14785,7 +15361,7 @@ function packageManagerIssues(repo) {
|
|
|
14785
15361
|
}
|
|
14786
15362
|
function plannedFiles(repoPath, version) {
|
|
14787
15363
|
const repo = inspectRepo({ repo: repoPath });
|
|
14788
|
-
const projectName = sanitizeProjectName(
|
|
15364
|
+
const projectName = sanitizeProjectName(import_node_path44.default.basename(repo.repoPath));
|
|
14789
15365
|
const nodeMajor = majorVersion(repo.node?.version, "24");
|
|
14790
15366
|
const pnpmVersion = repo.packageManager?.name === "pnpm" && repo.packageManager.version ? repo.packageManager.version : DEFAULT_PNPM_VERSION;
|
|
14791
15367
|
const port = inferPort2(repo);
|
|
@@ -14830,8 +15406,8 @@ function plannedFiles(repoPath, version) {
|
|
|
14830
15406
|
};
|
|
14831
15407
|
}
|
|
14832
15408
|
function classifyFile(repoPath, file) {
|
|
14833
|
-
const absolutePath =
|
|
14834
|
-
if (!
|
|
15409
|
+
const absolutePath = import_node_path44.default.join(repoPath, file.relativePath);
|
|
15410
|
+
if (!import_node_fs37.default.existsSync(absolutePath)) {
|
|
14835
15411
|
return {
|
|
14836
15412
|
path: file.relativePath,
|
|
14837
15413
|
action: "create",
|
|
@@ -14839,7 +15415,7 @@ function classifyFile(repoPath, file) {
|
|
|
14839
15415
|
bytes: Buffer.byteLength(file.content)
|
|
14840
15416
|
};
|
|
14841
15417
|
}
|
|
14842
|
-
const current =
|
|
15418
|
+
const current = import_node_fs37.default.readFileSync(absolutePath, "utf-8");
|
|
14843
15419
|
if (!current.includes(MANAGED_MARKER2)) {
|
|
14844
15420
|
return {
|
|
14845
15421
|
path: file.relativePath,
|
|
@@ -14896,11 +15472,11 @@ function buildPlan(repoPath, dryRun, version) {
|
|
|
14896
15472
|
};
|
|
14897
15473
|
}
|
|
14898
15474
|
function writeFile(repoPath, file) {
|
|
14899
|
-
const absolutePath =
|
|
14900
|
-
|
|
14901
|
-
|
|
15475
|
+
const absolutePath = import_node_path44.default.join(repoPath, file.relativePath);
|
|
15476
|
+
import_node_fs37.default.mkdirSync(import_node_path44.default.dirname(absolutePath), { recursive: true });
|
|
15477
|
+
import_node_fs37.default.writeFileSync(absolutePath, file.content, "utf-8");
|
|
14902
15478
|
if (file.executable) {
|
|
14903
|
-
|
|
15479
|
+
import_node_fs37.default.chmodSync(absolutePath, 493);
|
|
14904
15480
|
}
|
|
14905
15481
|
}
|
|
14906
15482
|
function writeDevcontainer(options = {}) {
|
|
@@ -14938,12 +15514,12 @@ function writeDevcontainer(options = {}) {
|
|
|
14938
15514
|
nextSteps: postWriteNextSteps(repoPath)
|
|
14939
15515
|
};
|
|
14940
15516
|
}
|
|
14941
|
-
var
|
|
15517
|
+
var import_node_fs37, import_node_path44, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
|
|
14942
15518
|
var init_devcontainer_write = __esm({
|
|
14943
15519
|
"src/core/devcontainer-write.ts"() {
|
|
14944
15520
|
"use strict";
|
|
14945
|
-
|
|
14946
|
-
|
|
15521
|
+
import_node_fs37 = __toESM(require("fs"));
|
|
15522
|
+
import_node_path44 = __toESM(require("path"));
|
|
14947
15523
|
init_repo_config();
|
|
14948
15524
|
init_repo_inspect();
|
|
14949
15525
|
MANAGED_MARKER2 = "devrouter:managed devcontainer";
|
|
@@ -15277,13 +15853,13 @@ function buildProfilePlanReport(options) {
|
|
|
15277
15853
|
return { ...report, contractPath, bindings };
|
|
15278
15854
|
}
|
|
15279
15855
|
function loadContract(repoPath, contractPath) {
|
|
15280
|
-
if (
|
|
15856
|
+
if (import_node_path45.default.isAbsolute(contractPath)) {
|
|
15281
15857
|
fail3("profile plan contract path must be relative to the repository.");
|
|
15282
15858
|
}
|
|
15283
15859
|
const absolutePath = assertPathWithinRepo(contractPath, repoPath, "Profile plan contract");
|
|
15284
15860
|
let metadata;
|
|
15285
15861
|
try {
|
|
15286
|
-
metadata =
|
|
15862
|
+
metadata = import_node_fs38.default.lstatSync(absolutePath);
|
|
15287
15863
|
} catch (error) {
|
|
15288
15864
|
fail3(`could not inspect profile plan contract '${contractPath}': ${error.message}`);
|
|
15289
15865
|
}
|
|
@@ -15293,16 +15869,16 @@ function loadContract(repoPath, contractPath) {
|
|
|
15293
15869
|
if (metadata.size > MAX_CONTRACT_BYTES) {
|
|
15294
15870
|
fail3(`profile plan contract exceeds the ${MAX_CONTRACT_BYTES}-byte limit.`);
|
|
15295
15871
|
}
|
|
15296
|
-
const realRepo =
|
|
15297
|
-
const realContract =
|
|
15872
|
+
const realRepo = import_node_fs38.default.realpathSync(repoPath);
|
|
15873
|
+
const realContract = import_node_fs38.default.realpathSync(absolutePath);
|
|
15298
15874
|
assertPathWithinRepo(realContract, realRepo, "Profile plan contract");
|
|
15299
|
-
const source =
|
|
15875
|
+
const source = import_node_fs38.default.readFileSync(absolutePath, "utf-8");
|
|
15300
15876
|
if (Buffer.byteLength(source, "utf-8") > MAX_CONTRACT_BYTES) {
|
|
15301
15877
|
fail3(`profile plan contract exceeds the ${MAX_CONTRACT_BYTES}-byte limit.`);
|
|
15302
15878
|
}
|
|
15303
15879
|
return {
|
|
15304
15880
|
contract: parseProfilePlanContract(source),
|
|
15305
|
-
contractPath:
|
|
15881
|
+
contractPath: import_node_path45.default.relative(repoPath, absolutePath).split(import_node_path45.default.sep).join("/")
|
|
15306
15882
|
};
|
|
15307
15883
|
}
|
|
15308
15884
|
function resolveProfilePlan(options) {
|
|
@@ -15316,12 +15892,12 @@ function resolveProfilePlan(options) {
|
|
|
15316
15892
|
contractPath: loaded.contractPath
|
|
15317
15893
|
});
|
|
15318
15894
|
}
|
|
15319
|
-
var
|
|
15895
|
+
var import_node_fs38, import_node_path45, import_yaml7, CONTRACT_VERSION, MAX_CONTRACT_BYTES, MAX_APP_MAPPINGS, MAX_BINDING_KEYS, MAX_BINDING_VALUES, MAX_LITERAL_LENGTH, BINDING_KEY;
|
|
15320
15896
|
var init_profile_plan = __esm({
|
|
15321
15897
|
"src/core/profile-plan.ts"() {
|
|
15322
15898
|
"use strict";
|
|
15323
|
-
|
|
15324
|
-
|
|
15899
|
+
import_node_fs38 = __toESM(require("fs"));
|
|
15900
|
+
import_node_path45 = __toESM(require("path"));
|
|
15325
15901
|
import_yaml7 = require("yaml");
|
|
15326
15902
|
init_paths();
|
|
15327
15903
|
init_profile_resolution();
|
|
@@ -15353,7 +15929,7 @@ async function runProfileResolveCommand(options) {
|
|
|
15353
15929
|
async function runProfilePlanCommand(options) {
|
|
15354
15930
|
const report = resolveProfilePlan(options);
|
|
15355
15931
|
if (options.output) {
|
|
15356
|
-
writeFileAtomically(
|
|
15932
|
+
writeFileAtomically(import_node_path46.default.resolve(options.output), `${JSON.stringify(report, null, 2)}
|
|
15357
15933
|
`);
|
|
15358
15934
|
}
|
|
15359
15935
|
if (options.json) {
|
|
@@ -15389,14 +15965,14 @@ function printProfilePlanSummary(report, output2) {
|
|
|
15389
15965
|
process.stdout.write(`Binding ${key}: ${renderValues(report.bindings[key] ?? [])}
|
|
15390
15966
|
`);
|
|
15391
15967
|
}
|
|
15392
|
-
if (output2) process.stdout.write(`Output: ${
|
|
15968
|
+
if (output2) process.stdout.write(`Output: ${import_node_path46.default.resolve(output2)}
|
|
15393
15969
|
`);
|
|
15394
15970
|
}
|
|
15395
|
-
var
|
|
15971
|
+
var import_node_path46;
|
|
15396
15972
|
var init_profile = __esm({
|
|
15397
15973
|
"src/commands/profile.ts"() {
|
|
15398
15974
|
"use strict";
|
|
15399
|
-
|
|
15975
|
+
import_node_path46 = __toESM(require("path"));
|
|
15400
15976
|
init_atomic_file();
|
|
15401
15977
|
init_output();
|
|
15402
15978
|
init_profile_plan();
|
|
@@ -15686,7 +16262,7 @@ function sanitizeRouterId(value) {
|
|
|
15686
16262
|
return value.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
15687
16263
|
}
|
|
15688
16264
|
function repoHash(repoPath) {
|
|
15689
|
-
return (0, import_node_crypto17.createHash)("sha1").update(
|
|
16265
|
+
return (0, import_node_crypto17.createHash)("sha1").update(import_node_path47.default.resolve(repoPath)).digest("hex").slice(0, 12);
|
|
15690
16266
|
}
|
|
15691
16267
|
function asDockerApp(app) {
|
|
15692
16268
|
return app.runtime === "docker";
|
|
@@ -15765,11 +16341,11 @@ function prepareDockerOverlay(repoPath, appName, apps, publishTcpPorts = false)
|
|
|
15765
16341
|
if (dockerApps.length === 0) {
|
|
15766
16342
|
throw new Error("No docker apps selected to prepare compose overlay.");
|
|
15767
16343
|
}
|
|
15768
|
-
const cachePath =
|
|
15769
|
-
|
|
15770
|
-
const overlayPath =
|
|
16344
|
+
const cachePath = import_node_path47.default.join(CACHE_DIR, repoHash(repoPath), sanitizeRouterId(appName));
|
|
16345
|
+
import_node_fs39.default.mkdirSync(cachePath, { recursive: true });
|
|
16346
|
+
const overlayPath = import_node_path47.default.join(cachePath, "compose.devrouter.yml");
|
|
15771
16347
|
const overlayDocument = buildOverlayDocument(dockerApps, publishTcpPorts);
|
|
15772
|
-
|
|
16348
|
+
import_node_fs39.default.writeFileSync(overlayPath, import_yaml8.default.stringify(overlayDocument, { lineWidth: 0 }), "utf-8");
|
|
15773
16349
|
return {
|
|
15774
16350
|
overlayPath,
|
|
15775
16351
|
composeFiles: ensureComposeFiles(dockerApps),
|
|
@@ -15889,14 +16465,14 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
|
|
|
15889
16465
|
const port = Number(match[1]);
|
|
15890
16466
|
return Number.isInteger(port) && port > 0 ? port : void 0;
|
|
15891
16467
|
}
|
|
15892
|
-
var import_node_child_process27, import_node_crypto17,
|
|
16468
|
+
var import_node_child_process27, import_node_crypto17, import_node_fs39, import_node_path47, import_yaml8;
|
|
15893
16469
|
var init_docker_run = __esm({
|
|
15894
16470
|
"src/core/docker-run.ts"() {
|
|
15895
16471
|
"use strict";
|
|
15896
16472
|
import_node_child_process27 = require("child_process");
|
|
15897
16473
|
import_node_crypto17 = require("crypto");
|
|
15898
|
-
|
|
15899
|
-
|
|
16474
|
+
import_node_fs39 = __toESM(require("fs"));
|
|
16475
|
+
import_node_path47 = __toESM(require("path"));
|
|
15900
16476
|
import_yaml8 = __toESM(require("yaml"));
|
|
15901
16477
|
init_docker_error_guidance();
|
|
15902
16478
|
init_paths();
|
|
@@ -16718,13 +17294,13 @@ function measureWorktreeConsumption(worktreePath, options) {
|
|
|
16718
17294
|
const startedAt = Date.now();
|
|
16719
17295
|
let rootStat;
|
|
16720
17296
|
try {
|
|
16721
|
-
rootStat =
|
|
17297
|
+
rootStat = import_node_fs40.default.lstatSync(worktreePath);
|
|
16722
17298
|
} catch (error) {
|
|
16723
17299
|
return { status: "unknown", reason: describeError(error, worktreePath) };
|
|
16724
17300
|
}
|
|
16725
17301
|
let rootEntries;
|
|
16726
17302
|
try {
|
|
16727
|
-
rootEntries =
|
|
17303
|
+
rootEntries = import_node_fs40.default.readdirSync(worktreePath, { withFileTypes: true });
|
|
16728
17304
|
} catch (error) {
|
|
16729
17305
|
return { status: "unknown", reason: describeError(error, worktreePath) };
|
|
16730
17306
|
}
|
|
@@ -16750,10 +17326,10 @@ function measureWorktreeConsumption(worktreePath, options) {
|
|
|
16750
17326
|
timedOut = true;
|
|
16751
17327
|
break;
|
|
16752
17328
|
}
|
|
16753
|
-
const entryPath =
|
|
17329
|
+
const entryPath = import_node_path48.default.join(dirPath, entry.name);
|
|
16754
17330
|
let entryStat;
|
|
16755
17331
|
try {
|
|
16756
|
-
entryStat =
|
|
17332
|
+
entryStat = import_node_fs40.default.lstatSync(entryPath);
|
|
16757
17333
|
} catch (error) {
|
|
16758
17334
|
if (error?.code === "ENOENT") continue;
|
|
16759
17335
|
unreadableReason = describeIncompleteWalk(error);
|
|
@@ -16763,7 +17339,7 @@ function measureWorktreeConsumption(worktreePath, options) {
|
|
|
16763
17339
|
if (!entryStat.isDirectory()) continue;
|
|
16764
17340
|
let childEntries;
|
|
16765
17341
|
try {
|
|
16766
|
-
childEntries =
|
|
17342
|
+
childEntries = import_node_fs40.default.readdirSync(entryPath, { withFileTypes: true });
|
|
16767
17343
|
} catch (error) {
|
|
16768
17344
|
unreadableReason = describeIncompleteWalk(error);
|
|
16769
17345
|
break;
|
|
@@ -16827,12 +17403,12 @@ function describeError(error, worktreePath) {
|
|
|
16827
17403
|
}
|
|
16828
17404
|
return `could not stat worktree path '${worktreePath}': ${error?.message ?? String(error)}`;
|
|
16829
17405
|
}
|
|
16830
|
-
var
|
|
17406
|
+
var import_node_fs40, import_node_path48, DEFAULT_DEADLINE_MS, BLOCK_SIZE_BYTES;
|
|
16831
17407
|
var init_workspace_consumption = __esm({
|
|
16832
17408
|
"src/core/workspace-consumption.ts"() {
|
|
16833
17409
|
"use strict";
|
|
16834
|
-
|
|
16835
|
-
|
|
17410
|
+
import_node_fs40 = __toESM(require("fs"));
|
|
17411
|
+
import_node_path48 = __toESM(require("path"));
|
|
16836
17412
|
init_devpod_environment();
|
|
16837
17413
|
DEFAULT_DEADLINE_MS = 1e4;
|
|
16838
17414
|
BLOCK_SIZE_BYTES = 512;
|
|
@@ -16933,7 +17509,7 @@ function evaluateWorkspaceActivity(evidence, cutoff) {
|
|
|
16933
17509
|
}
|
|
16934
17510
|
function readGitSnapshot(worktree, commandRunner) {
|
|
16935
17511
|
const comparablePath = comparableWorkspacePath(worktree.path);
|
|
16936
|
-
if (worktree.prunable || !
|
|
17512
|
+
if (worktree.prunable || !import_node_fs41.default.existsSync(comparablePath)) {
|
|
16937
17513
|
return { worktree, checkout: "missing", head: null, committerDate: null };
|
|
16938
17514
|
}
|
|
16939
17515
|
const head = gitOutput(comparablePath, ["rev-parse", "--verify", "HEAD"], commandRunner);
|
|
@@ -17082,8 +17658,8 @@ function inspectWorkspaceIntegration(repoPath, snapshot, branch, checkMerged, co
|
|
|
17082
17658
|
return { status: "unknown", reason: "Current HEAD or branch evidence is unavailable." };
|
|
17083
17659
|
}
|
|
17084
17660
|
const originUrl = gitOutput(repoPath, ["config", "--get", "remote.origin.url"], commandRunner);
|
|
17085
|
-
const
|
|
17086
|
-
if (!
|
|
17661
|
+
const identity2 = originUrl ? parseRemoteIdentity(originUrl) : void 0;
|
|
17662
|
+
if (!identity2) {
|
|
17087
17663
|
return { status: "unknown", reason: "The origin is missing or uses an unsupported forge." };
|
|
17088
17664
|
}
|
|
17089
17665
|
const localTargetBranch = parseTargetBranch(
|
|
@@ -17128,11 +17704,11 @@ function inspectWorkspaceIntegration(repoPath, snapshot, branch, checkMerged, co
|
|
|
17128
17704
|
};
|
|
17129
17705
|
}
|
|
17130
17706
|
const forgeChanges = [];
|
|
17131
|
-
const forgeCommand =
|
|
17707
|
+
const forgeCommand = identity2.provider === "github" ? [
|
|
17132
17708
|
"pr",
|
|
17133
17709
|
"list",
|
|
17134
17710
|
"--repo",
|
|
17135
|
-
|
|
17711
|
+
identity2.project,
|
|
17136
17712
|
"--state",
|
|
17137
17713
|
"all",
|
|
17138
17714
|
"--head",
|
|
@@ -17143,20 +17719,20 @@ function inspectWorkspaceIntegration(repoPath, snapshot, branch, checkMerged, co
|
|
|
17143
17719
|
"mr",
|
|
17144
17720
|
"list",
|
|
17145
17721
|
"--repo",
|
|
17146
|
-
|
|
17722
|
+
identity2.project,
|
|
17147
17723
|
"--all",
|
|
17148
17724
|
"--source-branch",
|
|
17149
17725
|
branch,
|
|
17150
17726
|
"--output",
|
|
17151
17727
|
"json"
|
|
17152
17728
|
];
|
|
17153
|
-
const forge = commandRunner(
|
|
17729
|
+
const forge = commandRunner(identity2.provider === "github" ? "gh" : "glab", forgeCommand);
|
|
17154
17730
|
if (forge.status !== 0 || forge.error) {
|
|
17155
17731
|
return { status: "unknown", reason: "The forge query was unavailable or unauthenticated." };
|
|
17156
17732
|
}
|
|
17157
17733
|
try {
|
|
17158
17734
|
const parsed = JSON.parse(forge.stdout);
|
|
17159
|
-
const changes =
|
|
17735
|
+
const changes = identity2.provider === "github" ? parseGitHubChanges(parsed, identity2.project, branch) : parseGitLabChanges(parsed, identity2.project, branch);
|
|
17160
17736
|
if (!changes) {
|
|
17161
17737
|
return { status: "unknown", reason: "The forge response was malformed." };
|
|
17162
17738
|
}
|
|
@@ -17222,31 +17798,31 @@ function inspectWorkspaceIntegration(repoPath, snapshot, branch, checkMerged, co
|
|
|
17222
17798
|
reason: "Fresh target and forge evidence did not prove integration."
|
|
17223
17799
|
};
|
|
17224
17800
|
}
|
|
17225
|
-
function routeStatus(
|
|
17801
|
+
function routeStatus(record4, routes) {
|
|
17226
17802
|
if (!routes) return "unknown";
|
|
17227
17803
|
const matchingPath = routes.filter(
|
|
17228
|
-
(route) => sameWorkspacePath(route.repoPath,
|
|
17804
|
+
(route) => sameWorkspacePath(route.repoPath, record4.worktreePath)
|
|
17229
17805
|
);
|
|
17230
17806
|
if (matchingPath.length === 0) return "absent";
|
|
17231
|
-
return matchingPath.every((route) => route.workspace ===
|
|
17807
|
+
return matchingPath.every((route) => route.workspace === record4.workspace) ? "owned" : "conflict";
|
|
17232
17808
|
}
|
|
17233
|
-
function buildActivityEvidence(
|
|
17809
|
+
function buildActivityEvidence(record4, providerStatus, provider, routeEntries, git) {
|
|
17234
17810
|
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
17811
|
const routeTimestamps = routeEntries?.map((route) => route.updatedAt) ?? [];
|
|
17236
17812
|
const malformedRoute = routeTimestamps.some((timestamp) => !validTimestamp(timestamp));
|
|
17237
17813
|
const newestRoute = routeTimestamps.filter(validTimestamp).sort((left, right) => (parseTimestamp(right) ?? 0) - (parseTimestamp(left) ?? 0))[0];
|
|
17238
17814
|
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(
|
|
17815
|
+
const ownerEvidence = validTimestamp(record4.updatedAt) ? { source: "ownership.updatedAt", status: "valid", timestamp: record4.updatedAt } : { source: "ownership.updatedAt", status: "malformed" };
|
|
17240
17816
|
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
17817
|
return [providerEvidence, routeEvidence, ownerEvidence, gitEvidence];
|
|
17242
17818
|
}
|
|
17243
|
-
function buildSuggestions(repoPath,
|
|
17819
|
+
function buildSuggestions(repoPath, record4, ownership, provider, runtime, checkout, route, activity, integration, worktree, checkMerged) {
|
|
17244
17820
|
const reasons = [];
|
|
17245
17821
|
const eligibleActions = [];
|
|
17246
17822
|
const suggestions = [];
|
|
17247
17823
|
const gcCommand = workspaceCommand(repoPath, ["gc"], [], ["--yes"]);
|
|
17248
|
-
const keepCommand = workspaceCommand(repoPath, ["down",
|
|
17249
|
-
const downCommand = workspaceCommand(repoPath, ["down",
|
|
17824
|
+
const keepCommand = workspaceCommand(repoPath, ["down", record4.workspace], ["--keep-worktree"]);
|
|
17825
|
+
const downCommand = workspaceCommand(repoPath, ["down", record4.workspace]);
|
|
17250
17826
|
const routeSafe = route === "owned" || route === "absent";
|
|
17251
17827
|
const checkoutSafe = checkout === "clean" && !worktree?.locked;
|
|
17252
17828
|
const providerSafe = provider === "owned";
|
|
@@ -17324,17 +17900,17 @@ function buildSuggestions(repoPath, record3, ownership, provider, runtime, check
|
|
|
17324
17900
|
}
|
|
17325
17901
|
return { eligibleActions, suggestions, reasons };
|
|
17326
17902
|
}
|
|
17327
|
-
function buildRow(repoPath,
|
|
17328
|
-
const ownershipEvidence = inspectOwnershipFn(
|
|
17903
|
+
function buildRow(repoPath, record4, worktrees, devpods, routes, snapshot, cutoff, checkMerged, commandRunner, inspectOwnershipFn, inspectIntegrationFn, inspectRuntimeFn) {
|
|
17904
|
+
const ownershipEvidence = inspectOwnershipFn(record4, worktrees, devpods);
|
|
17329
17905
|
const providerOwnership = devpods?.find(
|
|
17330
|
-
(devpod) => devpod.id ===
|
|
17906
|
+
(devpod) => devpod.id === record4.devpodId && sameWorkspacePath(devpod.source.localFolder, record4.worktreePath)
|
|
17331
17907
|
);
|
|
17332
17908
|
const matchingRoutes = routes?.filter(
|
|
17333
|
-
(route) => sameWorkspacePath(route.repoPath,
|
|
17909
|
+
(route) => sameWorkspacePath(route.repoPath, record4.worktreePath)
|
|
17334
17910
|
);
|
|
17335
17911
|
const activity = evaluateWorkspaceActivity(
|
|
17336
17912
|
buildActivityEvidence(
|
|
17337
|
-
|
|
17913
|
+
record4,
|
|
17338
17914
|
ownershipEvidence.devpodStatus,
|
|
17339
17915
|
providerOwnership,
|
|
17340
17916
|
matchingRoutes,
|
|
@@ -17342,23 +17918,23 @@ function buildRow(repoPath, record3, worktrees, devpods, routes, snapshot, cutof
|
|
|
17342
17918
|
),
|
|
17343
17919
|
cutoff
|
|
17344
17920
|
);
|
|
17345
|
-
const branch = snapshot.checkout === "detached" ? null : snapshot.worktree.branch ??
|
|
17921
|
+
const branch = snapshot.checkout === "detached" ? null : snapshot.worktree.branch ?? record4.branch;
|
|
17346
17922
|
const integration = inspectIntegrationFn?.(repoPath, snapshot, branch, checkMerged) ?? inspectWorkspaceIntegration(repoPath, snapshot, branch, checkMerged, commandRunner);
|
|
17347
17923
|
const runtime = ownershipEvidence.devpodStatus === "absent" ? "absent" : ownershipEvidence.devpodStatus === "owned" ? (() => {
|
|
17348
17924
|
try {
|
|
17349
|
-
return inspectRuntimeFn(
|
|
17925
|
+
return inspectRuntimeFn(record4.devpodId);
|
|
17350
17926
|
} catch {
|
|
17351
17927
|
return "unknown";
|
|
17352
17928
|
}
|
|
17353
17929
|
})() : "unknown";
|
|
17354
17930
|
const suggestions = buildSuggestions(
|
|
17355
17931
|
repoPath,
|
|
17356
|
-
|
|
17932
|
+
record4,
|
|
17357
17933
|
ownershipEvidence.ownerStatus,
|
|
17358
17934
|
ownershipEvidence.devpodStatus,
|
|
17359
17935
|
runtime,
|
|
17360
17936
|
snapshot.checkout,
|
|
17361
|
-
routeStatus(
|
|
17937
|
+
routeStatus(record4, routes),
|
|
17362
17938
|
activity.status,
|
|
17363
17939
|
integration,
|
|
17364
17940
|
snapshot.worktree,
|
|
@@ -17369,7 +17945,7 @@ function buildRow(repoPath, record3, worktrees, devpods, routes, snapshot, cutof
|
|
|
17369
17945
|
`provider=${ownershipEvidence.devpodStatus}`,
|
|
17370
17946
|
`runtime=${runtime}`,
|
|
17371
17947
|
`checkout=${snapshot.checkout}`,
|
|
17372
|
-
`route=${routeStatus(
|
|
17948
|
+
`route=${routeStatus(record4, routes)}`,
|
|
17373
17949
|
`activity=${activity.status}`,
|
|
17374
17950
|
`integration=${integration.status}`,
|
|
17375
17951
|
...integration.reason ? [integration.reason] : [],
|
|
@@ -17377,16 +17953,16 @@ function buildRow(repoPath, record3, worktrees, devpods, routes, snapshot, cutof
|
|
|
17377
17953
|
];
|
|
17378
17954
|
return {
|
|
17379
17955
|
schemaVersion: 2,
|
|
17380
|
-
workspace:
|
|
17956
|
+
workspace: record4.workspace,
|
|
17381
17957
|
branch,
|
|
17382
17958
|
repo: repoPath,
|
|
17383
|
-
worktreePath:
|
|
17384
|
-
devpodId:
|
|
17959
|
+
worktreePath: record4.worktreePath,
|
|
17960
|
+
devpodId: record4.devpodId,
|
|
17385
17961
|
ownership: ownershipEvidence.ownerStatus,
|
|
17386
17962
|
provider: ownershipEvidence.devpodStatus,
|
|
17387
17963
|
runtime,
|
|
17388
17964
|
checkout: snapshot.checkout,
|
|
17389
|
-
route: routeStatus(
|
|
17965
|
+
route: routeStatus(record4, routes),
|
|
17390
17966
|
activity: activity.status,
|
|
17391
17967
|
cutoff,
|
|
17392
17968
|
latestTimestamp: activity.latestTimestamp,
|
|
@@ -17453,30 +18029,30 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
|
|
|
17453
18029
|
const measureContainersFn = dependencies.measureContainers ?? measureContainerConsumption;
|
|
17454
18030
|
let containers2 = /* @__PURE__ */ new Map();
|
|
17455
18031
|
if (measureSize) {
|
|
17456
|
-
const worktreePaths = records.map((
|
|
18032
|
+
const worktreePaths = records.map((record4) => record4.worktreePath);
|
|
17457
18033
|
try {
|
|
17458
18034
|
containers2 = measureContainersFn(worktreePaths);
|
|
17459
18035
|
} catch (error) {
|
|
17460
18036
|
const reason = `container measurement failed: ${describeCause(error)}`;
|
|
17461
|
-
containers2 = new Map(worktreePaths.map((
|
|
18037
|
+
containers2 = new Map(worktreePaths.map((path49) => [path49, unknownContainers(reason)]));
|
|
17462
18038
|
}
|
|
17463
18039
|
}
|
|
17464
|
-
const rows = records.map((
|
|
18040
|
+
const rows = records.map((record4) => {
|
|
17465
18041
|
const worktree = worktrees.find(
|
|
17466
|
-
(candidate) => sameWorkspacePath(candidate.path,
|
|
18042
|
+
(candidate) => sameWorkspacePath(candidate.path, record4.worktreePath)
|
|
17467
18043
|
);
|
|
17468
18044
|
const snapshot = (dependencies.readGitSnapshot ?? ((candidate) => readGitSnapshot(candidate, commandRunner)))(
|
|
17469
18045
|
worktree ?? {
|
|
17470
|
-
path:
|
|
17471
|
-
branch:
|
|
18046
|
+
path: record4.worktreePath,
|
|
18047
|
+
branch: record4.branch ?? void 0,
|
|
17472
18048
|
locked: false,
|
|
17473
18049
|
prunable: true
|
|
17474
18050
|
}
|
|
17475
18051
|
);
|
|
17476
|
-
const devpods = dependencies.listDevpods ? dependencies.listDevpods() : listDevpodWorkspacesFromSnapshots(
|
|
18052
|
+
const devpods = dependencies.listDevpods ? dependencies.listDevpods() : listDevpodWorkspacesFromSnapshots(record4.worktreePath);
|
|
17477
18053
|
const row = buildRow(
|
|
17478
18054
|
repoPath,
|
|
17479
|
-
|
|
18055
|
+
record4,
|
|
17480
18056
|
worktrees,
|
|
17481
18057
|
devpods,
|
|
17482
18058
|
routes,
|
|
@@ -17489,11 +18065,11 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
|
|
|
17489
18065
|
return { ownerStatus: status.ownerStatus, devpodStatus: status.devpodStatus };
|
|
17490
18066
|
}),
|
|
17491
18067
|
dependencies.inspectIntegration,
|
|
17492
|
-
dependencies.inspectDevpodRuntime ?? ((devpodId) => inspectDevpodRuntimeStatus(devpodId,
|
|
18068
|
+
dependencies.inspectDevpodRuntime ?? ((devpodId) => inspectDevpodRuntimeStatus(devpodId, record4.worktreePath))
|
|
17493
18069
|
);
|
|
17494
18070
|
return measureSize ? {
|
|
17495
18071
|
...row,
|
|
17496
|
-
consumption: collectConsumption(
|
|
18072
|
+
consumption: collectConsumption(record4.worktreePath, measureWorktreeFn, containers2)
|
|
17497
18073
|
} : row;
|
|
17498
18074
|
}).sort((left, right) => left.workspace.localeCompare(right.workspace));
|
|
17499
18075
|
return {
|
|
@@ -17507,12 +18083,12 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
|
|
|
17507
18083
|
workspaces: rows
|
|
17508
18084
|
};
|
|
17509
18085
|
}
|
|
17510
|
-
var import_node_child_process29,
|
|
18086
|
+
var import_node_child_process29, import_node_fs41, DEFAULT_INACTIVE_FOR, READ_ONLY_GIT_ENV2, ACTIVITY_SOURCES, defaultCommandRunner;
|
|
17511
18087
|
var init_workspace_cleanup = __esm({
|
|
17512
18088
|
"src/core/workspace-cleanup.ts"() {
|
|
17513
18089
|
"use strict";
|
|
17514
18090
|
import_node_child_process29 = require("child_process");
|
|
17515
|
-
|
|
18091
|
+
import_node_fs41 = __toESM(require("fs"));
|
|
17516
18092
|
init_devpod_workspaces();
|
|
17517
18093
|
init_host_routes();
|
|
17518
18094
|
init_repo_config();
|
|
@@ -17559,7 +18135,7 @@ function warnMissingWorkspaceOwnership(repoPath) {
|
|
|
17559
18135
|
);
|
|
17560
18136
|
}
|
|
17561
18137
|
function defaultWorktreePath(mainRepo, ws) {
|
|
17562
|
-
return
|
|
18138
|
+
return import_node_path49.default.join(mainRepo, "trees", ws);
|
|
17563
18139
|
}
|
|
17564
18140
|
function assertDefaultWorktreeRootIgnored(mainRepo) {
|
|
17565
18141
|
const ignored = (0, import_node_child_process30.spawnSync)("git", ["-C", mainRepo, "check-ignore", "-q", "--no-index", "trees/"], {
|
|
@@ -17567,12 +18143,12 @@ function assertDefaultWorktreeRootIgnored(mainRepo) {
|
|
|
17567
18143
|
});
|
|
17568
18144
|
if (ignored.status !== 0) {
|
|
17569
18145
|
throw new Error(
|
|
17570
|
-
`Default worktree root '${
|
|
18146
|
+
`Default worktree root '${import_node_path49.default.join(mainRepo, "trees")}' is not ignored. Add 'trees/' to '${import_node_path49.default.join(mainRepo, ".gitignore")}' or use --path.`
|
|
17571
18147
|
);
|
|
17572
18148
|
}
|
|
17573
18149
|
}
|
|
17574
18150
|
function legacyDefaultWorktreePath(mainRepo, ws) {
|
|
17575
|
-
return
|
|
18151
|
+
return import_node_path49.default.join(import_node_path49.default.dirname(mainRepo), `${import_node_path49.default.basename(mainRepo)}-${ws}`);
|
|
17576
18152
|
}
|
|
17577
18153
|
function teardownFallbackPath(mainRepo, workspace) {
|
|
17578
18154
|
const candidates = [
|
|
@@ -17625,7 +18201,7 @@ function resolveWorkspaceTarget(mainRepo, target, worktrees, records) {
|
|
|
17625
18201
|
}
|
|
17626
18202
|
const liveRecord = oneRecordMatch(
|
|
17627
18203
|
target,
|
|
17628
|
-
records.filter((
|
|
18204
|
+
records.filter((record5) => sameWorkspacePath(record5.worktreePath, liveBranchWorktree.path))
|
|
17629
18205
|
);
|
|
17630
18206
|
return {
|
|
17631
18207
|
workspace: liveRecord?.workspace ?? liveWorkspace,
|
|
@@ -17634,13 +18210,13 @@ function resolveWorkspaceTarget(mainRepo, target, worktrees, records) {
|
|
|
17634
18210
|
record: liveRecord
|
|
17635
18211
|
};
|
|
17636
18212
|
}
|
|
17637
|
-
const
|
|
17638
|
-
if (
|
|
18213
|
+
const record4 = records.find((candidate) => candidate.workspace === requestedWorkspace);
|
|
18214
|
+
if (record4) {
|
|
17639
18215
|
return {
|
|
17640
|
-
workspace:
|
|
17641
|
-
worktreePath:
|
|
17642
|
-
worktree: worktreeForRecord(worktrees,
|
|
17643
|
-
record:
|
|
18216
|
+
workspace: record4.workspace,
|
|
18217
|
+
worktreePath: record4.worktreePath,
|
|
18218
|
+
worktree: worktreeForRecord(worktrees, record4),
|
|
18219
|
+
record: record4
|
|
17644
18220
|
};
|
|
17645
18221
|
}
|
|
17646
18222
|
const linked = worktrees.filter((worktree2) => worktree2.workspace !== void 0);
|
|
@@ -17661,8 +18237,8 @@ function resolveWorkspaceTarget(mainRepo, target, worktrees, records) {
|
|
|
17661
18237
|
record: void 0
|
|
17662
18238
|
};
|
|
17663
18239
|
}
|
|
17664
|
-
function worktreeForRecord(worktrees,
|
|
17665
|
-
return worktrees.find((candidate) => sameWorkspacePath(candidate.path,
|
|
18240
|
+
function worktreeForRecord(worktrees, record4) {
|
|
18241
|
+
return worktrees.find((candidate) => sameWorkspacePath(candidate.path, record4.worktreePath));
|
|
17666
18242
|
}
|
|
17667
18243
|
function assertDevpodTargetSafe(target, worktrees, devpods) {
|
|
17668
18244
|
if (target.record) {
|
|
@@ -17684,7 +18260,7 @@ function assertFullDownPreflight(mainRepo, target) {
|
|
|
17684
18260
|
if (sameWorkspacePath(target.worktreePath, mainRepo)) {
|
|
17685
18261
|
throw new Error("Refusing to remove the primary Git checkout.");
|
|
17686
18262
|
}
|
|
17687
|
-
if (!target.worktree || target.worktree.prunable || !
|
|
18263
|
+
if (!target.worktree || target.worktree.prunable || !import_node_fs42.default.existsSync(target.worktreePath)) return;
|
|
17688
18264
|
if (target.worktree.locked) {
|
|
17689
18265
|
throw new Error(
|
|
17690
18266
|
`Worktree '${target.worktreePath}' is locked; unlock it before workspace down.`
|
|
@@ -17724,9 +18300,9 @@ async function workspaceUp(branch, opts = {}) {
|
|
|
17724
18300
|
(candidate) => defaultWorktreePath(mainRepo, candidate)
|
|
17725
18301
|
);
|
|
17726
18302
|
const generatedPath = candidatePaths.find(
|
|
17727
|
-
(candidate) => !
|
|
18303
|
+
(candidate) => !import_node_fs42.default.existsSync(candidate) && !worktrees.some((worktree) => sameWorkspacePath(worktree.path, candidate))
|
|
17728
18304
|
);
|
|
17729
|
-
const selectedPath = opts.path ?
|
|
18305
|
+
const selectedPath = opts.path ? import_node_path49.default.resolve(opts.path) : existingBranch?.path ?? generatedPath;
|
|
17730
18306
|
if (!selectedPath) {
|
|
17731
18307
|
throw new Error(`Could not allocate a collision-safe worktree path for branch '${branch}'.`);
|
|
17732
18308
|
}
|
|
@@ -17735,7 +18311,7 @@ async function workspaceUp(branch, opts = {}) {
|
|
|
17735
18311
|
`Branch '${branch}' already uses worktree '${existingBranch.path}', not '${selectedPath}'.`
|
|
17736
18312
|
);
|
|
17737
18313
|
}
|
|
17738
|
-
if (
|
|
18314
|
+
if (import_node_fs42.default.existsSync(selectedPath)) {
|
|
17739
18315
|
const registered = worktrees.find(
|
|
17740
18316
|
(worktree) => sameWorkspacePath(worktree.path, selectedPath)
|
|
17741
18317
|
);
|
|
@@ -17799,26 +18375,26 @@ function workspaceLs(repoPath) {
|
|
|
17799
18375
|
const paths = Array.from(
|
|
17800
18376
|
/* @__PURE__ */ new Set([
|
|
17801
18377
|
...worktrees.map((worktree) => worktree.path),
|
|
17802
|
-
...records.map((
|
|
18378
|
+
...records.map((record4) => record4.worktreePath)
|
|
17803
18379
|
])
|
|
17804
18380
|
);
|
|
17805
18381
|
const routesByWorktreePath = listRoutesForWorktreePaths(paths);
|
|
17806
18382
|
const worktreesByPath = new Map(worktrees.map((worktree) => [worktree.path, worktree]));
|
|
17807
|
-
const recordsByPath = new Map(records.map((
|
|
18383
|
+
const recordsByPath = new Map(records.map((record4) => [record4.worktreePath, record4]));
|
|
17808
18384
|
return paths.map((worktreePath) => {
|
|
17809
18385
|
const worktree = worktreesByPath.get(worktreePath);
|
|
17810
|
-
const
|
|
18386
|
+
const record4 = recordsByPath.get(worktreePath);
|
|
17811
18387
|
const devpods = devpodsForPath(worktreePath);
|
|
17812
|
-
const status =
|
|
18388
|
+
const status = record4 ? inspectWorkspaceOwnership(record4, worktrees, devpods) : void 0;
|
|
17813
18389
|
const matchingDevpods = devpods?.filter(
|
|
17814
18390
|
(devpod) => sameWorkspacePath(devpod.source.localFolder, worktreePath)
|
|
17815
18391
|
);
|
|
17816
18392
|
const wsRoutes = routesByWorktreePath.get(worktreePath) ?? [];
|
|
17817
18393
|
return {
|
|
17818
|
-
workspace:
|
|
17819
|
-
branch: worktree?.branch ??
|
|
18394
|
+
workspace: record4?.workspace ?? worktree?.workspace,
|
|
18395
|
+
branch: worktree?.branch ?? record4?.branch ?? void 0,
|
|
17820
18396
|
worktreePath,
|
|
17821
|
-
legacy: !
|
|
18397
|
+
legacy: !record4 && worktree?.workspace !== void 0,
|
|
17822
18398
|
ownerStatus: status?.ownerStatus,
|
|
17823
18399
|
devpodStatus: status?.devpodStatus ?? (devpods === void 0 ? "unknown" : matchingDevpods?.length === 0 ? "absent" : matchingDevpods?.length === 1 ? "owned" : "conflict"),
|
|
17824
18400
|
routeCount: wsRoutes.length,
|
|
@@ -17875,7 +18451,7 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
|
|
|
17875
18451
|
}
|
|
17876
18452
|
const result = await mutateWorkspaceRuntime("delete", resolved, worktrees, opts.quiet);
|
|
17877
18453
|
if (removeWorktree) {
|
|
17878
|
-
if (resolved.worktree && !resolved.worktree.prunable &&
|
|
18454
|
+
if (resolved.worktree && !resolved.worktree.prunable && import_node_fs42.default.existsSync(resolved.worktreePath)) {
|
|
17879
18455
|
const rm = (0, import_node_child_process30.spawnSync)("git", ["-C", mainRepo, "worktree", "remove", resolved.worktreePath], {
|
|
17880
18456
|
encoding: "utf-8"
|
|
17881
18457
|
});
|
|
@@ -17894,7 +18470,7 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
|
|
|
17894
18470
|
}
|
|
17895
18471
|
return result;
|
|
17896
18472
|
};
|
|
17897
|
-
return resolved.worktree && !resolved.worktree.prunable &&
|
|
18473
|
+
return resolved.worktree && !resolved.worktree.prunable && import_node_fs42.default.existsSync(resolved.worktreePath) ? withLifecycleOperationLock(resolved.worktreePath, operation) : operation();
|
|
17898
18474
|
}
|
|
17899
18475
|
async function workspaceStop(target, opts = {}) {
|
|
17900
18476
|
return runWorkspaceLifecycle("stop", target, opts);
|
|
@@ -17902,13 +18478,13 @@ async function workspaceStop(target, opts = {}) {
|
|
|
17902
18478
|
async function workspaceDown(target, opts = {}) {
|
|
17903
18479
|
return runWorkspaceLifecycle("down", target, opts);
|
|
17904
18480
|
}
|
|
17905
|
-
var import_node_child_process30,
|
|
18481
|
+
var import_node_child_process30, import_node_fs42, import_node_path49, WORKSPACE_ALLOCATION_LOCK_WAIT_MS;
|
|
17906
18482
|
var init_workspace_lifecycle = __esm({
|
|
17907
18483
|
"src/core/workspace-lifecycle.ts"() {
|
|
17908
18484
|
"use strict";
|
|
17909
18485
|
import_node_child_process30 = require("child_process");
|
|
17910
|
-
|
|
17911
|
-
|
|
18486
|
+
import_node_fs42 = __toESM(require("fs"));
|
|
18487
|
+
import_node_path49 = __toESM(require("path"));
|
|
17912
18488
|
init_devpod_mutation();
|
|
17913
18489
|
init_devpod_workspaces();
|
|
17914
18490
|
init_managed_runtime_state();
|
|
@@ -18067,7 +18643,7 @@ var init_version = __esm({
|
|
|
18067
18643
|
|
|
18068
18644
|
// src/cli.ts
|
|
18069
18645
|
var import_commander = require("commander");
|
|
18070
|
-
var CLI_VERSION = true ? "0.0.
|
|
18646
|
+
var CLI_VERSION = true ? "0.0.62" : "0.0.0-dev";
|
|
18071
18647
|
var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
|
|
18072
18648
|
function withErrorHandling(action2) {
|
|
18073
18649
|
return async (...args) => {
|