@devrouter/cli 0.0.61 → 0.0.63
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 +917 -659
- package/dist/devrouter.js +600 -177
- package/package.json +1 -1
- package/upgrade-prompts/0.0.62.md +35 -0
- package/upgrade-prompts/0.0.63.md +36 -0
package/dist/devrouter.js
CHANGED
|
@@ -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.63" : "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(
|
|
@@ -4558,6 +4558,19 @@ function parseManagedStopJson(output2) {
|
|
|
4558
4558
|
throw new Error("Managed stop Docker response is malformed.");
|
|
4559
4559
|
}
|
|
4560
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
|
+
}
|
|
4561
4574
|
function inspectManagedStopDaemon(endpoint) {
|
|
4562
4575
|
const value = parseManagedStopJson(
|
|
4563
4576
|
runManagedStopDocker(["info", "--format", "{{json .ID}}"], endpoint)
|
|
@@ -4579,6 +4592,28 @@ function inspectManagedStopRunnerId(endpoint, containerId) {
|
|
|
4579
4592
|
throw new Error("Provider container binding is unavailable.");
|
|
4580
4593
|
return value;
|
|
4581
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
|
+
}
|
|
4582
4617
|
function inspectManagedStopWorkspaceIds(endpoint, composeDirectory) {
|
|
4583
4618
|
const ids = parseDockerLines(
|
|
4584
4619
|
runManagedStopDocker(
|
|
@@ -4599,6 +4634,33 @@ function inspectManagedStopWorkspaceIds(endpoint, composeDirectory) {
|
|
|
4599
4634
|
assertUniqueContainerIds(ids);
|
|
4600
4635
|
return ids;
|
|
4601
4636
|
}
|
|
4637
|
+
function hasExactManagedStopMissingObjectError(stderr, id3) {
|
|
4638
|
+
if (typeof stderr !== "string") return false;
|
|
4639
|
+
const line = stderr.endsWith("\r\n") ? stderr.slice(0, -2) : stderr.endsWith("\n") ? stderr.slice(0, -1) : stderr;
|
|
4640
|
+
return line.toLowerCase() === `error: no such object: ${id3}`;
|
|
4641
|
+
}
|
|
4642
|
+
function assertManagedStopContainersAbsent(endpoint, ids) {
|
|
4643
|
+
assertManagedStopEndpoint(endpoint);
|
|
4644
|
+
if (!Array.isArray(ids) || ids.length === 0 || ids.length > 256) {
|
|
4645
|
+
throw new Error("Managed stop absence proof requires between 1 and 256 container ids.");
|
|
4646
|
+
}
|
|
4647
|
+
ids.forEach(assertFullContainerId);
|
|
4648
|
+
assertUniqueContainerIds(ids);
|
|
4649
|
+
const env = { ...process.env };
|
|
4650
|
+
delete env.DOCKER_CONTEXT;
|
|
4651
|
+
delete env.DOCKER_HOST;
|
|
4652
|
+
for (const id3 of ids) {
|
|
4653
|
+
const result = (0, import_node_child_process6.spawnSync)("docker", ["--host", endpoint, "inspect", "--format", "{{.Id}}", id3], {
|
|
4654
|
+
env,
|
|
4655
|
+
encoding: "utf-8",
|
|
4656
|
+
timeout: MANAGED_STOP_DOCKER_TIMEOUT_MS,
|
|
4657
|
+
maxBuffer: MANAGED_STOP_DOCKER_MAX_BUFFER
|
|
4658
|
+
});
|
|
4659
|
+
if (result.status !== 1 || result.error || result.signal || result.stdout !== "" && result.stdout !== "\n" && result.stdout !== "\r\n" || !hasExactManagedStopMissingObjectError(result.stderr, id3)) {
|
|
4660
|
+
throw new Error("Managed stop Docker inspection did not prove container absence.");
|
|
4661
|
+
}
|
|
4662
|
+
}
|
|
4663
|
+
}
|
|
4602
4664
|
function stopPinnedManagedContainer(endpoint, containerId) {
|
|
4603
4665
|
assertFullContainerId(containerId);
|
|
4604
4666
|
runManagedStopDocker(["stop", containerId], endpoint, 3e4);
|
|
@@ -7106,6 +7168,8 @@ function buildOnboardingPrompt(options = {}) {
|
|
|
7106
7168
|
`- 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.`,
|
|
7107
7169
|
"- 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.",
|
|
7108
7170
|
"- 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.",
|
|
7171
|
+
"- 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.",
|
|
7172
|
+
"- Busy exec waits up to thirty minutes for a positively identified active lifecycle worker on the same checkout, with stderr progress. Do not stop healthy work to make room. Commands stay serial; timeout or cancellation before admission leaves prior work untouched. A lifecycle fence change cancels admission, and uncertain execution is never replayed.",
|
|
7109
7173
|
"- 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.",
|
|
7110
7174
|
"- 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.",
|
|
7111
7175
|
"- A managed adapter plus `postCreateCommand` requires `waitFor` exactly `postCreateCommand` or `postStartCommand`; managed selective config preserves lifecycle fields and changes only `runServices`.",
|
|
@@ -8689,6 +8753,9 @@ var init_devcontainer_diagnostics = __esm({
|
|
|
8689
8753
|
});
|
|
8690
8754
|
|
|
8691
8755
|
// src/core/route-state.ts
|
|
8756
|
+
function listRoutesForWorktreePath(worktreePath, routes = listHostRouteState()) {
|
|
8757
|
+
return routes.filter((route) => sameWorkspacePath(route.repoPath, worktreePath));
|
|
8758
|
+
}
|
|
8692
8759
|
function listRoutesForWorktreePaths(worktreePaths) {
|
|
8693
8760
|
const routes = listHostRouteState();
|
|
8694
8761
|
const byWorktreePath = /* @__PURE__ */ new Map();
|
|
@@ -9548,11 +9615,13 @@ var init_status = __esm({
|
|
|
9548
9615
|
});
|
|
9549
9616
|
|
|
9550
9617
|
// src/core/devpod-registry.ts
|
|
9551
|
-
function listDevpodWorkspacesRaw() {
|
|
9618
|
+
function listDevpodWorkspacesRaw(options = {}) {
|
|
9552
9619
|
const result = (0, import_node_child_process12.spawnSync)("devpod", ["list", "--output", "json", "--skip-pro"], {
|
|
9553
9620
|
encoding: "utf-8"
|
|
9554
9621
|
});
|
|
9555
|
-
if (result.
|
|
9622
|
+
if (options.allowMissingExecutable && result.error?.code === "ENOENT")
|
|
9623
|
+
return [];
|
|
9624
|
+
if (result.error || result.status !== 0) {
|
|
9556
9625
|
const details = [result.error?.message, result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
9557
9626
|
throw new Error(`devpod list failed: ${details || "devpod is not installed or unavailable"}`);
|
|
9558
9627
|
}
|
|
@@ -9634,6 +9703,10 @@ function listDevsyWorkspaces() {
|
|
|
9634
9703
|
...typeof candidate.uid === "string" ? { uid: candidate.uid } : {},
|
|
9635
9704
|
...typeof candidate.context === "string" ? { context: candidate.context } : {}
|
|
9636
9705
|
};
|
|
9706
|
+
const provider = candidate.provider;
|
|
9707
|
+
if (provider && typeof provider.name === "string") workspace.providerName = provider.name;
|
|
9708
|
+
if (typeof provider?.options?.DOCKER_PATH?.value === "string")
|
|
9709
|
+
workspace.dockerPathOption = provider.options.DOCKER_PATH.value;
|
|
9637
9710
|
if ("lastUsed" in candidate) {
|
|
9638
9711
|
if (typeof candidate.lastUsed === "string") {
|
|
9639
9712
|
workspace.lastUsed = candidate.lastUsed;
|
|
@@ -9691,6 +9764,16 @@ function inspectDevsyWorkspaceOwnership(workspaces, devsyId, worktreePath) {
|
|
|
9691
9764
|
}
|
|
9692
9765
|
return exact[0] ? { status: "owned", workspace: exact[0] } : { status: "absent" };
|
|
9693
9766
|
}
|
|
9767
|
+
function selectDevsyWorkspace(workspaces, repoPath) {
|
|
9768
|
+
const matches = workspaces.filter(
|
|
9769
|
+
(workspace) => sameWorkspacePath(workspace.source.localFolder, repoPath)
|
|
9770
|
+
);
|
|
9771
|
+
if (matches.length > 1) {
|
|
9772
|
+
const ids = matches.map((match) => match.id).join(", ");
|
|
9773
|
+
throw new Error(`Multiple Devsy workspaces reference '${repoPath}': ${ids}`);
|
|
9774
|
+
}
|
|
9775
|
+
return matches[0];
|
|
9776
|
+
}
|
|
9694
9777
|
var import_node_child_process13;
|
|
9695
9778
|
var init_devsy_workspaces = __esm({
|
|
9696
9779
|
"src/core/devsy-workspaces.ts"() {
|
|
@@ -10904,7 +10987,56 @@ function proveRetainedManagedStop(state) {
|
|
|
10904
10987
|
}
|
|
10905
10988
|
return result;
|
|
10906
10989
|
}
|
|
10990
|
+
function absentRegistrationIdentity(state) {
|
|
10991
|
+
resetWorkspaceRuntimeCaches();
|
|
10992
|
+
if (resolveWorkspaceRuntimeOrDefault(state.repoPath) !== "devsy")
|
|
10993
|
+
throw new Error("Stop provider changed.");
|
|
10994
|
+
if (!state.workspace || !isLinkedWorktree(state.repoPath))
|
|
10995
|
+
throw new Error("Absent stop requires a linked workspace.");
|
|
10996
|
+
const record4 = readWorkspaceOwnership(state.repoPath, state.workspace);
|
|
10997
|
+
if (!record4 || record4.workspace !== state.workspace || record4.devpodId !== state.devpodId || !sameWorkspacePath(record4.worktreePath, state.repoPath) || resolveWorktreeWorkspace(state.repoPath) !== state.workspace || inspectWorkspaceOwnership(record4, listGitWorktrees(state.repoPath), void 0).ownerStatus !== "present")
|
|
10998
|
+
throw new Error("Absent stop workspace ownership changed.");
|
|
10999
|
+
for (const entries of [
|
|
11000
|
+
listDevsyWorkspaces(),
|
|
11001
|
+
listDevpodWorkspacesRaw({ allowMissingExecutable: true })
|
|
11002
|
+
]) {
|
|
11003
|
+
if (entries.some(
|
|
11004
|
+
(entry) => entry.id === state.devpodId || sameWorkspacePath(entry.source.localFolder, state.repoPath)
|
|
11005
|
+
))
|
|
11006
|
+
throw new Error("Absent stop requires both provider registrations to remain absent.");
|
|
11007
|
+
}
|
|
11008
|
+
return { record: record4, gitCommonDir: resolveGitCommonDir(state.repoPath) };
|
|
11009
|
+
}
|
|
11010
|
+
function proveManagedStop(state) {
|
|
11011
|
+
const baseline = validateManagedStopBaseline(state.stopBaseline, state);
|
|
11012
|
+
const owner = inspectDevsyWorkspaceOwnership(
|
|
11013
|
+
listDevsyWorkspaces(),
|
|
11014
|
+
state.devpodId,
|
|
11015
|
+
state.repoPath
|
|
11016
|
+
);
|
|
11017
|
+
if (owner.status !== "absent")
|
|
11018
|
+
return { status: "retained", containers: proveRetainedManagedStop(state) };
|
|
11019
|
+
if (baseline.sourceContainer) throw new Error("Absent stop cannot prove a source container.");
|
|
11020
|
+
const before = absentRegistrationIdentity(state);
|
|
11021
|
+
if (!(0, import_node_util2.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state))
|
|
11022
|
+
throw new Error("Retained stop generation changed.");
|
|
11023
|
+
if (inspectManagedStopDaemon(baseline.endpoint) !== baseline.daemonId)
|
|
11024
|
+
throw new Error("Stop daemon changed.");
|
|
11025
|
+
assertManagedStopContainersAbsent(
|
|
11026
|
+
baseline.endpoint,
|
|
11027
|
+
baseline.containers.map((container) => container.id)
|
|
11028
|
+
);
|
|
11029
|
+
const uidBytes = Buffer.byteLength(baseline.uid);
|
|
11030
|
+
const runnerId = uidBytes === 16 || uidBytes === 40 ? baseline.uid : baseline.providerId;
|
|
11031
|
+
if (inspectManagedStopContainers(baseline.project, baseline.endpoint).length !== 0 || inspectManagedStopWorkspaceIds(baseline.endpoint, baseline.composeDirectory).length !== 0 || inspectProviderRunnerContainers(baseline.endpoint, runnerId).length !== 0)
|
|
11032
|
+
throw new Error("Absent stop observed a remaining workspace population.");
|
|
11033
|
+
if (inspectManagedStopDaemon(baseline.endpoint) !== baseline.daemonId || !(0, import_node_util2.isDeepStrictEqual)(absentRegistrationIdentity(state), before) || !(0, import_node_util2.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state))
|
|
11034
|
+
throw new Error("Absent stop authority changed during inspection.");
|
|
11035
|
+
return { status: "proven-absent", containers: [] };
|
|
11036
|
+
}
|
|
10907
11037
|
function stopFromManagedBaseline(state) {
|
|
11038
|
+
const proof = proveManagedStop(state);
|
|
11039
|
+
if (proof.status === "proven-absent") return proof.status;
|
|
10908
11040
|
const baseline = validateManagedStopBaseline(state.stopBaseline, state);
|
|
10909
11041
|
const stopped = /* @__PURE__ */ new Set();
|
|
10910
11042
|
for (const initial of proveRetainedManagedStop(state))
|
|
@@ -10927,6 +11059,12 @@ function stopFromManagedBaseline(state) {
|
|
|
10927
11059
|
}
|
|
10928
11060
|
if (inspect().some((container) => container.state.Running))
|
|
10929
11061
|
throw new Error("Retained workloads remain running.");
|
|
11062
|
+
return "retained";
|
|
11063
|
+
}
|
|
11064
|
+
function managedStopRouteReferences(state) {
|
|
11065
|
+
return state.desired.apps.flatMap(
|
|
11066
|
+
(name) => ["http", "tcp"].map((protocol) => ({ repoPath: state.repoPath, name, protocol }))
|
|
11067
|
+
);
|
|
10930
11068
|
}
|
|
10931
11069
|
var import_node_os2, import_node_path31, import_node_util2;
|
|
10932
11070
|
var init_managed_stop_recovery = __esm({
|
|
@@ -10936,6 +11074,7 @@ var init_managed_stop_recovery = __esm({
|
|
|
10936
11074
|
import_node_path31 = __toESM(require("path"));
|
|
10937
11075
|
import_node_util2 = require("util");
|
|
10938
11076
|
init_devpod_environment();
|
|
11077
|
+
init_devpod_registry();
|
|
10939
11078
|
init_devsy_workspaces();
|
|
10940
11079
|
init_managed_compose_population();
|
|
10941
11080
|
init_managed_runtime_state();
|
|
@@ -11045,8 +11184,7 @@ function stopRetainedManagedDevsyWorkspace(options) {
|
|
|
11045
11184
|
throw new Error("Managed stop requires the exact retained workspace identity.");
|
|
11046
11185
|
}
|
|
11047
11186
|
if (state.stopBaseline) {
|
|
11048
|
-
stopFromManagedBaseline(state);
|
|
11049
|
-
return true;
|
|
11187
|
+
return stopFromManagedBaseline(state) === "proven-absent" ? "proven-absent" : true;
|
|
11050
11188
|
}
|
|
11051
11189
|
const workspaceEnv = workspace ? { token: workspace, gitCommonDir: resolveGitCommonDir(repoPath) } : void 0;
|
|
11052
11190
|
function registration2() {
|
|
@@ -11230,12 +11368,14 @@ function inspectExactOwnership(devsyId, worktreePath) {
|
|
|
11230
11368
|
}
|
|
11231
11369
|
function mutateOwnedDevsyWorkspace(action2, devsyId, worktreePath) {
|
|
11232
11370
|
return withMutationLock(`Devsy ${action2}`, worktreePath, () => {
|
|
11233
|
-
if (action2 === "stop"
|
|
11234
|
-
|
|
11235
|
-
|
|
11236
|
-
|
|
11237
|
-
|
|
11238
|
-
|
|
11371
|
+
if (action2 === "stop") {
|
|
11372
|
+
const stopped = stopRetainedManagedDevsyWorkspace({
|
|
11373
|
+
repoPath: worktreePath,
|
|
11374
|
+
devsyId,
|
|
11375
|
+
stopProvider: () => runDevsyAction("stop", devsyId)
|
|
11376
|
+
});
|
|
11377
|
+
if (stopped) return { status: stopped === "proven-absent" ? "proven-absent" : "changed" };
|
|
11378
|
+
}
|
|
11239
11379
|
const before = inspectExactOwnership(devsyId, worktreePath);
|
|
11240
11380
|
if (before.status === "absent") return { status: "absent" };
|
|
11241
11381
|
runDevsyAction(action2, devsyId);
|
|
@@ -12018,7 +12158,7 @@ async function buildDoctorReport(options = {}) {
|
|
|
12018
12158
|
const config = runtimeConfig.config;
|
|
12019
12159
|
loadedConfig = config;
|
|
12020
12160
|
loadedWorkspace = runtimeConfig.workspace;
|
|
12021
|
-
const cliVersion = true ? "0.0.
|
|
12161
|
+
const cliVersion = true ? "0.0.63" : "0.0.0-dev";
|
|
12022
12162
|
const configVersion = config.devrouter?.version;
|
|
12023
12163
|
if (configVersion && cliVersion !== "0.0.0-dev" && compareSemver(configVersion, cliVersion) > 0) {
|
|
12024
12164
|
addCheck(checks, {
|
|
@@ -12651,6 +12791,96 @@ var init_up = __esm({
|
|
|
12651
12791
|
}
|
|
12652
12792
|
});
|
|
12653
12793
|
|
|
12794
|
+
// src/core/devsy-exec-proof.ts
|
|
12795
|
+
function proveLocalDockerSelection(workspace) {
|
|
12796
|
+
const name = workspace.providerName;
|
|
12797
|
+
const context = workspace.context;
|
|
12798
|
+
if (!name || !context || !/^[a-z0-9][a-z0-9-]{0,31}$/.test(name) || !/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(context))
|
|
12799
|
+
throw new Error("Retained exec provider configuration identity is unavailable.");
|
|
12800
|
+
const file = import_node_path37.default.join(
|
|
12801
|
+
process.env.DEVSY_HOME || import_node_path37.default.join(import_node_os4.default.homedir(), ".devsy"),
|
|
12802
|
+
"contexts",
|
|
12803
|
+
context,
|
|
12804
|
+
"providers",
|
|
12805
|
+
name,
|
|
12806
|
+
"provider.json"
|
|
12807
|
+
);
|
|
12808
|
+
try {
|
|
12809
|
+
if (import_node_fs30.default.statSync(file).size > 1024 * 1024) throw new Error();
|
|
12810
|
+
const config = JSON.parse(import_node_fs30.default.readFileSync(file, "utf8"));
|
|
12811
|
+
const driver = config.agent?.driver ?? "docker";
|
|
12812
|
+
const configuredPath = config.agent?.docker?.path ?? "";
|
|
12813
|
+
if (typeof configuredPath !== "string") throw new Error();
|
|
12814
|
+
const command = configuredPath === "${DOCKER_PATH}" || configuredPath === "$DOCKER_PATH" ? workspace.dockerPathOption || process.env.DOCKER_PATH || "docker" : configuredPath || "docker";
|
|
12815
|
+
if (config.name !== name || driver !== "docker" || command !== "docker") throw new Error();
|
|
12816
|
+
} catch {
|
|
12817
|
+
throw new Error("Retained exec requires a provider configured for the local Docker command.");
|
|
12818
|
+
}
|
|
12819
|
+
return name;
|
|
12820
|
+
}
|
|
12821
|
+
function captureDevsyExecProof(repoPath) {
|
|
12822
|
+
repoPath = comparableWorkspacePath(repoPath);
|
|
12823
|
+
const registry = listDevsyWorkspaces();
|
|
12824
|
+
const workspace = selectDevsyWorkspace(registry, repoPath);
|
|
12825
|
+
if (!workspace?.uid || !workspace.context || workspace.source.container || inspectDevsyWorkspaceOwnership(registry, workspace.id, repoPath).status !== "owned" || inspectDevsyRuntimeStatus(workspace.id) !== "running")
|
|
12826
|
+
throw new Error("Retained exec requires one exact running Devsy registration.");
|
|
12827
|
+
const providerName = proveLocalDockerSelection(workspace);
|
|
12828
|
+
const endpoint = resolveManagedStopEndpoint();
|
|
12829
|
+
if (!supportsManagedStopBaseline(endpoint))
|
|
12830
|
+
throw new Error("Retained exec requires an exact local Docker endpoint.");
|
|
12831
|
+
const daemon = inspectManagedStopDaemon(endpoint);
|
|
12832
|
+
const candidates = workspaceAppContainers(inspectWorkspaceContainers(), repoPath).filter(
|
|
12833
|
+
(container) => container.state.Running
|
|
12834
|
+
);
|
|
12835
|
+
if (candidates.length !== 1)
|
|
12836
|
+
throw new Error("Retained exec requires one exact running primary container.");
|
|
12837
|
+
const candidate = candidates[0];
|
|
12838
|
+
const project = candidate.labels["com.docker.compose.project"];
|
|
12839
|
+
if (!project) throw new Error("Retained exec primary has no Compose identity.");
|
|
12840
|
+
const pinned = workspaceAppContainers(
|
|
12841
|
+
inspectManagedStopContainers(project, endpoint),
|
|
12842
|
+
repoPath
|
|
12843
|
+
).filter((container) => container.state.Running);
|
|
12844
|
+
if (pinned.length !== 1 || pinned[0].id !== candidate.id)
|
|
12845
|
+
throw new Error("Retained exec primary changed during inspection.");
|
|
12846
|
+
const mounts = pinned[0].mounts.filter(
|
|
12847
|
+
(mount) => mount.Type === "bind" && sameWorkspacePath(mount.Source, repoPath)
|
|
12848
|
+
);
|
|
12849
|
+
if (mounts.length !== 1 || !mounts[0].Destination.startsWith("/"))
|
|
12850
|
+
throw new Error("Retained exec requires one exact source mount.");
|
|
12851
|
+
const uidBytes = Buffer.byteLength(workspace.uid);
|
|
12852
|
+
const runner = uidBytes === 16 || uidBytes === 40 ? workspace.uid : workspace.id;
|
|
12853
|
+
const runnerContainers = inspectProviderRunnerContainers(endpoint, runner);
|
|
12854
|
+
if (runnerContainers.length !== 1 || runnerContainers[0] !== candidate.id)
|
|
12855
|
+
throw new Error("Retained exec runner does not select the exact primary container.");
|
|
12856
|
+
if (inspectManagedStopRunnerId(endpoint, candidate.id) !== runner || inspectManagedStopDaemon(endpoint) !== daemon)
|
|
12857
|
+
throw new Error("Retained exec provider or Docker identity changed.");
|
|
12858
|
+
return {
|
|
12859
|
+
repoPath,
|
|
12860
|
+
id: workspace.id,
|
|
12861
|
+
uid: workspace.uid,
|
|
12862
|
+
context: workspace.context,
|
|
12863
|
+
providerName,
|
|
12864
|
+
endpoint,
|
|
12865
|
+
daemon,
|
|
12866
|
+
containerId: candidate.id,
|
|
12867
|
+
workspacePath: mounts[0].Destination
|
|
12868
|
+
};
|
|
12869
|
+
}
|
|
12870
|
+
var import_node_fs30, import_node_os4, import_node_path37, import_node_util3;
|
|
12871
|
+
var init_devsy_exec_proof = __esm({
|
|
12872
|
+
"src/core/devsy-exec-proof.ts"() {
|
|
12873
|
+
"use strict";
|
|
12874
|
+
import_node_fs30 = __toESM(require("fs"));
|
|
12875
|
+
import_node_os4 = __toESM(require("os"));
|
|
12876
|
+
import_node_path37 = __toESM(require("path"));
|
|
12877
|
+
import_node_util3 = require("util");
|
|
12878
|
+
init_devpod_environment();
|
|
12879
|
+
init_devsy_workspaces();
|
|
12880
|
+
init_workspace();
|
|
12881
|
+
}
|
|
12882
|
+
});
|
|
12883
|
+
|
|
12654
12884
|
// src/core/reliability-model.ts
|
|
12655
12885
|
function record3(value) {
|
|
12656
12886
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -12680,7 +12910,7 @@ function assertReliabilityEvent(value) {
|
|
|
12680
12910
|
valid = oneOf2(value.mode, ["start", "attach"]) && isReliabilityId(value.key) && isReliabilityId(value.operationId) && isReliabilityProfile(value.profile) && isConsumer(value.consumer);
|
|
12681
12911
|
break;
|
|
12682
12912
|
case "operation-request":
|
|
12683
|
-
valid = oneOf2(value.kind, ["ensure", "exec"]) && isReliabilityId(value.key) && isReliabilityId(value.operationId) && isReliabilityProfile(value.profile) && isConsumer(value.consumer) && typeof value.runtimeRunning === "boolean";
|
|
12913
|
+
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");
|
|
12684
12914
|
break;
|
|
12685
12915
|
case "drained":
|
|
12686
12916
|
valid = isReliabilityId(value.operationId);
|
|
@@ -12863,6 +13093,10 @@ function handleRequest(state, event) {
|
|
|
12863
13093
|
});
|
|
12864
13094
|
return transition(state, "accepted");
|
|
12865
13095
|
}
|
|
13096
|
+
function canExecAfterInterruptedEnsure(state) {
|
|
13097
|
+
const latestEnsure = [...state.operationHistory].reverse().find((operation) => operation.kind === "ensure");
|
|
13098
|
+
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));
|
|
13099
|
+
}
|
|
12866
13100
|
function handleOperationRequest(state, event) {
|
|
12867
13101
|
if (state.executionPolicy !== "manual") return unchanged(state, "blocked");
|
|
12868
13102
|
const previous = state.operationHistory.find((entry) => entry.key === event.key);
|
|
@@ -12873,9 +13107,11 @@ function handleOperationRequest(state, event) {
|
|
|
12873
13107
|
);
|
|
12874
13108
|
if (state.operationHistory.some((entry) => entry.id === event.operationId))
|
|
12875
13109
|
return unchanged(state, "conflict");
|
|
13110
|
+
if (event.kind === "exec" && canExecAfterInterruptedEnsure(state) && event.recoverInterruptedEnsure !== true)
|
|
13111
|
+
return unchanged(state, "blocked");
|
|
12876
13112
|
const fullyStopped = state.stopProof.workloadsStopped && state.stopProof.routesRemoved;
|
|
12877
13113
|
const reconcileEnsure = event.kind === "ensure" && state.operation?.kind === "ensure" && ["NOT_STARTED", "INTERRUPTED"].includes(state.operation.status);
|
|
12878
|
-
if (state.operation && (!state.operation.drained || !["COMPLETED", "NOT_LAUNCHED", "NOT_STARTED"].includes(state.operation.status) && !fullyStopped && !reconcileEnsure))
|
|
13114
|
+
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))))
|
|
12879
13115
|
return unchanged(state, "blocked");
|
|
12880
13116
|
if (state.phase === "stopping" || state.desired === "parked-for-capacity")
|
|
12881
13117
|
return unchanged(state, "blocked");
|
|
@@ -12886,8 +13122,9 @@ function handleOperationRequest(state, event) {
|
|
|
12886
13122
|
if (rollover) {
|
|
12887
13123
|
if (state.operation && (!state.operation.drained || !["COMPLETED", "NOT_LAUNCHED"].includes(state.operation.status)))
|
|
12888
13124
|
return unchanged(state, "blocked");
|
|
13125
|
+
const latestEnsureId = [...state.operationHistory].reverse().find((entry) => entry.kind === "ensure")?.id;
|
|
12889
13126
|
retired = state.operationHistory.findIndex(
|
|
12890
|
-
(entry) => entry.id !== state.operation?.id && entry.drained && ["COMPLETED", "NOT_LAUNCHED"].includes(entry.status)
|
|
13127
|
+
(entry) => entry.id !== state.operation?.id && entry.id !== latestEnsureId && entry.drained && ["COMPLETED", "NOT_LAUNCHED"].includes(entry.status)
|
|
12891
13128
|
);
|
|
12892
13129
|
if (retired === -1) return unchanged(state, "blocked");
|
|
12893
13130
|
}
|
|
@@ -13215,7 +13452,7 @@ function applyReliabilityEvent(state, event, nowMs) {
|
|
|
13215
13452
|
case "not-started":
|
|
13216
13453
|
if (next.executionPolicy !== "manual" || next.operation?.kind !== "exec")
|
|
13217
13454
|
return unchanged(next, "blocked");
|
|
13218
|
-
if (next.operation.id !== event.operationId || next.operation.status
|
|
13455
|
+
if (next.operation.id !== event.operationId || !["RUNNING", "DISPATCH_RECORDED"].includes(next.operation.status))
|
|
13219
13456
|
return unchanged(next, "stale");
|
|
13220
13457
|
next.operation = { ...next.operation, status: "NOT_LAUNCHED", exitCode: null };
|
|
13221
13458
|
return transition(next, "accepted");
|
|
@@ -13270,6 +13507,11 @@ function sameFence2(record4, fence) {
|
|
|
13270
13507
|
([key, value]) => value === fence[key]
|
|
13271
13508
|
);
|
|
13272
13509
|
}
|
|
13510
|
+
function hasDuplicateOperation(record4, requestId, operationId) {
|
|
13511
|
+
return record4.state.operationHistory.some(
|
|
13512
|
+
(operation) => operation.key === requestId || operation.id === operationId
|
|
13513
|
+
);
|
|
13514
|
+
}
|
|
13273
13515
|
function workerGroupAbsent(pid) {
|
|
13274
13516
|
try {
|
|
13275
13517
|
process.kill(-pid, 0);
|
|
@@ -13286,11 +13528,11 @@ function signalOwnedGroup(child, signal) {
|
|
|
13286
13528
|
if (error.code !== "ESRCH") throw error;
|
|
13287
13529
|
}
|
|
13288
13530
|
}
|
|
13289
|
-
async function runLifecycleWorker(request) {
|
|
13531
|
+
async function runLifecycleWorker(request, beforeAdmission) {
|
|
13290
13532
|
if (process.platform === "win32")
|
|
13291
13533
|
throw new Error("Lifecycle workers require POSIX process-group ownership.");
|
|
13292
|
-
const workerPath =
|
|
13293
|
-
if (!
|
|
13534
|
+
const workerPath = import_node_path38.default.join(__dirname, "devrouter-lifecycle-worker.js");
|
|
13535
|
+
if (!import_node_fs31.default.existsSync(workerPath))
|
|
13294
13536
|
throw new Error("The packaged lifecycle worker is missing; rebuild the CLI.");
|
|
13295
13537
|
const child = (0, import_node_child_process21.fork)(workerPath, [], {
|
|
13296
13538
|
detached: true,
|
|
@@ -13298,13 +13540,18 @@ async function runLifecycleWorker(request) {
|
|
|
13298
13540
|
execArgv: []
|
|
13299
13541
|
});
|
|
13300
13542
|
let ready = false;
|
|
13543
|
+
let closed = false;
|
|
13544
|
+
let registered = false;
|
|
13545
|
+
let dispatchAcknowledged = false;
|
|
13546
|
+
let sendAttempted = false;
|
|
13547
|
+
let readinessTimer;
|
|
13301
13548
|
let result;
|
|
13302
13549
|
let cancelled = false;
|
|
13303
13550
|
let forceTimer;
|
|
13304
13551
|
let monitor;
|
|
13305
13552
|
let failure;
|
|
13306
13553
|
const cancel = () => {
|
|
13307
|
-
if (cancelled) return;
|
|
13554
|
+
if (cancelled || closed) return;
|
|
13308
13555
|
cancelled = true;
|
|
13309
13556
|
try {
|
|
13310
13557
|
signalOwnedGroup(child, "SIGTERM");
|
|
@@ -13327,20 +13574,56 @@ async function runLifecycleWorker(request) {
|
|
|
13327
13574
|
child.once("error", (error) => {
|
|
13328
13575
|
failure = error;
|
|
13329
13576
|
});
|
|
13330
|
-
|
|
13577
|
+
readinessTimer = setTimeout(() => {
|
|
13578
|
+
failure = new Error("Lifecycle worker readiness timed out before dispatch.");
|
|
13579
|
+
cancel();
|
|
13580
|
+
}, 3e4);
|
|
13581
|
+
child.on("message", async (message) => {
|
|
13331
13582
|
if (!message || typeof message !== "object") return;
|
|
13332
13583
|
if ("ready" in message && message.ready === true && !ready) {
|
|
13333
13584
|
ready = true;
|
|
13585
|
+
if (readinessTimer) clearTimeout(readinessTimer);
|
|
13334
13586
|
try {
|
|
13587
|
+
await new Promise((resolve2) => setImmediate(resolve2));
|
|
13588
|
+
if (closed) return;
|
|
13335
13589
|
if (cancelled || !child.pid)
|
|
13336
13590
|
throw new Error("Lifecycle invocation was cancelled before dispatch.");
|
|
13337
13591
|
if (request.kind !== "stop") {
|
|
13338
13592
|
const birth = processBirthIdentity(child.pid);
|
|
13339
13593
|
if (!birth) throw new Error("Could not prove lifecycle worker incarnation.");
|
|
13340
13594
|
updateReliabilityOperation(request.identity, (record4) => {
|
|
13341
|
-
|
|
13595
|
+
beforeAdmission?.();
|
|
13596
|
+
if (!request.admission)
|
|
13597
|
+
throw new Error("Lifecycle operation admission is required.");
|
|
13598
|
+
if (hasDuplicateOperation(record4, request.requestId, request.operationId))
|
|
13599
|
+
throw new Error(
|
|
13600
|
+
"Lifecycle operation request conflicts with an existing operation."
|
|
13601
|
+
);
|
|
13602
|
+
if (!sameFence2(record4, request.fence))
|
|
13342
13603
|
throw new Error("Lifecycle intent changed before dispatch.");
|
|
13604
|
+
if (record4.worker || record4.revision !== request.admission.expectedRevision) {
|
|
13605
|
+
throw new LifecycleWorkerAdmissionBusyError();
|
|
13343
13606
|
}
|
|
13607
|
+
const admitted = stepReliability(
|
|
13608
|
+
record4.state,
|
|
13609
|
+
{
|
|
13610
|
+
...request.fence,
|
|
13611
|
+
type: "operation-request",
|
|
13612
|
+
kind: request.kind === "exec" ? "exec" : "ensure",
|
|
13613
|
+
key: request.requestId,
|
|
13614
|
+
operationId: request.operationId,
|
|
13615
|
+
profile: request.admission.profile,
|
|
13616
|
+
consumer: request.admission.consumer,
|
|
13617
|
+
runtimeRunning: request.admission.runtimeRunning,
|
|
13618
|
+
...request.admission.recoverInterruptedEnsure ? { recoverInterruptedEnsure: true } : {}
|
|
13619
|
+
},
|
|
13620
|
+
Date.now()
|
|
13621
|
+
);
|
|
13622
|
+
if (admitted.outcome !== "accepted")
|
|
13623
|
+
throw new Error(`Lifecycle admission is ${admitted.outcome}.`);
|
|
13624
|
+
record4.state = admitted.state;
|
|
13625
|
+
record4.outcome = null;
|
|
13626
|
+
request.fence = reliabilityFence(record4.state);
|
|
13344
13627
|
record4.worker = {
|
|
13345
13628
|
id: request.workerId,
|
|
13346
13629
|
operationId: request.operationId,
|
|
@@ -13356,6 +13639,7 @@ async function runLifecycleWorker(request) {
|
|
|
13356
13639
|
throw new Error("Lifecycle dispatch is blocked.");
|
|
13357
13640
|
record4.state = transition2.state;
|
|
13358
13641
|
});
|
|
13642
|
+
registered = true;
|
|
13359
13643
|
updateReliabilityOperation(request.identity, (record4) => {
|
|
13360
13644
|
const transition2 = stepReliability(
|
|
13361
13645
|
record4.state,
|
|
@@ -13366,12 +13650,18 @@ async function runLifecycleWorker(request) {
|
|
|
13366
13650
|
},
|
|
13367
13651
|
Date.now()
|
|
13368
13652
|
);
|
|
13369
|
-
record4.state = transition2.state;
|
|
13370
13653
|
if (!transition2.effects.some((effect2) => effect2.kind === "launch")) {
|
|
13371
13654
|
throw new Error("Lifecycle dispatch persistence was not acknowledged.");
|
|
13372
13655
|
}
|
|
13656
|
+
record4.state = transition2.state;
|
|
13373
13657
|
});
|
|
13658
|
+
dispatchAcknowledged = true;
|
|
13374
13659
|
}
|
|
13660
|
+
await new Promise((resolve2) => setImmediate(resolve2));
|
|
13661
|
+
if (closed) return;
|
|
13662
|
+
beforeAdmission?.();
|
|
13663
|
+
if (cancelled) throw new Error("Lifecycle invocation was cancelled before dispatch.");
|
|
13664
|
+
sendAttempted = true;
|
|
13375
13665
|
child.send({ request }, (error) => {
|
|
13376
13666
|
if (error) {
|
|
13377
13667
|
failure = error;
|
|
@@ -13396,8 +13686,9 @@ async function runLifecycleWorker(request) {
|
|
|
13396
13686
|
}
|
|
13397
13687
|
});
|
|
13398
13688
|
child.once("close", () => {
|
|
13689
|
+
closed = true;
|
|
13399
13690
|
try {
|
|
13400
|
-
if (request.kind !== "stop") {
|
|
13691
|
+
if (request.kind !== "stop" && registered) {
|
|
13401
13692
|
updateReliabilityOperation(request.identity, (record4) => {
|
|
13402
13693
|
if (record4.worker?.id !== request.workerId || record4.worker.pid !== child.pid) return;
|
|
13403
13694
|
if (!result) {
|
|
@@ -13405,7 +13696,7 @@ async function runLifecycleWorker(request) {
|
|
|
13405
13696
|
record4.state,
|
|
13406
13697
|
{
|
|
13407
13698
|
...reliabilityFence(record4.state),
|
|
13408
|
-
type: "interrupted",
|
|
13699
|
+
type: request.kind === "exec" && dispatchAcknowledged && !sendAttempted ? "not-started" : "interrupted",
|
|
13409
13700
|
operationId: request.operationId
|
|
13410
13701
|
},
|
|
13411
13702
|
Date.now()
|
|
@@ -13439,6 +13730,7 @@ async function runLifecycleWorker(request) {
|
|
|
13439
13730
|
});
|
|
13440
13731
|
});
|
|
13441
13732
|
} finally {
|
|
13733
|
+
if (readinessTimer) clearTimeout(readinessTimer);
|
|
13442
13734
|
if (monitor) clearInterval(monitor);
|
|
13443
13735
|
if (forceTimer) clearTimeout(forceTimer);
|
|
13444
13736
|
process.removeListener("SIGINT", onSignal);
|
|
@@ -13448,29 +13740,24 @@ async function runLifecycleWorker(request) {
|
|
|
13448
13740
|
function newLifecycleIds() {
|
|
13449
13741
|
return { requestId: (0, import_node_crypto16.randomUUID)(), operationId: (0, import_node_crypto16.randomUUID)(), workerId: (0, import_node_crypto16.randomUUID)() };
|
|
13450
13742
|
}
|
|
13451
|
-
var import_node_child_process21, import_node_crypto16,
|
|
13743
|
+
var import_node_child_process21, import_node_crypto16, import_node_fs31, import_node_path38, LifecycleWorkerAdmissionBusyError;
|
|
13452
13744
|
var init_reliability_worker = __esm({
|
|
13453
13745
|
"src/core/reliability-worker.ts"() {
|
|
13454
13746
|
"use strict";
|
|
13455
13747
|
import_node_child_process21 = require("child_process");
|
|
13456
13748
|
import_node_crypto16 = require("crypto");
|
|
13457
|
-
|
|
13458
|
-
|
|
13749
|
+
import_node_fs31 = __toESM(require("fs"));
|
|
13750
|
+
import_node_path38 = __toESM(require("path"));
|
|
13459
13751
|
init_file_lock();
|
|
13460
13752
|
init_reliability_contract();
|
|
13461
13753
|
init_reliability_model();
|
|
13462
13754
|
init_reliability_operation_store();
|
|
13463
|
-
|
|
13464
|
-
|
|
13465
|
-
|
|
13466
|
-
|
|
13467
|
-
|
|
13468
|
-
|
|
13469
|
-
"src/core/managed-host-preparation.ts"() {
|
|
13470
|
-
"use strict";
|
|
13471
|
-
import_node_child_process22 = require("child_process");
|
|
13472
|
-
import_node_fs31 = __toESM(require("fs"));
|
|
13473
|
-
init_repo_config();
|
|
13755
|
+
LifecycleWorkerAdmissionBusyError = class extends Error {
|
|
13756
|
+
constructor() {
|
|
13757
|
+
super("Lifecycle admission lost to another operation.");
|
|
13758
|
+
this.name = "LifecycleWorkerAdmissionBusyError";
|
|
13759
|
+
}
|
|
13760
|
+
};
|
|
13474
13761
|
}
|
|
13475
13762
|
});
|
|
13476
13763
|
|
|
@@ -13479,7 +13766,7 @@ function sleep(ms) {
|
|
|
13479
13766
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
13480
13767
|
}
|
|
13481
13768
|
function inspectRouterApi(protocol) {
|
|
13482
|
-
const result = (0,
|
|
13769
|
+
const result = (0, import_node_child_process22.spawnSync)(
|
|
13483
13770
|
"curl",
|
|
13484
13771
|
[
|
|
13485
13772
|
"--silent",
|
|
@@ -13506,6 +13793,11 @@ function inspectRouterApi(protocol) {
|
|
|
13506
13793
|
if (!Array.isArray(value)) {
|
|
13507
13794
|
return { ok: false, details: `${protocol.toUpperCase()} router API returned a non-array` };
|
|
13508
13795
|
}
|
|
13796
|
+
if (value.some((item) => !isRecord4(item) || typeof item.name !== "string" || !item.name))
|
|
13797
|
+
return {
|
|
13798
|
+
ok: false,
|
|
13799
|
+
details: `${protocol.toUpperCase()} router API returned malformed entries`
|
|
13800
|
+
};
|
|
13509
13801
|
return {
|
|
13510
13802
|
ok: true,
|
|
13511
13803
|
reachedPageLimit: value.length >= ROUTER_API_PAGE_SIZE,
|
|
@@ -13518,6 +13810,9 @@ function inspectRouterApi(protocol) {
|
|
|
13518
13810
|
)
|
|
13519
13811
|
};
|
|
13520
13812
|
}
|
|
13813
|
+
function isRecord4(value) {
|
|
13814
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
13815
|
+
}
|
|
13521
13816
|
function inspectExpectedRoutes(routes, expectation) {
|
|
13522
13817
|
const mismatched = [];
|
|
13523
13818
|
const failures = [];
|
|
@@ -13609,19 +13904,19 @@ async function ensureTraefikRouteExpectation(routes, expectation, options) {
|
|
|
13609
13904
|
async function ensureTraefikRoutesRemoved(routes, options = {}) {
|
|
13610
13905
|
return ensureTraefikRouteExpectation(routes, "removed", options);
|
|
13611
13906
|
}
|
|
13612
|
-
var
|
|
13907
|
+
var import_node_child_process22, import_node_fs32, 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;
|
|
13613
13908
|
var init_traefik_route_health = __esm({
|
|
13614
13909
|
"src/core/traefik-route-health.ts"() {
|
|
13615
13910
|
"use strict";
|
|
13616
|
-
|
|
13911
|
+
import_node_child_process22 = require("child_process");
|
|
13617
13912
|
import_node_fs32 = __toESM(require("fs"));
|
|
13618
|
-
|
|
13913
|
+
import_node_path39 = __toESM(require("path"));
|
|
13619
13914
|
init_file_lock();
|
|
13620
13915
|
init_host_routes();
|
|
13621
13916
|
init_reliability_context();
|
|
13622
13917
|
init_router();
|
|
13623
13918
|
ROUTER_API_BASE = "http://127.0.0.1:8080/api";
|
|
13624
|
-
ROUTER_RELOAD_LOCK_FILE =
|
|
13919
|
+
ROUTER_RELOAD_LOCK_FILE = import_node_path39.default.join(DEVROUTER_HOME, "router-reload.lock");
|
|
13625
13920
|
ROUTER_API_REQUEST_TIMEOUT_SECONDS = "2";
|
|
13626
13921
|
DEFAULT_INITIAL_TIMEOUT_MS = 3e3;
|
|
13627
13922
|
DEFAULT_RECOVERY_TIMEOUT_MS = 1e4;
|
|
@@ -13630,6 +13925,17 @@ var init_traefik_route_health = __esm({
|
|
|
13630
13925
|
}
|
|
13631
13926
|
});
|
|
13632
13927
|
|
|
13928
|
+
// src/core/managed-host-preparation.ts
|
|
13929
|
+
var import_node_child_process23, import_node_fs33;
|
|
13930
|
+
var init_managed_host_preparation = __esm({
|
|
13931
|
+
"src/core/managed-host-preparation.ts"() {
|
|
13932
|
+
"use strict";
|
|
13933
|
+
import_node_child_process23 = require("child_process");
|
|
13934
|
+
import_node_fs33 = __toESM(require("fs"));
|
|
13935
|
+
init_repo_config();
|
|
13936
|
+
}
|
|
13937
|
+
});
|
|
13938
|
+
|
|
13633
13939
|
// src/core/workspace-ensure.ts
|
|
13634
13940
|
function resolveLinkedTarget(repoPath) {
|
|
13635
13941
|
const snapshots = getWorkspaceRegistrySnapshots();
|
|
@@ -13660,13 +13966,13 @@ function resolveLinkedTarget(repoPath) {
|
|
|
13660
13966
|
gitCommonDir: resolveGitCommonDir(repoPath)
|
|
13661
13967
|
};
|
|
13662
13968
|
}
|
|
13663
|
-
var import_node_child_process24,
|
|
13969
|
+
var import_node_child_process24, import_node_fs34, import_node_path40;
|
|
13664
13970
|
var init_workspace_ensure = __esm({
|
|
13665
13971
|
"src/core/workspace-ensure.ts"() {
|
|
13666
13972
|
"use strict";
|
|
13667
13973
|
import_node_child_process24 = require("child_process");
|
|
13668
|
-
|
|
13669
|
-
|
|
13974
|
+
import_node_fs34 = __toESM(require("fs"));
|
|
13975
|
+
import_node_path40 = __toESM(require("path"));
|
|
13670
13976
|
init_devcontainer_profile();
|
|
13671
13977
|
init_devpod_environment();
|
|
13672
13978
|
init_devpod_mutation();
|
|
@@ -13692,6 +13998,11 @@ var init_workspace_ensure = __esm({
|
|
|
13692
13998
|
});
|
|
13693
13999
|
|
|
13694
14000
|
// src/core/reliability-lifecycle.ts
|
|
14001
|
+
function matchesFence(record4, fence) {
|
|
14002
|
+
return Object.entries(reliabilityFence(record4.state)).every(
|
|
14003
|
+
([key, value]) => value === fence[key]
|
|
14004
|
+
);
|
|
14005
|
+
}
|
|
13695
14006
|
async function withLifecycleOperationLock(repoPath, operation) {
|
|
13696
14007
|
if (activeWorker && lockHeld) {
|
|
13697
14008
|
if (!sameWorkspacePath(repoPath, activeWorker.repoPath))
|
|
@@ -13739,6 +14050,23 @@ function reconcileDrained(record4) {
|
|
|
13739
14050
|
record4.worker = null;
|
|
13740
14051
|
}
|
|
13741
14052
|
}
|
|
14053
|
+
function reportBusyExecWait(operationId, waitedMs) {
|
|
14054
|
+
process.stderr.write(
|
|
14055
|
+
`Lifecycle exec ${operationId} is waiting for the existing worker (${Math.ceil(waitedMs / 1e3)}s).
|
|
14056
|
+
`
|
|
14057
|
+
);
|
|
14058
|
+
}
|
|
14059
|
+
function installLifecycleWaitSignals(cancellation) {
|
|
14060
|
+
const onSignal = () => {
|
|
14061
|
+
cancellation.requested = true;
|
|
14062
|
+
};
|
|
14063
|
+
process.once("SIGINT", onSignal);
|
|
14064
|
+
process.once("SIGTERM", onSignal);
|
|
14065
|
+
return () => {
|
|
14066
|
+
process.removeListener("SIGINT", onSignal);
|
|
14067
|
+
process.removeListener("SIGTERM", onSignal);
|
|
14068
|
+
};
|
|
14069
|
+
}
|
|
13742
14070
|
async function superviseLifecycle(kind, repoPath, options = {}, command) {
|
|
13743
14071
|
repoPath = comparableWorkspacePath(repoPath);
|
|
13744
14072
|
if (isLinkedWorktree(repoPath) && !readPersistedWorkspace(repoPath)) {
|
|
@@ -13751,47 +14079,119 @@ async function superviseLifecycle(kind, repoPath, options = {}, command) {
|
|
|
13751
14079
|
provider: resolveWorkspaceRuntimeOrDefault(repoPath)
|
|
13752
14080
|
};
|
|
13753
14081
|
const ids = newLifecycleIds();
|
|
13754
|
-
|
|
13755
|
-
const
|
|
13756
|
-
|
|
13757
|
-
|
|
13758
|
-
|
|
14082
|
+
const copiedOptions = { ...options };
|
|
14083
|
+
const copiedCommand = command ? [...command] : void 0;
|
|
14084
|
+
if (kind === "stop") {
|
|
14085
|
+
const fence = updateReliabilityOperation(identity2, (record4) => {
|
|
14086
|
+
reconcileDrained(record4);
|
|
13759
14087
|
stepRecord(record4, { ...reliabilityFence(record4.state), type: "stop" });
|
|
13760
|
-
|
|
13761
|
-
|
|
14088
|
+
return reliabilityFence(record4.state);
|
|
14089
|
+
});
|
|
14090
|
+
return runLifecycleWorker({ kind, repoPath, identity: identity2, ...ids, fence, options: copiedOptions });
|
|
14091
|
+
}
|
|
14092
|
+
const initial = readReliabilityOperation(identity2) ?? updateReliabilityOperation(identity2, (record4) => record4);
|
|
14093
|
+
const expectedFence = reliabilityFence(initial.state);
|
|
14094
|
+
const deadline = Date.now() + BUSY_EXEC_WAIT_MS;
|
|
14095
|
+
const cancellation = { requested: false };
|
|
14096
|
+
const removeSignals = installLifecycleWaitSignals(cancellation);
|
|
14097
|
+
let lastProgress = -Infinity;
|
|
14098
|
+
const assertWaiting = () => {
|
|
14099
|
+
if (cancellation.requested)
|
|
14100
|
+
throw new Error(`Lifecycle exec ${ids.operationId} cancelled before dispatch.`);
|
|
14101
|
+
if (Date.now() >= deadline)
|
|
14102
|
+
throw new Error(
|
|
14103
|
+
`Lifecycle exec ${ids.operationId} admission timed out; command was not launched.`
|
|
14104
|
+
);
|
|
14105
|
+
};
|
|
14106
|
+
try {
|
|
14107
|
+
for (; ; ) {
|
|
14108
|
+
assertWaiting();
|
|
14109
|
+
let previous = readReliabilityOperation(identity2);
|
|
14110
|
+
if (!previous || !matchesFence(previous, expectedFence))
|
|
13762
14111
|
throw new Error(
|
|
13763
|
-
"
|
|
14112
|
+
"Lifecycle intent changed while awaiting admission; command was not launched."
|
|
13764
14113
|
);
|
|
13765
|
-
|
|
13766
|
-
|
|
13767
|
-
|
|
13768
|
-
|
|
13769
|
-
|
|
13770
|
-
|
|
13771
|
-
|
|
13772
|
-
|
|
13773
|
-
|
|
13774
|
-
|
|
13775
|
-
|
|
14114
|
+
if (hasDuplicateOperation(previous, ids.requestId, ids.operationId))
|
|
14115
|
+
throw new Error("Lifecycle request identity already exists; command was not replayed.");
|
|
14116
|
+
if (previous.worker) {
|
|
14117
|
+
const worker = previous.worker;
|
|
14118
|
+
const birth = processBirthIdentity(worker.pid);
|
|
14119
|
+
if (birth !== worker.birth && workerGroupAbsent(worker.pid)) {
|
|
14120
|
+
previous = updateReliabilityOperation(identity2, (record4) => {
|
|
14121
|
+
reconcileDrained(record4);
|
|
14122
|
+
return record4;
|
|
14123
|
+
});
|
|
14124
|
+
} else {
|
|
14125
|
+
if (!birth || birth !== worker.birth || previous.state.operation?.status === "COMPLETION_UNKNOWN")
|
|
14126
|
+
throw new Error(
|
|
14127
|
+
"Existing lifecycle worker identity or completion is uncertain; preserve its evidence."
|
|
14128
|
+
);
|
|
14129
|
+
if (kind !== "exec")
|
|
14130
|
+
throw new Error("A lifecycle worker is active; wait for its completion before ensure.");
|
|
14131
|
+
const now = Date.now();
|
|
14132
|
+
if (now - lastProgress >= BUSY_EXEC_PROGRESS_MS) {
|
|
14133
|
+
reportBusyExecWait(ids.operationId, now - (deadline - BUSY_EXEC_WAIT_MS));
|
|
14134
|
+
lastProgress = now;
|
|
14135
|
+
}
|
|
14136
|
+
await new Promise(
|
|
14137
|
+
(resolve) => setTimeout(resolve, Math.min(BUSY_EXEC_POLL_MS, deadline - now))
|
|
14138
|
+
);
|
|
14139
|
+
continue;
|
|
14140
|
+
}
|
|
14141
|
+
}
|
|
14142
|
+
if (!previous.worker && previous.state.operation?.status === "NOT_STARTED" && !previous.state.operation.drained)
|
|
14143
|
+
previous = updateReliabilityOperation(identity2, (record4) => {
|
|
14144
|
+
reconcileDrained(record4);
|
|
14145
|
+
return record4;
|
|
14146
|
+
});
|
|
14147
|
+
assertWaiting();
|
|
14148
|
+
const runtimeRunning = kind === "exec" && Boolean(resolveRunningWorkspaceContainer(repoPath));
|
|
14149
|
+
let retainedExecProof;
|
|
14150
|
+
if (kind === "exec" && identity2.provider === "devsy" && canExecAfterInterruptedEnsure(previous.state))
|
|
14151
|
+
retainedExecProof = withMutationLock(
|
|
14152
|
+
"Prove retained exec",
|
|
14153
|
+
repoPath,
|
|
14154
|
+
() => captureDevsyExecProof(repoPath)
|
|
14155
|
+
);
|
|
14156
|
+
assertWaiting();
|
|
14157
|
+
try {
|
|
14158
|
+
return await runLifecycleWorker(
|
|
14159
|
+
{
|
|
14160
|
+
kind,
|
|
14161
|
+
repoPath,
|
|
14162
|
+
identity: identity2,
|
|
14163
|
+
...ids,
|
|
14164
|
+
fence: { ...expectedFence },
|
|
14165
|
+
options: copiedOptions,
|
|
14166
|
+
...copiedCommand ? { command: copiedCommand } : {},
|
|
14167
|
+
...retainedExecProof ? { retainedExecProof } : {},
|
|
14168
|
+
admission: {
|
|
14169
|
+
expectedRevision: previous.revision,
|
|
14170
|
+
profile: copiedOptions.profile ?? previous.state.profile ?? "full",
|
|
14171
|
+
consumer: { id: "manual-cli", requiredCapabilities: [], pinned: false },
|
|
14172
|
+
runtimeRunning,
|
|
14173
|
+
...retainedExecProof ? { recoverInterruptedEnsure: true } : {}
|
|
14174
|
+
}
|
|
14175
|
+
},
|
|
14176
|
+
assertWaiting
|
|
14177
|
+
);
|
|
14178
|
+
} catch (error) {
|
|
14179
|
+
if (!(error instanceof LifecycleWorkerAdmissionBusyError) || kind !== "exec") throw error;
|
|
14180
|
+
assertWaiting();
|
|
14181
|
+
await new Promise((resolve) => setTimeout(resolve, BUSY_EXEC_POLL_MS));
|
|
14182
|
+
}
|
|
13776
14183
|
}
|
|
13777
|
-
|
|
13778
|
-
|
|
13779
|
-
|
|
13780
|
-
kind,
|
|
13781
|
-
repoPath,
|
|
13782
|
-
identity: identity2,
|
|
13783
|
-
...ids,
|
|
13784
|
-
fence,
|
|
13785
|
-
options,
|
|
13786
|
-
...command ? { command } : {}
|
|
13787
|
-
});
|
|
14184
|
+
} finally {
|
|
14185
|
+
removeSignals();
|
|
14186
|
+
}
|
|
13788
14187
|
}
|
|
13789
|
-
var
|
|
14188
|
+
var import_node_path41, activeWorker, lockHeld, BUSY_EXEC_WAIT_MS, BUSY_EXEC_POLL_MS, BUSY_EXEC_PROGRESS_MS;
|
|
13790
14189
|
var init_reliability_lifecycle = __esm({
|
|
13791
14190
|
"src/core/reliability-lifecycle.ts"() {
|
|
13792
14191
|
"use strict";
|
|
13793
|
-
|
|
14192
|
+
import_node_path41 = __toESM(require("path"));
|
|
13794
14193
|
init_devpod_environment();
|
|
14194
|
+
init_devsy_exec_proof();
|
|
13795
14195
|
init_devsy_mutation();
|
|
13796
14196
|
init_file_lock();
|
|
13797
14197
|
init_host_routes();
|
|
@@ -13802,11 +14202,15 @@ var init_reliability_lifecycle = __esm({
|
|
|
13802
14202
|
init_reliability_model();
|
|
13803
14203
|
init_reliability_operation_store();
|
|
13804
14204
|
init_reliability_worker();
|
|
14205
|
+
init_traefik_route_health();
|
|
13805
14206
|
init_workspace();
|
|
13806
14207
|
init_workspace_ensure();
|
|
13807
14208
|
init_workspace_runtime();
|
|
13808
14209
|
init_reliability_context();
|
|
13809
14210
|
lockHeld = false;
|
|
14211
|
+
BUSY_EXEC_WAIT_MS = 30 * 60 * 1e3;
|
|
14212
|
+
BUSY_EXEC_POLL_MS = 250;
|
|
14213
|
+
BUSY_EXEC_PROGRESS_MS = 1e4;
|
|
13810
14214
|
}
|
|
13811
14215
|
});
|
|
13812
14216
|
|
|
@@ -14200,17 +14604,17 @@ function asRecord3(value) {
|
|
|
14200
14604
|
return value;
|
|
14201
14605
|
}
|
|
14202
14606
|
function readJson(filePath) {
|
|
14203
|
-
if (!
|
|
14607
|
+
if (!import_node_fs35.default.existsSync(filePath)) {
|
|
14204
14608
|
return void 0;
|
|
14205
14609
|
}
|
|
14206
14610
|
try {
|
|
14207
|
-
return JSON.parse(
|
|
14611
|
+
return JSON.parse(import_node_fs35.default.readFileSync(filePath, "utf-8"));
|
|
14208
14612
|
} catch {
|
|
14209
14613
|
return void 0;
|
|
14210
14614
|
}
|
|
14211
14615
|
}
|
|
14212
14616
|
function relative(repoPath, filePath) {
|
|
14213
|
-
return
|
|
14617
|
+
return import_node_path42.default.relative(repoPath, filePath) || ".";
|
|
14214
14618
|
}
|
|
14215
14619
|
function redactEnvAssignments(value) {
|
|
14216
14620
|
return value.replace(
|
|
@@ -14251,7 +14655,7 @@ function inspectPackageManager(repoPath, pkg) {
|
|
|
14251
14655
|
["bun.lock", "bun"]
|
|
14252
14656
|
];
|
|
14253
14657
|
for (const [fileName, name] of lockfiles) {
|
|
14254
|
-
if (
|
|
14658
|
+
if (import_node_fs35.default.existsSync(import_node_path42.default.join(repoPath, fileName))) {
|
|
14255
14659
|
return { name, source: fileName };
|
|
14256
14660
|
}
|
|
14257
14661
|
}
|
|
@@ -14266,9 +14670,9 @@ function inspectNode(repoPath, pkg) {
|
|
|
14266
14670
|
if (typeof engines?.node === "string") {
|
|
14267
14671
|
return { version: engines.node, source: "package.json:engines.node" };
|
|
14268
14672
|
}
|
|
14269
|
-
const nvmrc =
|
|
14270
|
-
if (
|
|
14271
|
-
const version =
|
|
14673
|
+
const nvmrc = import_node_path42.default.join(repoPath, ".nvmrc");
|
|
14674
|
+
if (import_node_fs35.default.existsSync(nvmrc)) {
|
|
14675
|
+
const version = import_node_fs35.default.readFileSync(nvmrc, "utf-8").trim();
|
|
14272
14676
|
return { version, source: ".nvmrc" };
|
|
14273
14677
|
}
|
|
14274
14678
|
return void 0;
|
|
@@ -14329,7 +14733,7 @@ function configuredComposeFiles(repoPath) {
|
|
|
14329
14733
|
const files = config.apps.filter(
|
|
14330
14734
|
(app) => app.runtime === "docker"
|
|
14331
14735
|
).flatMap((app) => app.docker.composeFiles).filter(
|
|
14332
|
-
(fileName) => !
|
|
14736
|
+
(fileName) => !import_node_path42.default.isAbsolute(fileName) && !import_node_path42.default.normalize(fileName).startsWith("..")
|
|
14333
14737
|
);
|
|
14334
14738
|
return Array.from(new Set(files));
|
|
14335
14739
|
} catch {
|
|
@@ -14347,7 +14751,7 @@ function composeFiles(repoPath) {
|
|
|
14347
14751
|
...configuredComposeFiles(repoPath)
|
|
14348
14752
|
];
|
|
14349
14753
|
return Array.from(new Set(candidates)).filter(
|
|
14350
|
-
(fileName) =>
|
|
14754
|
+
(fileName) => import_node_fs35.default.existsSync(import_node_path42.default.join(repoPath, fileName))
|
|
14351
14755
|
);
|
|
14352
14756
|
}
|
|
14353
14757
|
function stringArray2(value) {
|
|
@@ -14385,7 +14789,7 @@ function inspectServices(repoPath) {
|
|
|
14385
14789
|
const services = [];
|
|
14386
14790
|
for (const fileName of composeFiles(repoPath)) {
|
|
14387
14791
|
try {
|
|
14388
|
-
const parsed = import_yaml6.default.parse(
|
|
14792
|
+
const parsed = import_yaml6.default.parse(import_node_fs35.default.readFileSync(import_node_path42.default.join(repoPath, fileName), "utf-8"));
|
|
14389
14793
|
const serviceMap = asRecord3(asRecord3(parsed)?.services);
|
|
14390
14794
|
for (const [name, value] of Object.entries(serviceMap ?? {})) {
|
|
14391
14795
|
const service = asRecord3(value);
|
|
@@ -14420,8 +14824,8 @@ function inspectServices(repoPath) {
|
|
|
14420
14824
|
return services;
|
|
14421
14825
|
}
|
|
14422
14826
|
function inspectEnvFiles(repoPath) {
|
|
14423
|
-
const files =
|
|
14424
|
-
const content =
|
|
14827
|
+
const files = import_node_fs35.default.readdirSync(repoPath).filter((fileName) => /^\.env(\.|$)/.test(fileName)).sort().map((fileName) => {
|
|
14828
|
+
const content = import_node_fs35.default.readFileSync(import_node_path42.default.join(repoPath, fileName), "utf-8");
|
|
14425
14829
|
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();
|
|
14426
14830
|
return { path: fileName, names };
|
|
14427
14831
|
});
|
|
@@ -14435,18 +14839,18 @@ function inspectEnvFiles(repoPath) {
|
|
|
14435
14839
|
};
|
|
14436
14840
|
}
|
|
14437
14841
|
function inspectDevcontainer(repoPath) {
|
|
14438
|
-
const dir =
|
|
14439
|
-
if (!
|
|
14842
|
+
const dir = import_node_path42.default.join(repoPath, ".devcontainer");
|
|
14843
|
+
if (!import_node_fs35.default.existsSync(dir)) {
|
|
14440
14844
|
return { exists: false, files: [] };
|
|
14441
14845
|
}
|
|
14442
14846
|
return {
|
|
14443
14847
|
exists: true,
|
|
14444
|
-
files:
|
|
14848
|
+
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}`)
|
|
14445
14849
|
};
|
|
14446
14850
|
}
|
|
14447
14851
|
function inspectDevrouter(repoPath) {
|
|
14448
14852
|
const configPath = getRepoConfigPath(repoPath);
|
|
14449
|
-
if (!
|
|
14853
|
+
if (!import_node_fs35.default.existsSync(configPath)) {
|
|
14450
14854
|
return {
|
|
14451
14855
|
exists: false,
|
|
14452
14856
|
configPath,
|
|
@@ -14490,15 +14894,15 @@ function inspectAgentGuidance(repoPath) {
|
|
|
14490
14894
|
["AGENTS.md", "agents"],
|
|
14491
14895
|
["CLAUDE.md", "claude"]
|
|
14492
14896
|
]) {
|
|
14493
|
-
if (
|
|
14897
|
+
if (import_node_fs35.default.existsSync(import_node_path42.default.join(repoPath, fileName))) {
|
|
14494
14898
|
results.push({ path: fileName, kind });
|
|
14495
14899
|
}
|
|
14496
14900
|
}
|
|
14497
|
-
const skillsDir =
|
|
14498
|
-
if (
|
|
14499
|
-
for (const name of
|
|
14500
|
-
const skillPath =
|
|
14501
|
-
if (
|
|
14901
|
+
const skillsDir = import_node_path42.default.join(repoPath, ".agents", "skills");
|
|
14902
|
+
if (import_node_fs35.default.existsSync(skillsDir)) {
|
|
14903
|
+
for (const name of import_node_fs35.default.readdirSync(skillsDir).sort()) {
|
|
14904
|
+
const skillPath = import_node_path42.default.join(skillsDir, name, "SKILL.md");
|
|
14905
|
+
if (import_node_fs35.default.existsSync(skillPath)) {
|
|
14502
14906
|
results.push({ path: relative(repoPath, skillPath), kind: "skill" });
|
|
14503
14907
|
}
|
|
14504
14908
|
}
|
|
@@ -14541,7 +14945,7 @@ function buildIssues(report) {
|
|
|
14541
14945
|
}
|
|
14542
14946
|
function inspectRepo(options = {}) {
|
|
14543
14947
|
const repoPath = resolveRepoPath(options.repo);
|
|
14544
|
-
const pkg = readJson(
|
|
14948
|
+
const pkg = readJson(import_node_path42.default.join(repoPath, "package.json"));
|
|
14545
14949
|
const scripts = inspectScripts(pkg);
|
|
14546
14950
|
const reportWithoutIssues = {
|
|
14547
14951
|
repoPath,
|
|
@@ -14560,12 +14964,12 @@ function inspectRepo(options = {}) {
|
|
|
14560
14964
|
issues: buildIssues(reportWithoutIssues)
|
|
14561
14965
|
};
|
|
14562
14966
|
}
|
|
14563
|
-
var
|
|
14967
|
+
var import_node_fs35, import_node_path42, import_yaml6;
|
|
14564
14968
|
var init_repo_inspect = __esm({
|
|
14565
14969
|
"src/core/repo-inspect.ts"() {
|
|
14566
14970
|
"use strict";
|
|
14567
|
-
|
|
14568
|
-
|
|
14971
|
+
import_node_fs35 = __toESM(require("fs"));
|
|
14972
|
+
import_node_path42 = __toESM(require("path"));
|
|
14569
14973
|
import_yaml6 = __toESM(require("yaml"));
|
|
14570
14974
|
init_repo_config();
|
|
14571
14975
|
}
|
|
@@ -14670,7 +15074,7 @@ function requiredFileChecks(repoPath) {
|
|
|
14670
15074
|
".devcontainer/docker-compose.yml",
|
|
14671
15075
|
".devrouter.yml"
|
|
14672
15076
|
];
|
|
14673
|
-
const missing = required.filter((fileName) => !
|
|
15077
|
+
const missing = required.filter((fileName) => !import_node_fs36.default.existsSync(import_node_path43.default.join(repoPath, fileName)));
|
|
14674
15078
|
return {
|
|
14675
15079
|
id: "repo.devcontainer.verify-files",
|
|
14676
15080
|
level: missing.length === 0 ? "ok" : "error",
|
|
@@ -14895,12 +15299,12 @@ async function verifyDevcontainer(options = {}) {
|
|
|
14895
15299
|
nextSteps: collectNextSteps3(checks)
|
|
14896
15300
|
};
|
|
14897
15301
|
}
|
|
14898
|
-
var
|
|
15302
|
+
var import_node_fs36, import_node_path43;
|
|
14899
15303
|
var init_devcontainer_verify = __esm({
|
|
14900
15304
|
"src/core/devcontainer-verify.ts"() {
|
|
14901
15305
|
"use strict";
|
|
14902
|
-
|
|
14903
|
-
|
|
15306
|
+
import_node_fs36 = __toESM(require("fs"));
|
|
15307
|
+
import_node_path43 = __toESM(require("path"));
|
|
14904
15308
|
init_capabilities();
|
|
14905
15309
|
init_doctor();
|
|
14906
15310
|
init_host_routes();
|
|
@@ -15197,7 +15601,7 @@ function packageManagerIssues(repo) {
|
|
|
15197
15601
|
}
|
|
15198
15602
|
function plannedFiles(repoPath, version) {
|
|
15199
15603
|
const repo = inspectRepo({ repo: repoPath });
|
|
15200
|
-
const projectName = sanitizeProjectName(
|
|
15604
|
+
const projectName = sanitizeProjectName(import_node_path44.default.basename(repo.repoPath));
|
|
15201
15605
|
const nodeMajor = majorVersion(repo.node?.version, "24");
|
|
15202
15606
|
const pnpmVersion = repo.packageManager?.name === "pnpm" && repo.packageManager.version ? repo.packageManager.version : DEFAULT_PNPM_VERSION;
|
|
15203
15607
|
const port = inferPort2(repo);
|
|
@@ -15242,8 +15646,8 @@ function plannedFiles(repoPath, version) {
|
|
|
15242
15646
|
};
|
|
15243
15647
|
}
|
|
15244
15648
|
function classifyFile(repoPath, file) {
|
|
15245
|
-
const absolutePath =
|
|
15246
|
-
if (!
|
|
15649
|
+
const absolutePath = import_node_path44.default.join(repoPath, file.relativePath);
|
|
15650
|
+
if (!import_node_fs37.default.existsSync(absolutePath)) {
|
|
15247
15651
|
return {
|
|
15248
15652
|
path: file.relativePath,
|
|
15249
15653
|
action: "create",
|
|
@@ -15251,7 +15655,7 @@ function classifyFile(repoPath, file) {
|
|
|
15251
15655
|
bytes: Buffer.byteLength(file.content)
|
|
15252
15656
|
};
|
|
15253
15657
|
}
|
|
15254
|
-
const current =
|
|
15658
|
+
const current = import_node_fs37.default.readFileSync(absolutePath, "utf-8");
|
|
15255
15659
|
if (!current.includes(MANAGED_MARKER2)) {
|
|
15256
15660
|
return {
|
|
15257
15661
|
path: file.relativePath,
|
|
@@ -15308,11 +15712,11 @@ function buildPlan(repoPath, dryRun, version) {
|
|
|
15308
15712
|
};
|
|
15309
15713
|
}
|
|
15310
15714
|
function writeFile(repoPath, file) {
|
|
15311
|
-
const absolutePath =
|
|
15312
|
-
|
|
15313
|
-
|
|
15715
|
+
const absolutePath = import_node_path44.default.join(repoPath, file.relativePath);
|
|
15716
|
+
import_node_fs37.default.mkdirSync(import_node_path44.default.dirname(absolutePath), { recursive: true });
|
|
15717
|
+
import_node_fs37.default.writeFileSync(absolutePath, file.content, "utf-8");
|
|
15314
15718
|
if (file.executable) {
|
|
15315
|
-
|
|
15719
|
+
import_node_fs37.default.chmodSync(absolutePath, 493);
|
|
15316
15720
|
}
|
|
15317
15721
|
}
|
|
15318
15722
|
function writeDevcontainer(options = {}) {
|
|
@@ -15350,12 +15754,12 @@ function writeDevcontainer(options = {}) {
|
|
|
15350
15754
|
nextSteps: postWriteNextSteps(repoPath)
|
|
15351
15755
|
};
|
|
15352
15756
|
}
|
|
15353
|
-
var
|
|
15757
|
+
var import_node_fs37, import_node_path44, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
|
|
15354
15758
|
var init_devcontainer_write = __esm({
|
|
15355
15759
|
"src/core/devcontainer-write.ts"() {
|
|
15356
15760
|
"use strict";
|
|
15357
|
-
|
|
15358
|
-
|
|
15761
|
+
import_node_fs37 = __toESM(require("fs"));
|
|
15762
|
+
import_node_path44 = __toESM(require("path"));
|
|
15359
15763
|
init_repo_config();
|
|
15360
15764
|
init_repo_inspect();
|
|
15361
15765
|
MANAGED_MARKER2 = "devrouter:managed devcontainer";
|
|
@@ -15468,11 +15872,11 @@ var init_repo_devcontainer = __esm({
|
|
|
15468
15872
|
function fail3(message) {
|
|
15469
15873
|
throw new Error(message);
|
|
15470
15874
|
}
|
|
15471
|
-
function
|
|
15875
|
+
function isRecord5(value) {
|
|
15472
15876
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15473
15877
|
}
|
|
15474
15878
|
function requireRecord(value, label) {
|
|
15475
|
-
if (!
|
|
15879
|
+
if (!isRecord5(value)) fail3(`${label} must be a mapping.`);
|
|
15476
15880
|
return value;
|
|
15477
15881
|
}
|
|
15478
15882
|
function requireExactKeys(value, allowed, label) {
|
|
@@ -15689,13 +16093,13 @@ function buildProfilePlanReport(options) {
|
|
|
15689
16093
|
return { ...report, contractPath, bindings };
|
|
15690
16094
|
}
|
|
15691
16095
|
function loadContract(repoPath, contractPath) {
|
|
15692
|
-
if (
|
|
16096
|
+
if (import_node_path45.default.isAbsolute(contractPath)) {
|
|
15693
16097
|
fail3("profile plan contract path must be relative to the repository.");
|
|
15694
16098
|
}
|
|
15695
16099
|
const absolutePath = assertPathWithinRepo(contractPath, repoPath, "Profile plan contract");
|
|
15696
16100
|
let metadata;
|
|
15697
16101
|
try {
|
|
15698
|
-
metadata =
|
|
16102
|
+
metadata = import_node_fs38.default.lstatSync(absolutePath);
|
|
15699
16103
|
} catch (error) {
|
|
15700
16104
|
fail3(`could not inspect profile plan contract '${contractPath}': ${error.message}`);
|
|
15701
16105
|
}
|
|
@@ -15705,16 +16109,16 @@ function loadContract(repoPath, contractPath) {
|
|
|
15705
16109
|
if (metadata.size > MAX_CONTRACT_BYTES) {
|
|
15706
16110
|
fail3(`profile plan contract exceeds the ${MAX_CONTRACT_BYTES}-byte limit.`);
|
|
15707
16111
|
}
|
|
15708
|
-
const realRepo =
|
|
15709
|
-
const realContract =
|
|
16112
|
+
const realRepo = import_node_fs38.default.realpathSync(repoPath);
|
|
16113
|
+
const realContract = import_node_fs38.default.realpathSync(absolutePath);
|
|
15710
16114
|
assertPathWithinRepo(realContract, realRepo, "Profile plan contract");
|
|
15711
|
-
const source =
|
|
16115
|
+
const source = import_node_fs38.default.readFileSync(absolutePath, "utf-8");
|
|
15712
16116
|
if (Buffer.byteLength(source, "utf-8") > MAX_CONTRACT_BYTES) {
|
|
15713
16117
|
fail3(`profile plan contract exceeds the ${MAX_CONTRACT_BYTES}-byte limit.`);
|
|
15714
16118
|
}
|
|
15715
16119
|
return {
|
|
15716
16120
|
contract: parseProfilePlanContract(source),
|
|
15717
|
-
contractPath:
|
|
16121
|
+
contractPath: import_node_path45.default.relative(repoPath, absolutePath).split(import_node_path45.default.sep).join("/")
|
|
15718
16122
|
};
|
|
15719
16123
|
}
|
|
15720
16124
|
function resolveProfilePlan(options) {
|
|
@@ -15728,12 +16132,12 @@ function resolveProfilePlan(options) {
|
|
|
15728
16132
|
contractPath: loaded.contractPath
|
|
15729
16133
|
});
|
|
15730
16134
|
}
|
|
15731
|
-
var
|
|
16135
|
+
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;
|
|
15732
16136
|
var init_profile_plan = __esm({
|
|
15733
16137
|
"src/core/profile-plan.ts"() {
|
|
15734
16138
|
"use strict";
|
|
15735
|
-
|
|
15736
|
-
|
|
16139
|
+
import_node_fs38 = __toESM(require("fs"));
|
|
16140
|
+
import_node_path45 = __toESM(require("path"));
|
|
15737
16141
|
import_yaml7 = require("yaml");
|
|
15738
16142
|
init_paths();
|
|
15739
16143
|
init_profile_resolution();
|
|
@@ -15765,7 +16169,7 @@ async function runProfileResolveCommand(options) {
|
|
|
15765
16169
|
async function runProfilePlanCommand(options) {
|
|
15766
16170
|
const report = resolveProfilePlan(options);
|
|
15767
16171
|
if (options.output) {
|
|
15768
|
-
writeFileAtomically(
|
|
16172
|
+
writeFileAtomically(import_node_path46.default.resolve(options.output), `${JSON.stringify(report, null, 2)}
|
|
15769
16173
|
`);
|
|
15770
16174
|
}
|
|
15771
16175
|
if (options.json) {
|
|
@@ -15801,14 +16205,14 @@ function printProfilePlanSummary(report, output2) {
|
|
|
15801
16205
|
process.stdout.write(`Binding ${key}: ${renderValues(report.bindings[key] ?? [])}
|
|
15802
16206
|
`);
|
|
15803
16207
|
}
|
|
15804
|
-
if (output2) process.stdout.write(`Output: ${
|
|
16208
|
+
if (output2) process.stdout.write(`Output: ${import_node_path46.default.resolve(output2)}
|
|
15805
16209
|
`);
|
|
15806
16210
|
}
|
|
15807
|
-
var
|
|
16211
|
+
var import_node_path46;
|
|
15808
16212
|
var init_profile = __esm({
|
|
15809
16213
|
"src/commands/profile.ts"() {
|
|
15810
16214
|
"use strict";
|
|
15811
|
-
|
|
16215
|
+
import_node_path46 = __toESM(require("path"));
|
|
15812
16216
|
init_atomic_file();
|
|
15813
16217
|
init_output();
|
|
15814
16218
|
init_profile_plan();
|
|
@@ -16098,7 +16502,7 @@ function sanitizeRouterId(value) {
|
|
|
16098
16502
|
return value.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
16099
16503
|
}
|
|
16100
16504
|
function repoHash(repoPath) {
|
|
16101
|
-
return (0, import_node_crypto17.createHash)("sha1").update(
|
|
16505
|
+
return (0, import_node_crypto17.createHash)("sha1").update(import_node_path47.default.resolve(repoPath)).digest("hex").slice(0, 12);
|
|
16102
16506
|
}
|
|
16103
16507
|
function asDockerApp(app) {
|
|
16104
16508
|
return app.runtime === "docker";
|
|
@@ -16177,11 +16581,11 @@ function prepareDockerOverlay(repoPath, appName, apps, publishTcpPorts = false)
|
|
|
16177
16581
|
if (dockerApps.length === 0) {
|
|
16178
16582
|
throw new Error("No docker apps selected to prepare compose overlay.");
|
|
16179
16583
|
}
|
|
16180
|
-
const cachePath =
|
|
16181
|
-
|
|
16182
|
-
const overlayPath =
|
|
16584
|
+
const cachePath = import_node_path47.default.join(CACHE_DIR, repoHash(repoPath), sanitizeRouterId(appName));
|
|
16585
|
+
import_node_fs39.default.mkdirSync(cachePath, { recursive: true });
|
|
16586
|
+
const overlayPath = import_node_path47.default.join(cachePath, "compose.devrouter.yml");
|
|
16183
16587
|
const overlayDocument = buildOverlayDocument(dockerApps, publishTcpPorts);
|
|
16184
|
-
|
|
16588
|
+
import_node_fs39.default.writeFileSync(overlayPath, import_yaml8.default.stringify(overlayDocument, { lineWidth: 0 }), "utf-8");
|
|
16185
16589
|
return {
|
|
16186
16590
|
overlayPath,
|
|
16187
16591
|
composeFiles: ensureComposeFiles(dockerApps),
|
|
@@ -16301,14 +16705,14 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
|
|
|
16301
16705
|
const port = Number(match[1]);
|
|
16302
16706
|
return Number.isInteger(port) && port > 0 ? port : void 0;
|
|
16303
16707
|
}
|
|
16304
|
-
var import_node_child_process27, import_node_crypto17,
|
|
16708
|
+
var import_node_child_process27, import_node_crypto17, import_node_fs39, import_node_path47, import_yaml8;
|
|
16305
16709
|
var init_docker_run = __esm({
|
|
16306
16710
|
"src/core/docker-run.ts"() {
|
|
16307
16711
|
"use strict";
|
|
16308
16712
|
import_node_child_process27 = require("child_process");
|
|
16309
16713
|
import_node_crypto17 = require("crypto");
|
|
16310
|
-
|
|
16311
|
-
|
|
16714
|
+
import_node_fs39 = __toESM(require("fs"));
|
|
16715
|
+
import_node_path47 = __toESM(require("path"));
|
|
16312
16716
|
import_yaml8 = __toESM(require("yaml"));
|
|
16313
16717
|
init_docker_error_guidance();
|
|
16314
16718
|
init_paths();
|
|
@@ -17130,13 +17534,13 @@ function measureWorktreeConsumption(worktreePath, options) {
|
|
|
17130
17534
|
const startedAt = Date.now();
|
|
17131
17535
|
let rootStat;
|
|
17132
17536
|
try {
|
|
17133
|
-
rootStat =
|
|
17537
|
+
rootStat = import_node_fs40.default.lstatSync(worktreePath);
|
|
17134
17538
|
} catch (error) {
|
|
17135
17539
|
return { status: "unknown", reason: describeError(error, worktreePath) };
|
|
17136
17540
|
}
|
|
17137
17541
|
let rootEntries;
|
|
17138
17542
|
try {
|
|
17139
|
-
rootEntries =
|
|
17543
|
+
rootEntries = import_node_fs40.default.readdirSync(worktreePath, { withFileTypes: true });
|
|
17140
17544
|
} catch (error) {
|
|
17141
17545
|
return { status: "unknown", reason: describeError(error, worktreePath) };
|
|
17142
17546
|
}
|
|
@@ -17162,10 +17566,10 @@ function measureWorktreeConsumption(worktreePath, options) {
|
|
|
17162
17566
|
timedOut = true;
|
|
17163
17567
|
break;
|
|
17164
17568
|
}
|
|
17165
|
-
const entryPath =
|
|
17569
|
+
const entryPath = import_node_path48.default.join(dirPath, entry.name);
|
|
17166
17570
|
let entryStat;
|
|
17167
17571
|
try {
|
|
17168
|
-
entryStat =
|
|
17572
|
+
entryStat = import_node_fs40.default.lstatSync(entryPath);
|
|
17169
17573
|
} catch (error) {
|
|
17170
17574
|
if (error?.code === "ENOENT") continue;
|
|
17171
17575
|
unreadableReason = describeIncompleteWalk(error);
|
|
@@ -17175,7 +17579,7 @@ function measureWorktreeConsumption(worktreePath, options) {
|
|
|
17175
17579
|
if (!entryStat.isDirectory()) continue;
|
|
17176
17580
|
let childEntries;
|
|
17177
17581
|
try {
|
|
17178
|
-
childEntries =
|
|
17582
|
+
childEntries = import_node_fs40.default.readdirSync(entryPath, { withFileTypes: true });
|
|
17179
17583
|
} catch (error) {
|
|
17180
17584
|
unreadableReason = describeIncompleteWalk(error);
|
|
17181
17585
|
break;
|
|
@@ -17239,12 +17643,12 @@ function describeError(error, worktreePath) {
|
|
|
17239
17643
|
}
|
|
17240
17644
|
return `could not stat worktree path '${worktreePath}': ${error?.message ?? String(error)}`;
|
|
17241
17645
|
}
|
|
17242
|
-
var
|
|
17646
|
+
var import_node_fs40, import_node_path48, DEFAULT_DEADLINE_MS, BLOCK_SIZE_BYTES;
|
|
17243
17647
|
var init_workspace_consumption = __esm({
|
|
17244
17648
|
"src/core/workspace-consumption.ts"() {
|
|
17245
17649
|
"use strict";
|
|
17246
|
-
|
|
17247
|
-
|
|
17650
|
+
import_node_fs40 = __toESM(require("fs"));
|
|
17651
|
+
import_node_path48 = __toESM(require("path"));
|
|
17248
17652
|
init_devpod_environment();
|
|
17249
17653
|
DEFAULT_DEADLINE_MS = 1e4;
|
|
17250
17654
|
BLOCK_SIZE_BYTES = 512;
|
|
@@ -17252,7 +17656,7 @@ var init_workspace_consumption = __esm({
|
|
|
17252
17656
|
});
|
|
17253
17657
|
|
|
17254
17658
|
// src/core/workspace-cleanup.ts
|
|
17255
|
-
function
|
|
17659
|
+
function isRecord6(value) {
|
|
17256
17660
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
17257
17661
|
}
|
|
17258
17662
|
function isSha(value) {
|
|
@@ -17345,7 +17749,7 @@ function evaluateWorkspaceActivity(evidence, cutoff) {
|
|
|
17345
17749
|
}
|
|
17346
17750
|
function readGitSnapshot(worktree, commandRunner) {
|
|
17347
17751
|
const comparablePath = comparableWorkspacePath(worktree.path);
|
|
17348
|
-
if (worktree.prunable || !
|
|
17752
|
+
if (worktree.prunable || !import_node_fs41.default.existsSync(comparablePath)) {
|
|
17349
17753
|
return { worktree, checkout: "missing", head: null, committerDate: null };
|
|
17350
17754
|
}
|
|
17351
17755
|
const head = gitOutput(comparablePath, ["rev-parse", "--verify", "HEAD"], commandRunner);
|
|
@@ -17396,14 +17800,14 @@ function parseGitHubChanges(value, project, branch) {
|
|
|
17396
17800
|
if (!Array.isArray(value)) return void 0;
|
|
17397
17801
|
const changes = [];
|
|
17398
17802
|
for (const item of value) {
|
|
17399
|
-
if (!
|
|
17400
|
-
const repository =
|
|
17401
|
-
const headRepository =
|
|
17803
|
+
if (!isRecord6(item)) return void 0;
|
|
17804
|
+
const repository = isRecord6(item.repository) ? item.repository.nameWithOwner : void 0;
|
|
17805
|
+
const headRepository = isRecord6(item.headRepository) ? item.headRepository.nameWithOwner : void 0;
|
|
17402
17806
|
if (repository !== project || headRepository !== project || item.headRefName !== branch || !isSha(item.headRefOid) || typeof item.baseRefName !== "string") {
|
|
17403
17807
|
continue;
|
|
17404
17808
|
}
|
|
17405
17809
|
const merged = item.state === "MERGED" && validTimestamp(item.mergedAt);
|
|
17406
|
-
const mergeCommit =
|
|
17810
|
+
const mergeCommit = isRecord6(item.mergeCommit) && isSha(item.mergeCommit.oid) ? item.mergeCommit.oid : void 0;
|
|
17407
17811
|
const baseSha = isSha(item.baseRefOid) ? item.baseRefOid : void 0;
|
|
17408
17812
|
changes.push({
|
|
17409
17813
|
sourceBranch: branch,
|
|
@@ -17420,14 +17824,14 @@ function parseGitLabChanges(value, _project, branch) {
|
|
|
17420
17824
|
if (!Array.isArray(value)) return void 0;
|
|
17421
17825
|
const changes = [];
|
|
17422
17826
|
for (const item of value) {
|
|
17423
|
-
if (!
|
|
17827
|
+
if (!isRecord6(item)) return void 0;
|
|
17424
17828
|
const sourceProjectId = item.source_project_id;
|
|
17425
17829
|
const targetProjectId = item.target_project_id;
|
|
17426
17830
|
const sameProject = typeof sourceProjectId === "number" && typeof targetProjectId === "number" && sourceProjectId === targetProjectId;
|
|
17427
17831
|
if (!sameProject || item.source_branch !== branch || !isSha(item.sha) || typeof item.target_branch !== "string") {
|
|
17428
17832
|
continue;
|
|
17429
17833
|
}
|
|
17430
|
-
const diffRefs =
|
|
17834
|
+
const diffRefs = isRecord6(item.diff_refs) ? item.diff_refs : void 0;
|
|
17431
17835
|
const mergeCommitSha = isSha(item.merge_commit_sha) ? item.merge_commit_sha : void 0;
|
|
17432
17836
|
const baseSha = diffRefs && isSha(diffRefs.base_sha) ? diffRefs.base_sha : void 0;
|
|
17433
17837
|
changes.push({
|
|
@@ -17870,7 +18274,7 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
|
|
|
17870
18274
|
containers2 = measureContainersFn(worktreePaths);
|
|
17871
18275
|
} catch (error) {
|
|
17872
18276
|
const reason = `container measurement failed: ${describeCause(error)}`;
|
|
17873
|
-
containers2 = new Map(worktreePaths.map((
|
|
18277
|
+
containers2 = new Map(worktreePaths.map((path49) => [path49, unknownContainers(reason)]));
|
|
17874
18278
|
}
|
|
17875
18279
|
}
|
|
17876
18280
|
const rows = records.map((record4) => {
|
|
@@ -17919,12 +18323,12 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
|
|
|
17919
18323
|
workspaces: rows
|
|
17920
18324
|
};
|
|
17921
18325
|
}
|
|
17922
|
-
var import_node_child_process29,
|
|
18326
|
+
var import_node_child_process29, import_node_fs41, DEFAULT_INACTIVE_FOR, READ_ONLY_GIT_ENV2, ACTIVITY_SOURCES, defaultCommandRunner;
|
|
17923
18327
|
var init_workspace_cleanup = __esm({
|
|
17924
18328
|
"src/core/workspace-cleanup.ts"() {
|
|
17925
18329
|
"use strict";
|
|
17926
18330
|
import_node_child_process29 = require("child_process");
|
|
17927
|
-
|
|
18331
|
+
import_node_fs41 = __toESM(require("fs"));
|
|
17928
18332
|
init_devpod_workspaces();
|
|
17929
18333
|
init_host_routes();
|
|
17930
18334
|
init_repo_config();
|
|
@@ -17971,7 +18375,7 @@ function warnMissingWorkspaceOwnership(repoPath) {
|
|
|
17971
18375
|
);
|
|
17972
18376
|
}
|
|
17973
18377
|
function defaultWorktreePath(mainRepo, ws) {
|
|
17974
|
-
return
|
|
18378
|
+
return import_node_path49.default.join(mainRepo, "trees", ws);
|
|
17975
18379
|
}
|
|
17976
18380
|
function assertDefaultWorktreeRootIgnored(mainRepo) {
|
|
17977
18381
|
const ignored = (0, import_node_child_process30.spawnSync)("git", ["-C", mainRepo, "check-ignore", "-q", "--no-index", "trees/"], {
|
|
@@ -17979,12 +18383,12 @@ function assertDefaultWorktreeRootIgnored(mainRepo) {
|
|
|
17979
18383
|
});
|
|
17980
18384
|
if (ignored.status !== 0) {
|
|
17981
18385
|
throw new Error(
|
|
17982
|
-
`Default worktree root '${
|
|
18386
|
+
`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.`
|
|
17983
18387
|
);
|
|
17984
18388
|
}
|
|
17985
18389
|
}
|
|
17986
18390
|
function legacyDefaultWorktreePath(mainRepo, ws) {
|
|
17987
|
-
return
|
|
18391
|
+
return import_node_path49.default.join(import_node_path49.default.dirname(mainRepo), `${import_node_path49.default.basename(mainRepo)}-${ws}`);
|
|
17988
18392
|
}
|
|
17989
18393
|
function teardownFallbackPath(mainRepo, workspace) {
|
|
17990
18394
|
const candidates = [
|
|
@@ -18096,7 +18500,7 @@ function assertFullDownPreflight(mainRepo, target) {
|
|
|
18096
18500
|
if (sameWorkspacePath(target.worktreePath, mainRepo)) {
|
|
18097
18501
|
throw new Error("Refusing to remove the primary Git checkout.");
|
|
18098
18502
|
}
|
|
18099
|
-
if (!target.worktree || target.worktree.prunable || !
|
|
18503
|
+
if (!target.worktree || target.worktree.prunable || !import_node_fs42.default.existsSync(target.worktreePath)) return;
|
|
18100
18504
|
if (target.worktree.locked) {
|
|
18101
18505
|
throw new Error(
|
|
18102
18506
|
`Worktree '${target.worktreePath}' is locked; unlock it before workspace down.`
|
|
@@ -18136,9 +18540,9 @@ async function workspaceUp(branch, opts = {}) {
|
|
|
18136
18540
|
(candidate) => defaultWorktreePath(mainRepo, candidate)
|
|
18137
18541
|
);
|
|
18138
18542
|
const generatedPath = candidatePaths.find(
|
|
18139
|
-
(candidate) => !
|
|
18543
|
+
(candidate) => !import_node_fs42.default.existsSync(candidate) && !worktrees.some((worktree) => sameWorkspacePath(worktree.path, candidate))
|
|
18140
18544
|
);
|
|
18141
|
-
const selectedPath = opts.path ?
|
|
18545
|
+
const selectedPath = opts.path ? import_node_path49.default.resolve(opts.path) : existingBranch?.path ?? generatedPath;
|
|
18142
18546
|
if (!selectedPath) {
|
|
18143
18547
|
throw new Error(`Could not allocate a collision-safe worktree path for branch '${branch}'.`);
|
|
18144
18548
|
}
|
|
@@ -18147,7 +18551,7 @@ async function workspaceUp(branch, opts = {}) {
|
|
|
18147
18551
|
`Branch '${branch}' already uses worktree '${existingBranch.path}', not '${selectedPath}'.`
|
|
18148
18552
|
);
|
|
18149
18553
|
}
|
|
18150
|
-
if (
|
|
18554
|
+
if (import_node_fs42.default.existsSync(selectedPath)) {
|
|
18151
18555
|
const registered = worktrees.find(
|
|
18152
18556
|
(worktree) => sameWorkspacePath(worktree.path, selectedPath)
|
|
18153
18557
|
);
|
|
@@ -18249,8 +18653,24 @@ async function mutateWorkspaceRuntime(action2, resolved, worktrees, quiet = fals
|
|
|
18249
18653
|
"Retained managed runtime has no exact provider registration; routes were preserved."
|
|
18250
18654
|
);
|
|
18251
18655
|
}
|
|
18252
|
-
const
|
|
18253
|
-
|
|
18656
|
+
const retained = action2 === "stop" ? readManagedRuntimeState(resolved.worktreePath, resolved.workspace) : void 0;
|
|
18657
|
+
let absent = false;
|
|
18658
|
+
const remove = () => {
|
|
18659
|
+
if (retained?.stopBaseline) {
|
|
18660
|
+
absent = proveManagedStop(retained).status === "proven-absent";
|
|
18661
|
+
if (absent && listRoutesForWorktreePath(resolved.worktreePath).some(
|
|
18662
|
+
(route) => route.workspace !== resolved.workspace || !retained.desired.apps.includes(route.name)
|
|
18663
|
+
))
|
|
18664
|
+
throw new Error("Absent stop contains routes outside the retained desired set.");
|
|
18665
|
+
}
|
|
18666
|
+
claimLifecycleEffect();
|
|
18667
|
+
return removeWorkspaceRoutesForWorktree(resolved.workspace, resolved.worktreePath);
|
|
18668
|
+
};
|
|
18669
|
+
const routes = retained?.stopBaseline ? withMutationLock("Revalidate stop routes", resolved.worktreePath, remove) : remove();
|
|
18670
|
+
await ensureTraefikRoutesRemoved([
|
|
18671
|
+
...routes,
|
|
18672
|
+
...absent && retained ? managedStopRouteReferences(retained) : []
|
|
18673
|
+
]);
|
|
18254
18674
|
if (!quiet) {
|
|
18255
18675
|
process.stdout.write(
|
|
18256
18676
|
`Freed ${routes.length} route(s) for workspace '${resolved.workspace}'.
|
|
@@ -18261,6 +18681,7 @@ async function mutateWorkspaceRuntime(action2, resolved, worktrees, quiet = fals
|
|
|
18261
18681
|
...mutation.status === "changed" ? { devpodId } : {},
|
|
18262
18682
|
freedRoutes: routes.length,
|
|
18263
18683
|
providerChanged: mutation.status === "changed",
|
|
18684
|
+
...absent ? { runtimeAbsent: true } : {},
|
|
18264
18685
|
workspace: resolved.workspace
|
|
18265
18686
|
};
|
|
18266
18687
|
}
|
|
@@ -18287,7 +18708,7 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
|
|
|
18287
18708
|
}
|
|
18288
18709
|
const result = await mutateWorkspaceRuntime("delete", resolved, worktrees, opts.quiet);
|
|
18289
18710
|
if (removeWorktree) {
|
|
18290
|
-
if (resolved.worktree && !resolved.worktree.prunable &&
|
|
18711
|
+
if (resolved.worktree && !resolved.worktree.prunable && import_node_fs42.default.existsSync(resolved.worktreePath)) {
|
|
18291
18712
|
const rm = (0, import_node_child_process30.spawnSync)("git", ["-C", mainRepo, "worktree", "remove", resolved.worktreePath], {
|
|
18292
18713
|
encoding: "utf-8"
|
|
18293
18714
|
});
|
|
@@ -18306,7 +18727,7 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
|
|
|
18306
18727
|
}
|
|
18307
18728
|
return result;
|
|
18308
18729
|
};
|
|
18309
|
-
return resolved.worktree && !resolved.worktree.prunable &&
|
|
18730
|
+
return resolved.worktree && !resolved.worktree.prunable && import_node_fs42.default.existsSync(resolved.worktreePath) ? withLifecycleOperationLock(resolved.worktreePath, operation) : operation();
|
|
18310
18731
|
}
|
|
18311
18732
|
async function workspaceStop(target, opts = {}) {
|
|
18312
18733
|
return runWorkspaceLifecycle("stop", target, opts);
|
|
@@ -18314,16 +18735,18 @@ async function workspaceStop(target, opts = {}) {
|
|
|
18314
18735
|
async function workspaceDown(target, opts = {}) {
|
|
18315
18736
|
return runWorkspaceLifecycle("down", target, opts);
|
|
18316
18737
|
}
|
|
18317
|
-
var import_node_child_process30,
|
|
18738
|
+
var import_node_child_process30, import_node_fs42, import_node_path49, WORKSPACE_ALLOCATION_LOCK_WAIT_MS;
|
|
18318
18739
|
var init_workspace_lifecycle = __esm({
|
|
18319
18740
|
"src/core/workspace-lifecycle.ts"() {
|
|
18320
18741
|
"use strict";
|
|
18321
18742
|
import_node_child_process30 = require("child_process");
|
|
18322
|
-
|
|
18323
|
-
|
|
18743
|
+
import_node_fs42 = __toESM(require("fs"));
|
|
18744
|
+
import_node_path49 = __toESM(require("path"));
|
|
18324
18745
|
init_devpod_mutation();
|
|
18325
18746
|
init_devpod_workspaces();
|
|
18747
|
+
init_devsy_mutation();
|
|
18326
18748
|
init_managed_runtime_state();
|
|
18749
|
+
init_managed_stop_recovery();
|
|
18327
18750
|
init_reliability_lifecycle();
|
|
18328
18751
|
init_repo_config();
|
|
18329
18752
|
init_route_state();
|
|
@@ -18479,7 +18902,7 @@ var init_version = __esm({
|
|
|
18479
18902
|
|
|
18480
18903
|
// src/cli.ts
|
|
18481
18904
|
var import_commander = require("commander");
|
|
18482
|
-
var CLI_VERSION = true ? "0.0.
|
|
18905
|
+
var CLI_VERSION = true ? "0.0.63" : "0.0.0-dev";
|
|
18483
18906
|
var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
|
|
18484
18907
|
function withErrorHandling(action2) {
|
|
18485
18908
|
return async (...args) => {
|