@devrouter/cli 0.0.65 → 0.0.66
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 +78 -2
- package/dist/devrouter.js +243 -166
- package/package.json +1 -1
- package/upgrade-prompts/0.0.66.md +31 -0
|
@@ -3202,7 +3202,16 @@ function registration(state) {
|
|
|
3202
3202
|
throw new Error("Stop workspace identity changed.");
|
|
3203
3203
|
resolveGitCommonDir(state.repoPath);
|
|
3204
3204
|
} else if (state.workspace !== void 0) throw new Error("Stop checkout identity changed.");
|
|
3205
|
-
|
|
3205
|
+
return identityOf(owner.workspace);
|
|
3206
|
+
}
|
|
3207
|
+
function managedRunnerId(uid, providerId) {
|
|
3208
|
+
if (uid === void 0) {
|
|
3209
|
+
return providerId;
|
|
3210
|
+
}
|
|
3211
|
+
const bytes = Buffer.byteLength(uid);
|
|
3212
|
+
return bytes === 16 || bytes === 40 ? uid : providerId;
|
|
3213
|
+
}
|
|
3214
|
+
function identityOf(value) {
|
|
3206
3215
|
return {
|
|
3207
3216
|
context: value.context ?? "",
|
|
3208
3217
|
providerId: value.id,
|
|
@@ -3347,6 +3356,64 @@ function absentRegistrationIdentity(state) {
|
|
|
3347
3356
|
}
|
|
3348
3357
|
return { record: record5, gitCommonDir: resolveGitCommonDir(state.repoPath) };
|
|
3349
3358
|
}
|
|
3359
|
+
function replacementObservation(state) {
|
|
3360
|
+
resetWorkspaceRuntimeCaches();
|
|
3361
|
+
if (resolveWorkspaceRuntimeOrDefault(state.repoPath) !== "devsy")
|
|
3362
|
+
throw new Error("Stop provider changed.");
|
|
3363
|
+
if (!state.workspace || !isLinkedWorktree(state.repoPath))
|
|
3364
|
+
throw new Error("Replacement stop requires a linked workspace.");
|
|
3365
|
+
const record5 = readWorkspaceOwnership(state.repoPath, state.workspace);
|
|
3366
|
+
if (!record5 || record5.workspace !== state.workspace || record5.devpodId !== state.devpodId || !sameWorkspacePath(record5.worktreePath, state.repoPath) || resolveWorktreeWorkspace(state.repoPath) !== state.workspace || inspectWorkspaceOwnership(record5, listGitWorktrees(state.repoPath), void 0).ownerStatus !== "present")
|
|
3367
|
+
throw new Error("Replacement stop workspace ownership changed.");
|
|
3368
|
+
const owner = inspectDevsyWorkspaceOwnership(
|
|
3369
|
+
listDevsyWorkspaces(),
|
|
3370
|
+
state.devpodId,
|
|
3371
|
+
state.repoPath
|
|
3372
|
+
);
|
|
3373
|
+
if (owner.status !== "owned")
|
|
3374
|
+
throw new Error("Replacement stop requires one exact provider registration.");
|
|
3375
|
+
if (listDevpodWorkspacesRaw({ allowMissingExecutable: true }).some(
|
|
3376
|
+
(entry) => entry.id === state.devpodId || sameWorkspacePath(entry.source.localFolder, state.repoPath)
|
|
3377
|
+
))
|
|
3378
|
+
throw new Error("Replacement stop requires no competing provider registration.");
|
|
3379
|
+
proveLocalDockerSelection(owner.workspace);
|
|
3380
|
+
return owner.workspace;
|
|
3381
|
+
}
|
|
3382
|
+
function proveReplacementStopAbsence(state, baseline, replacement) {
|
|
3383
|
+
const observation = () => {
|
|
3384
|
+
const workspace = replacementObservation(state);
|
|
3385
|
+
if (workspace.uid !== replacement.uid)
|
|
3386
|
+
throw new Error("Replacement stop registration changed during inspection.");
|
|
3387
|
+
if (resolveManagedStopEndpoint() !== baseline.endpoint)
|
|
3388
|
+
throw new Error("Stop endpoint changed.");
|
|
3389
|
+
if (inspectManagedStopDaemon(baseline.endpoint) !== baseline.daemonId)
|
|
3390
|
+
throw new Error("Stop daemon changed.");
|
|
3391
|
+
return workspace;
|
|
3392
|
+
};
|
|
3393
|
+
const before = observation();
|
|
3394
|
+
if (!(0, import_node_util2.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state))
|
|
3395
|
+
throw new Error("Retained stop generation changed.");
|
|
3396
|
+
assertManagedStopContainersAbsent(
|
|
3397
|
+
baseline.endpoint,
|
|
3398
|
+
baseline.containers.map((container) => container.id)
|
|
3399
|
+
);
|
|
3400
|
+
const oldRunner = managedRunnerId(baseline.uid, baseline.providerId);
|
|
3401
|
+
const newRunner = managedRunnerId(before.uid, baseline.providerId);
|
|
3402
|
+
const runners = oldRunner === newRunner ? [oldRunner] : [oldRunner, newRunner];
|
|
3403
|
+
const assertEmptyPopulation = () => {
|
|
3404
|
+
if (inspectManagedStopContainers(baseline.project, baseline.endpoint).length !== 0 || inspectManagedStopWorkspaceIds(baseline.endpoint, baseline.composeDirectory).length !== 0 || runners.some((id3) => inspectProviderRunnerContainers(baseline.endpoint, id3).length !== 0))
|
|
3405
|
+
throw new Error("Replacement stop observed a remaining workspace population.");
|
|
3406
|
+
};
|
|
3407
|
+
assertEmptyPopulation();
|
|
3408
|
+
const after = observation();
|
|
3409
|
+
if (after.uid !== before.uid || !(0, import_node_util2.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state))
|
|
3410
|
+
throw new Error("Replacement stop authority changed during inspection.");
|
|
3411
|
+
assertManagedStopContainersAbsent(
|
|
3412
|
+
baseline.endpoint,
|
|
3413
|
+
baseline.containers.map((container) => container.id)
|
|
3414
|
+
);
|
|
3415
|
+
assertEmptyPopulation();
|
|
3416
|
+
}
|
|
3350
3417
|
function proveManagedStop(state) {
|
|
3351
3418
|
const baseline = validateManagedStopBaseline(state.stopBaseline, state);
|
|
3352
3419
|
const owner = inspectDevsyWorkspaceOwnership(
|
|
@@ -3354,6 +3421,15 @@ function proveManagedStop(state) {
|
|
|
3354
3421
|
state.devpodId,
|
|
3355
3422
|
state.repoPath
|
|
3356
3423
|
);
|
|
3424
|
+
if (owner.status === "owned") {
|
|
3425
|
+
const identityKeys = ["context", "providerId", "uid", "sourcePath", "sourceContainer"];
|
|
3426
|
+
const current = identityOf(owner.workspace);
|
|
3427
|
+
const changed = identityKeys.filter((key) => baseline[key] !== current[key]);
|
|
3428
|
+
if (changed.length === 1 && changed[0] === "uid" && !baseline.sourceContainer && !current.sourceContainer && state.workspace !== void 0 && isLinkedWorktree(state.repoPath)) {
|
|
3429
|
+
proveReplacementStopAbsence(state, baseline, { uid: current.uid || void 0 });
|
|
3430
|
+
return { status: "proven-absent", containers: [] };
|
|
3431
|
+
}
|
|
3432
|
+
}
|
|
3357
3433
|
if (owner.status !== "absent")
|
|
3358
3434
|
return { status: "retained", containers: proveRetainedManagedStop(state) };
|
|
3359
3435
|
if (baseline.sourceContainer) throw new Error("Absent stop cannot prove a source container.");
|
|
@@ -4787,7 +4863,7 @@ function loadRepoConfig(repoPath, read2 = (file) => import_node_fs14.default.rea
|
|
|
4787
4863
|
const config = parseConfig(parsed ?? {}, configPath);
|
|
4788
4864
|
const requiredVersion = config.devrouter?.version;
|
|
4789
4865
|
if (requiredVersion && !hasWarnedVersionMismatch) {
|
|
4790
|
-
const cliVersion = true ? "0.0.
|
|
4866
|
+
const cliVersion = true ? "0.0.66" : "0.0.0-dev";
|
|
4791
4867
|
if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
|
|
4792
4868
|
hasWarnedVersionMismatch = true;
|
|
4793
4869
|
process.stderr.write(
|
package/dist/devrouter.js
CHANGED
|
@@ -2520,7 +2520,7 @@ function loadRepoConfig(repoPath, read2 = (file) => import_node_fs6.default.read
|
|
|
2520
2520
|
const config = parseConfig(parsed ?? {}, configPath);
|
|
2521
2521
|
const requiredVersion = config.devrouter?.version;
|
|
2522
2522
|
if (requiredVersion && !hasWarnedVersionMismatch) {
|
|
2523
|
-
const cliVersion = true ? "0.0.
|
|
2523
|
+
const cliVersion = true ? "0.0.66" : "0.0.0-dev";
|
|
2524
2524
|
if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
|
|
2525
2525
|
hasWarnedVersionMismatch = true;
|
|
2526
2526
|
process.stderr.write(
|
|
@@ -12140,6 +12140,96 @@ var init_managed_compose_population = __esm({
|
|
|
12140
12140
|
}
|
|
12141
12141
|
});
|
|
12142
12142
|
|
|
12143
|
+
// src/core/devsy-exec-proof.ts
|
|
12144
|
+
function proveLocalDockerSelection(workspace) {
|
|
12145
|
+
const name = workspace.providerName;
|
|
12146
|
+
const context = workspace.context;
|
|
12147
|
+
if (!name || !context || !/^[a-z0-9][a-z0-9-]{0,31}$/.test(name) || !/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(context))
|
|
12148
|
+
throw new Error("Retained exec provider configuration identity is unavailable.");
|
|
12149
|
+
const file = import_node_path33.default.join(
|
|
12150
|
+
process.env.DEVSY_HOME || import_node_path33.default.join(import_node_os2.default.homedir(), ".devsy"),
|
|
12151
|
+
"contexts",
|
|
12152
|
+
context,
|
|
12153
|
+
"providers",
|
|
12154
|
+
name,
|
|
12155
|
+
"provider.json"
|
|
12156
|
+
);
|
|
12157
|
+
try {
|
|
12158
|
+
if (import_node_fs29.default.statSync(file).size > 1024 * 1024) throw new Error();
|
|
12159
|
+
const config = JSON.parse(import_node_fs29.default.readFileSync(file, "utf8"));
|
|
12160
|
+
const driver = config.agent?.driver ?? "docker";
|
|
12161
|
+
const configuredPath = config.agent?.docker?.path ?? "";
|
|
12162
|
+
if (typeof configuredPath !== "string") throw new Error();
|
|
12163
|
+
const command = configuredPath === "${DOCKER_PATH}" || configuredPath === "$DOCKER_PATH" ? workspace.dockerPathOption || process.env.DOCKER_PATH || "docker" : configuredPath || "docker";
|
|
12164
|
+
if (config.name !== name || driver !== "docker" || command !== "docker") throw new Error();
|
|
12165
|
+
} catch {
|
|
12166
|
+
throw new Error("Retained exec requires a provider configured for the local Docker command.");
|
|
12167
|
+
}
|
|
12168
|
+
return name;
|
|
12169
|
+
}
|
|
12170
|
+
function captureDevsyExecProof(repoPath) {
|
|
12171
|
+
repoPath = comparableWorkspacePath(repoPath);
|
|
12172
|
+
const registry = listDevsyWorkspaces();
|
|
12173
|
+
const workspace = selectDevsyWorkspace(registry, repoPath);
|
|
12174
|
+
if (!workspace?.uid || !workspace.context || workspace.source.container || inspectDevsyWorkspaceOwnership(registry, workspace.id, repoPath).status !== "owned" || inspectDevsyRuntimeStatus(workspace.id) !== "running")
|
|
12175
|
+
throw new Error("Retained exec requires one exact running Devsy registration.");
|
|
12176
|
+
const providerName = proveLocalDockerSelection(workspace);
|
|
12177
|
+
const endpoint = resolveManagedStopEndpoint();
|
|
12178
|
+
if (!supportsManagedStopBaseline(endpoint))
|
|
12179
|
+
throw new Error("Retained exec requires an exact local Docker endpoint.");
|
|
12180
|
+
const daemon = inspectManagedStopDaemon(endpoint);
|
|
12181
|
+
const candidates = workspaceAppContainers(inspectWorkspaceContainers(), repoPath).filter(
|
|
12182
|
+
(container) => container.state.Running
|
|
12183
|
+
);
|
|
12184
|
+
if (candidates.length !== 1)
|
|
12185
|
+
throw new Error("Retained exec requires one exact running primary container.");
|
|
12186
|
+
const candidate = candidates[0];
|
|
12187
|
+
const project = candidate.labels["com.docker.compose.project"];
|
|
12188
|
+
if (!project) throw new Error("Retained exec primary has no Compose identity.");
|
|
12189
|
+
const pinned = workspaceAppContainers(
|
|
12190
|
+
inspectManagedStopContainers(project, endpoint),
|
|
12191
|
+
repoPath
|
|
12192
|
+
).filter((container) => container.state.Running);
|
|
12193
|
+
if (pinned.length !== 1 || pinned[0].id !== candidate.id)
|
|
12194
|
+
throw new Error("Retained exec primary changed during inspection.");
|
|
12195
|
+
const mounts = pinned[0].mounts.filter(
|
|
12196
|
+
(mount) => mount.Type === "bind" && sameWorkspacePath(mount.Source, repoPath)
|
|
12197
|
+
);
|
|
12198
|
+
if (mounts.length !== 1 || !mounts[0].Destination.startsWith("/"))
|
|
12199
|
+
throw new Error("Retained exec requires one exact source mount.");
|
|
12200
|
+
const uidBytes = Buffer.byteLength(workspace.uid);
|
|
12201
|
+
const runner = uidBytes === 16 || uidBytes === 40 ? workspace.uid : workspace.id;
|
|
12202
|
+
const runnerContainers = inspectProviderRunnerContainers(endpoint, runner);
|
|
12203
|
+
if (runnerContainers.length !== 1 || runnerContainers[0] !== candidate.id)
|
|
12204
|
+
throw new Error("Retained exec runner does not select the exact primary container.");
|
|
12205
|
+
if (inspectManagedStopRunnerId(endpoint, candidate.id) !== runner || inspectManagedStopDaemon(endpoint) !== daemon)
|
|
12206
|
+
throw new Error("Retained exec provider or Docker identity changed.");
|
|
12207
|
+
return {
|
|
12208
|
+
repoPath,
|
|
12209
|
+
id: workspace.id,
|
|
12210
|
+
uid: workspace.uid,
|
|
12211
|
+
context: workspace.context,
|
|
12212
|
+
providerName,
|
|
12213
|
+
endpoint,
|
|
12214
|
+
daemon,
|
|
12215
|
+
containerId: candidate.id,
|
|
12216
|
+
workspacePath: mounts[0].Destination
|
|
12217
|
+
};
|
|
12218
|
+
}
|
|
12219
|
+
var import_node_fs29, import_node_os2, import_node_path33, import_node_util2;
|
|
12220
|
+
var init_devsy_exec_proof = __esm({
|
|
12221
|
+
"src/core/devsy-exec-proof.ts"() {
|
|
12222
|
+
"use strict";
|
|
12223
|
+
import_node_fs29 = __toESM(require("fs"));
|
|
12224
|
+
import_node_os2 = __toESM(require("os"));
|
|
12225
|
+
import_node_path33 = __toESM(require("path"));
|
|
12226
|
+
import_node_util2 = require("util");
|
|
12227
|
+
init_devpod_environment();
|
|
12228
|
+
init_devsy_workspaces();
|
|
12229
|
+
init_workspace();
|
|
12230
|
+
}
|
|
12231
|
+
});
|
|
12232
|
+
|
|
12143
12233
|
// src/core/reliability-context.ts
|
|
12144
12234
|
function claimLifecycleEffect() {
|
|
12145
12235
|
claim?.();
|
|
@@ -12168,7 +12258,7 @@ function resolveGitCommonDir(repoPath) {
|
|
|
12168
12258
|
if (result.status !== 0 || !output2) {
|
|
12169
12259
|
throw commandError("Could not resolve the Git common directory", repoPath, result.stderr);
|
|
12170
12260
|
}
|
|
12171
|
-
return comparableWorkspacePath(
|
|
12261
|
+
return comparableWorkspacePath(import_node_path34.default.isAbsolute(output2) ? output2 : import_node_path34.default.resolve(repoPath, output2));
|
|
12172
12262
|
}
|
|
12173
12263
|
function resolveGitTopLevel(repoPath) {
|
|
12174
12264
|
const result = (0, import_node_child_process20.spawnSync)("git", ["-C", repoPath, "rev-parse", "--show-toplevel"], {
|
|
@@ -12219,7 +12309,7 @@ function listGitWorktrees(repoPath) {
|
|
|
12219
12309
|
return worktrees;
|
|
12220
12310
|
}
|
|
12221
12311
|
function ownershipDirectory(repoPath) {
|
|
12222
|
-
return
|
|
12312
|
+
return import_node_path34.default.join(resolveGitCommonDir(repoPath), OWNERSHIP_DIR);
|
|
12223
12313
|
}
|
|
12224
12314
|
function validateWorkspace(value, label) {
|
|
12225
12315
|
if (typeof value !== "string" || wsFromBranch(value) !== value) {
|
|
@@ -12247,7 +12337,7 @@ function validateRecord(value, expectedWorkspace) {
|
|
|
12247
12337
|
`workspace ownership file '${expectedWorkspace}' contains identity '${workspace}'`
|
|
12248
12338
|
);
|
|
12249
12339
|
}
|
|
12250
|
-
if (typeof candidate.worktreePath !== "string" || !
|
|
12340
|
+
if (typeof candidate.worktreePath !== "string" || !import_node_path34.default.isAbsolute(candidate.worktreePath)) {
|
|
12251
12341
|
throw new Error("invalid workspace ownership worktreePath");
|
|
12252
12342
|
}
|
|
12253
12343
|
if (candidate.branch !== null && typeof candidate.branch !== "string") {
|
|
@@ -12265,7 +12355,7 @@ function validateRecord(value, expectedWorkspace) {
|
|
|
12265
12355
|
};
|
|
12266
12356
|
}
|
|
12267
12357
|
function recordPath(repoPath, workspace) {
|
|
12268
|
-
return
|
|
12358
|
+
return import_node_path34.default.join(
|
|
12269
12359
|
ownershipDirectory(repoPath),
|
|
12270
12360
|
`${validateWorkspace(workspace, "workspace")}.json`
|
|
12271
12361
|
);
|
|
@@ -12273,7 +12363,7 @@ function recordPath(repoPath, workspace) {
|
|
|
12273
12363
|
function readRecordFile(filePath, expectedWorkspace) {
|
|
12274
12364
|
let parsed;
|
|
12275
12365
|
try {
|
|
12276
|
-
parsed = JSON.parse(
|
|
12366
|
+
parsed = JSON.parse(import_node_fs30.default.readFileSync(filePath, "utf-8"));
|
|
12277
12367
|
} catch (error) {
|
|
12278
12368
|
if (error instanceof SyntaxError) {
|
|
12279
12369
|
throw new Error(`invalid workspace ownership JSON at '${filePath}'`);
|
|
@@ -12298,7 +12388,7 @@ function listWorkspaceOwnership(repoPath) {
|
|
|
12298
12388
|
function listWorkspaceOwnershipInDirectory(directory) {
|
|
12299
12389
|
let entries;
|
|
12300
12390
|
try {
|
|
12301
|
-
entries =
|
|
12391
|
+
entries = import_node_fs30.default.readdirSync(directory, { withFileTypes: true });
|
|
12302
12392
|
} catch (error) {
|
|
12303
12393
|
if (error.code === "ENOENT") return [];
|
|
12304
12394
|
throw error;
|
|
@@ -12306,14 +12396,14 @@ function listWorkspaceOwnershipInDirectory(directory) {
|
|
|
12306
12396
|
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).sort((left, right) => left.name.localeCompare(right.name)).map((entry) => {
|
|
12307
12397
|
const workspace = entry.name.slice(0, -".json".length);
|
|
12308
12398
|
validateWorkspace(workspace, "filename");
|
|
12309
|
-
return readRecordFile(
|
|
12399
|
+
return readRecordFile(import_node_path34.default.join(directory, entry.name), workspace);
|
|
12310
12400
|
});
|
|
12311
12401
|
}
|
|
12312
12402
|
function writeWorkspaceOwnershipInDirectory(directory, input2) {
|
|
12313
12403
|
const workspace = validateWorkspace(input2.workspace, "workspace");
|
|
12314
12404
|
const devpodId = validateWorkspace(input2.devpodId, "devpodId");
|
|
12315
12405
|
const worktreePath = comparableWorkspacePath(input2.worktreePath);
|
|
12316
|
-
const filePath =
|
|
12406
|
+
const filePath = import_node_path34.default.join(directory, `${workspace}.json`);
|
|
12317
12407
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
12318
12408
|
const records = listWorkspaceOwnershipInDirectory(directory);
|
|
12319
12409
|
const existing = records.find((record6) => record6.workspace === workspace);
|
|
@@ -12349,9 +12439,9 @@ function writeWorkspaceOwnershipInDirectory(directory, input2) {
|
|
|
12349
12439
|
return record5;
|
|
12350
12440
|
}
|
|
12351
12441
|
function removeWorkspaceOwnershipInDirectory(directory, workspace) {
|
|
12352
|
-
const filePath =
|
|
12442
|
+
const filePath = import_node_path34.default.join(directory, `${validateWorkspace(workspace, "workspace")}.json`);
|
|
12353
12443
|
try {
|
|
12354
|
-
|
|
12444
|
+
import_node_fs30.default.rmSync(filePath);
|
|
12355
12445
|
return true;
|
|
12356
12446
|
} catch (error) {
|
|
12357
12447
|
if (error.code === "ENOENT") return false;
|
|
@@ -12362,7 +12452,7 @@ function sameOwnershipRecord(left, right) {
|
|
|
12362
12452
|
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;
|
|
12363
12453
|
}
|
|
12364
12454
|
function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
|
|
12365
|
-
const filePath =
|
|
12455
|
+
const filePath = import_node_path34.default.join(
|
|
12366
12456
|
directory,
|
|
12367
12457
|
`${validateWorkspace(expected.workspace, "workspace")}.json`
|
|
12368
12458
|
);
|
|
@@ -12374,14 +12464,14 @@ function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
|
|
|
12374
12464
|
throw error;
|
|
12375
12465
|
}
|
|
12376
12466
|
if (!sameOwnershipRecord(current, expected)) return "changed";
|
|
12377
|
-
|
|
12467
|
+
import_node_fs30.default.rmSync(filePath);
|
|
12378
12468
|
return "removed";
|
|
12379
12469
|
}
|
|
12380
12470
|
function withWorkspaceOwnershipTransaction(repoPath, operation, options = {}) {
|
|
12381
12471
|
const directory = ownershipDirectory(repoPath);
|
|
12382
|
-
|
|
12472
|
+
import_node_fs30.default.mkdirSync(directory, { recursive: true });
|
|
12383
12473
|
return withFileLockSync(
|
|
12384
|
-
|
|
12474
|
+
import_node_path34.default.join(directory, ".lock"),
|
|
12385
12475
|
{
|
|
12386
12476
|
activity: "workspace ownership transaction",
|
|
12387
12477
|
target: `'${repoPath}'`,
|
|
@@ -12414,7 +12504,7 @@ function persistedWorkspaceOwners(repoPath, worktreePath) {
|
|
|
12414
12504
|
try {
|
|
12415
12505
|
workspace = readPersistedWorkspace(worktree.path);
|
|
12416
12506
|
} catch (error) {
|
|
12417
|
-
if (!
|
|
12507
|
+
if (!import_node_fs30.default.existsSync(worktree.path)) continue;
|
|
12418
12508
|
throw error;
|
|
12419
12509
|
}
|
|
12420
12510
|
if (!workspace) continue;
|
|
@@ -12577,7 +12667,7 @@ function inspectWorkspaceOwnership(record5, worktrees, devpods) {
|
|
|
12577
12667
|
if (worktree?.locked) {
|
|
12578
12668
|
return { ownerStatus: "locked", devpodStatus, worktree };
|
|
12579
12669
|
}
|
|
12580
|
-
if ((!worktree || worktree.prunable) &&
|
|
12670
|
+
if ((!worktree || worktree.prunable) && import_node_fs30.default.existsSync(record5.worktreePath)) {
|
|
12581
12671
|
return { ownerStatus: "conflict", devpodStatus, worktree };
|
|
12582
12672
|
}
|
|
12583
12673
|
if (!worktree || worktree.prunable) {
|
|
@@ -12601,20 +12691,20 @@ function listMissingWorkspaceOwnership(repoPath) {
|
|
|
12601
12691
|
(record5) => inspectWorkspaceOwnership(record5, worktrees, void 0).ownerStatus === "missing"
|
|
12602
12692
|
);
|
|
12603
12693
|
}
|
|
12604
|
-
var import_node_child_process20,
|
|
12694
|
+
var import_node_child_process20, import_node_fs30, import_node_path34, READ_ONLY_GIT_ENV, OWNERSHIP_VERSION, OWNERSHIP_DIR;
|
|
12605
12695
|
var init_workspace_ownership = __esm({
|
|
12606
12696
|
"src/core/workspace-ownership.ts"() {
|
|
12607
12697
|
"use strict";
|
|
12608
12698
|
import_node_child_process20 = require("child_process");
|
|
12609
|
-
|
|
12610
|
-
|
|
12699
|
+
import_node_fs30 = __toESM(require("fs"));
|
|
12700
|
+
import_node_path34 = __toESM(require("path"));
|
|
12611
12701
|
init_atomic_file();
|
|
12612
12702
|
init_devpod_workspaces();
|
|
12613
12703
|
init_file_lock();
|
|
12614
12704
|
init_workspace();
|
|
12615
12705
|
READ_ONLY_GIT_ENV = { ...process.env, GIT_OPTIONAL_LOCKS: "0" };
|
|
12616
12706
|
OWNERSHIP_VERSION = 1;
|
|
12617
|
-
OWNERSHIP_DIR =
|
|
12707
|
+
OWNERSHIP_DIR = import_node_path34.default.join("devrouter", "workspaces");
|
|
12618
12708
|
}
|
|
12619
12709
|
});
|
|
12620
12710
|
|
|
@@ -12636,7 +12726,16 @@ function registration(state) {
|
|
|
12636
12726
|
throw new Error("Stop workspace identity changed.");
|
|
12637
12727
|
resolveGitCommonDir(state.repoPath);
|
|
12638
12728
|
} else if (state.workspace !== void 0) throw new Error("Stop checkout identity changed.");
|
|
12639
|
-
|
|
12729
|
+
return identityOf(owner.workspace);
|
|
12730
|
+
}
|
|
12731
|
+
function managedRunnerId(uid, providerId) {
|
|
12732
|
+
if (uid === void 0) {
|
|
12733
|
+
return providerId;
|
|
12734
|
+
}
|
|
12735
|
+
const bytes = Buffer.byteLength(uid);
|
|
12736
|
+
return bytes === 16 || bytes === 40 ? uid : providerId;
|
|
12737
|
+
}
|
|
12738
|
+
function identityOf(value) {
|
|
12640
12739
|
return {
|
|
12641
12740
|
context: value.context ?? "",
|
|
12642
12741
|
providerId: value.id,
|
|
@@ -12671,7 +12770,7 @@ function prove(state, baseline) {
|
|
|
12671
12770
|
const containers2 = inspectManagedStopContainers(baseline.project, baseline.endpoint);
|
|
12672
12771
|
const expected = baseline.containers.map((container) => ({ ...container, mounts: orderedMounts(container.mounts) })).sort((a, b) => compareText(a.id, b.id));
|
|
12673
12772
|
const observed = containers2.map(identity).sort((a, b) => compareText(a.id, b.id));
|
|
12674
|
-
if (!(0,
|
|
12773
|
+
if (!(0, import_node_util3.isDeepStrictEqual)(expected, observed) || containers2.some(
|
|
12675
12774
|
(container) => container.labels["com.docker.compose.project.working_dir"] !== baseline.composeDirectory
|
|
12676
12775
|
)) {
|
|
12677
12776
|
throw new Error("Retained container population or immutable identity changed.");
|
|
@@ -12680,7 +12779,7 @@ function prove(state, baseline) {
|
|
|
12680
12779
|
baseline.endpoint,
|
|
12681
12780
|
baseline.composeDirectory
|
|
12682
12781
|
).sort();
|
|
12683
|
-
if (!(0,
|
|
12782
|
+
if (!(0, import_node_util3.isDeepStrictEqual)(workspaceIds, expected.map((container) => container.id).sort())) {
|
|
12684
12783
|
throw new Error("Workspace contains an unrecorded container population.");
|
|
12685
12784
|
}
|
|
12686
12785
|
const primary = baseline.containers.find(
|
|
@@ -12692,18 +12791,18 @@ function prove(state, baseline) {
|
|
|
12692
12791
|
if (!baseline.sourceContainer && inspectManagedStopRunnerId(baseline.endpoint, primary.id) !== runnerId) {
|
|
12693
12792
|
throw new Error("Provider-to-container binding changed.");
|
|
12694
12793
|
}
|
|
12695
|
-
if (!(0,
|
|
12794
|
+
if (!(0, import_node_util3.isDeepStrictEqual)(registration(state), owner) || inspectManagedStopDaemon(baseline.endpoint) !== baseline.daemonId) {
|
|
12696
12795
|
throw new Error("Stop authority changed during inspection.");
|
|
12697
12796
|
}
|
|
12698
12797
|
return containers2;
|
|
12699
12798
|
}
|
|
12700
12799
|
function proveRetainedManagedStop(state) {
|
|
12701
12800
|
const baseline = validateManagedStopBaseline(state.stopBaseline, state);
|
|
12702
|
-
if (!(0,
|
|
12801
|
+
if (!(0, import_node_util3.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state)) {
|
|
12703
12802
|
throw new Error("Retained stop generation changed.");
|
|
12704
12803
|
}
|
|
12705
12804
|
const result = prove(state, baseline);
|
|
12706
|
-
if (!(0,
|
|
12805
|
+
if (!(0, import_node_util3.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state)) {
|
|
12707
12806
|
throw new Error("Retained stop generation changed during inspection.");
|
|
12708
12807
|
}
|
|
12709
12808
|
return result;
|
|
@@ -12728,6 +12827,64 @@ function absentRegistrationIdentity(state) {
|
|
|
12728
12827
|
}
|
|
12729
12828
|
return { record: record5, gitCommonDir: resolveGitCommonDir(state.repoPath) };
|
|
12730
12829
|
}
|
|
12830
|
+
function replacementObservation(state) {
|
|
12831
|
+
resetWorkspaceRuntimeCaches();
|
|
12832
|
+
if (resolveWorkspaceRuntimeOrDefault(state.repoPath) !== "devsy")
|
|
12833
|
+
throw new Error("Stop provider changed.");
|
|
12834
|
+
if (!state.workspace || !isLinkedWorktree(state.repoPath))
|
|
12835
|
+
throw new Error("Replacement stop requires a linked workspace.");
|
|
12836
|
+
const record5 = readWorkspaceOwnership(state.repoPath, state.workspace);
|
|
12837
|
+
if (!record5 || record5.workspace !== state.workspace || record5.devpodId !== state.devpodId || !sameWorkspacePath(record5.worktreePath, state.repoPath) || resolveWorktreeWorkspace(state.repoPath) !== state.workspace || inspectWorkspaceOwnership(record5, listGitWorktrees(state.repoPath), void 0).ownerStatus !== "present")
|
|
12838
|
+
throw new Error("Replacement stop workspace ownership changed.");
|
|
12839
|
+
const owner = inspectDevsyWorkspaceOwnership(
|
|
12840
|
+
listDevsyWorkspaces(),
|
|
12841
|
+
state.devpodId,
|
|
12842
|
+
state.repoPath
|
|
12843
|
+
);
|
|
12844
|
+
if (owner.status !== "owned")
|
|
12845
|
+
throw new Error("Replacement stop requires one exact provider registration.");
|
|
12846
|
+
if (listDevpodWorkspacesRaw({ allowMissingExecutable: true }).some(
|
|
12847
|
+
(entry) => entry.id === state.devpodId || sameWorkspacePath(entry.source.localFolder, state.repoPath)
|
|
12848
|
+
))
|
|
12849
|
+
throw new Error("Replacement stop requires no competing provider registration.");
|
|
12850
|
+
proveLocalDockerSelection(owner.workspace);
|
|
12851
|
+
return owner.workspace;
|
|
12852
|
+
}
|
|
12853
|
+
function proveReplacementStopAbsence(state, baseline, replacement) {
|
|
12854
|
+
const observation = () => {
|
|
12855
|
+
const workspace = replacementObservation(state);
|
|
12856
|
+
if (workspace.uid !== replacement.uid)
|
|
12857
|
+
throw new Error("Replacement stop registration changed during inspection.");
|
|
12858
|
+
if (resolveManagedStopEndpoint() !== baseline.endpoint)
|
|
12859
|
+
throw new Error("Stop endpoint changed.");
|
|
12860
|
+
if (inspectManagedStopDaemon(baseline.endpoint) !== baseline.daemonId)
|
|
12861
|
+
throw new Error("Stop daemon changed.");
|
|
12862
|
+
return workspace;
|
|
12863
|
+
};
|
|
12864
|
+
const before = observation();
|
|
12865
|
+
if (!(0, import_node_util3.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state))
|
|
12866
|
+
throw new Error("Retained stop generation changed.");
|
|
12867
|
+
assertManagedStopContainersAbsent(
|
|
12868
|
+
baseline.endpoint,
|
|
12869
|
+
baseline.containers.map((container) => container.id)
|
|
12870
|
+
);
|
|
12871
|
+
const oldRunner = managedRunnerId(baseline.uid, baseline.providerId);
|
|
12872
|
+
const newRunner = managedRunnerId(before.uid, baseline.providerId);
|
|
12873
|
+
const runners = oldRunner === newRunner ? [oldRunner] : [oldRunner, newRunner];
|
|
12874
|
+
const assertEmptyPopulation = () => {
|
|
12875
|
+
if (inspectManagedStopContainers(baseline.project, baseline.endpoint).length !== 0 || inspectManagedStopWorkspaceIds(baseline.endpoint, baseline.composeDirectory).length !== 0 || runners.some((id4) => inspectProviderRunnerContainers(baseline.endpoint, id4).length !== 0))
|
|
12876
|
+
throw new Error("Replacement stop observed a remaining workspace population.");
|
|
12877
|
+
};
|
|
12878
|
+
assertEmptyPopulation();
|
|
12879
|
+
const after = observation();
|
|
12880
|
+
if (after.uid !== before.uid || !(0, import_node_util3.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state))
|
|
12881
|
+
throw new Error("Replacement stop authority changed during inspection.");
|
|
12882
|
+
assertManagedStopContainersAbsent(
|
|
12883
|
+
baseline.endpoint,
|
|
12884
|
+
baseline.containers.map((container) => container.id)
|
|
12885
|
+
);
|
|
12886
|
+
assertEmptyPopulation();
|
|
12887
|
+
}
|
|
12731
12888
|
function proveManagedStop(state) {
|
|
12732
12889
|
const baseline = validateManagedStopBaseline(state.stopBaseline, state);
|
|
12733
12890
|
const owner = inspectDevsyWorkspaceOwnership(
|
|
@@ -12735,11 +12892,20 @@ function proveManagedStop(state) {
|
|
|
12735
12892
|
state.devpodId,
|
|
12736
12893
|
state.repoPath
|
|
12737
12894
|
);
|
|
12895
|
+
if (owner.status === "owned") {
|
|
12896
|
+
const identityKeys = ["context", "providerId", "uid", "sourcePath", "sourceContainer"];
|
|
12897
|
+
const current = identityOf(owner.workspace);
|
|
12898
|
+
const changed = identityKeys.filter((key) => baseline[key] !== current[key]);
|
|
12899
|
+
if (changed.length === 1 && changed[0] === "uid" && !baseline.sourceContainer && !current.sourceContainer && state.workspace !== void 0 && isLinkedWorktree(state.repoPath)) {
|
|
12900
|
+
proveReplacementStopAbsence(state, baseline, { uid: current.uid || void 0 });
|
|
12901
|
+
return { status: "proven-absent", containers: [] };
|
|
12902
|
+
}
|
|
12903
|
+
}
|
|
12738
12904
|
if (owner.status !== "absent")
|
|
12739
12905
|
return { status: "retained", containers: proveRetainedManagedStop(state) };
|
|
12740
12906
|
if (baseline.sourceContainer) throw new Error("Absent stop cannot prove a source container.");
|
|
12741
12907
|
const before = absentRegistrationIdentity(state);
|
|
12742
|
-
if (!(0,
|
|
12908
|
+
if (!(0, import_node_util3.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state))
|
|
12743
12909
|
throw new Error("Retained stop generation changed.");
|
|
12744
12910
|
if (inspectManagedStopDaemon(baseline.endpoint) !== baseline.daemonId)
|
|
12745
12911
|
throw new Error("Stop daemon changed.");
|
|
@@ -12751,7 +12917,7 @@ function proveManagedStop(state) {
|
|
|
12751
12917
|
const runnerId = uidBytes === 16 || uidBytes === 40 ? baseline.uid : baseline.providerId;
|
|
12752
12918
|
if (inspectManagedStopContainers(baseline.project, baseline.endpoint).length !== 0 || inspectManagedStopWorkspaceIds(baseline.endpoint, baseline.composeDirectory).length !== 0 || inspectProviderRunnerContainers(baseline.endpoint, runnerId).length !== 0)
|
|
12753
12919
|
throw new Error("Absent stop observed a remaining workspace population.");
|
|
12754
|
-
if (inspectManagedStopDaemon(baseline.endpoint) !== baseline.daemonId || !(0,
|
|
12920
|
+
if (inspectManagedStopDaemon(baseline.endpoint) !== baseline.daemonId || !(0, import_node_util3.isDeepStrictEqual)(absentRegistrationIdentity(state), before) || !(0, import_node_util3.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state))
|
|
12755
12921
|
throw new Error("Absent stop authority changed during inspection.");
|
|
12756
12922
|
return { status: "proven-absent", containers: [] };
|
|
12757
12923
|
}
|
|
@@ -12787,15 +12953,16 @@ function managedStopRouteReferences(state) {
|
|
|
12787
12953
|
(name) => ["http", "tcp"].map((protocol) => ({ repoPath: state.repoPath, name, protocol }))
|
|
12788
12954
|
);
|
|
12789
12955
|
}
|
|
12790
|
-
var
|
|
12956
|
+
var import_node_os3, import_node_path35, import_node_util3;
|
|
12791
12957
|
var init_managed_stop_recovery = __esm({
|
|
12792
12958
|
"src/core/managed-stop-recovery.ts"() {
|
|
12793
12959
|
"use strict";
|
|
12794
|
-
|
|
12795
|
-
|
|
12796
|
-
|
|
12960
|
+
import_node_os3 = __toESM(require("os"));
|
|
12961
|
+
import_node_path35 = __toESM(require("path"));
|
|
12962
|
+
import_node_util3 = require("util");
|
|
12797
12963
|
init_devpod_environment();
|
|
12798
12964
|
init_devpod_registry();
|
|
12965
|
+
init_devsy_exec_proof();
|
|
12799
12966
|
init_devsy_workspaces();
|
|
12800
12967
|
init_managed_compose_population();
|
|
12801
12968
|
init_managed_runtime_state();
|
|
@@ -12929,8 +13096,8 @@ function stopRetainedManagedDevsyWorkspace(options) {
|
|
|
12929
13096
|
if (!context || !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(context) || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(devsyId)) {
|
|
12930
13097
|
throw new Error("Managed stop requires a valid exact Devsy context and workspace ID.");
|
|
12931
13098
|
}
|
|
12932
|
-
const devsyRoot =
|
|
12933
|
-
const featureDirectory =
|
|
13099
|
+
const devsyRoot = import_node_path36.default.resolve(process.env.DEVSY_HOME || import_node_path36.default.join(import_node_os4.default.homedir(), ".devsy"));
|
|
13100
|
+
const featureDirectory = import_node_path36.default.join(
|
|
12934
13101
|
devsyRoot,
|
|
12935
13102
|
"contexts",
|
|
12936
13103
|
context,
|
|
@@ -13034,12 +13201,12 @@ function stopRetainedManagedDevsyWorkspace(options) {
|
|
|
13034
13201
|
if (providerFailed) throw providerError;
|
|
13035
13202
|
return true;
|
|
13036
13203
|
}
|
|
13037
|
-
var
|
|
13204
|
+
var import_node_os4, import_node_path36;
|
|
13038
13205
|
var init_managed_devsy_stop = __esm({
|
|
13039
13206
|
"src/core/managed-devsy-stop.ts"() {
|
|
13040
13207
|
"use strict";
|
|
13041
|
-
|
|
13042
|
-
|
|
13208
|
+
import_node_os4 = __toESM(require("os"));
|
|
13209
|
+
import_node_path36 = __toESM(require("path"));
|
|
13043
13210
|
init_devcontainer_profile();
|
|
13044
13211
|
init_devpod_environment();
|
|
13045
13212
|
init_devsy_workspaces();
|
|
@@ -13082,14 +13249,14 @@ var init_network_provider_binding = __esm({
|
|
|
13082
13249
|
|
|
13083
13250
|
// src/core/network-claim-lookup.ts
|
|
13084
13251
|
function networkOwnerKey(repoPath) {
|
|
13085
|
-
if (!
|
|
13252
|
+
if (!import_node_path37.default.isAbsolute(repoPath) || import_node_path37.default.resolve(repoPath) !== repoPath)
|
|
13086
13253
|
throw new Error("Invalid network owner path.");
|
|
13087
13254
|
return (0, import_node_crypto18.createHash)("sha256").update(repoPath).digest("hex");
|
|
13088
13255
|
}
|
|
13089
13256
|
function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
|
|
13090
13257
|
let names;
|
|
13091
13258
|
try {
|
|
13092
|
-
names =
|
|
13259
|
+
names = import_node_fs31.default.readdirSync(root);
|
|
13093
13260
|
} catch (error) {
|
|
13094
13261
|
if (error.code === "ENOENT") return void 0;
|
|
13095
13262
|
throw new Error("Network claims cannot be inspected; repair is required.");
|
|
@@ -13098,21 +13265,21 @@ function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
|
|
|
13098
13265
|
if (files.length > 128) throw new Error("Network daemon inventory exceeds its bound.");
|
|
13099
13266
|
let found;
|
|
13100
13267
|
for (const file of files) {
|
|
13101
|
-
const full =
|
|
13102
|
-
const stat =
|
|
13268
|
+
const full = import_node_path37.default.join(root, file);
|
|
13269
|
+
const stat = import_node_fs31.default.lstatSync(full);
|
|
13103
13270
|
if (!stat.isFile() || stat.size > NETWORK_CLAIMS_MAX_FILE_BYTES)
|
|
13104
13271
|
throw new Error("Invalid retained network claim file.");
|
|
13105
13272
|
let value;
|
|
13106
|
-
const descriptor =
|
|
13273
|
+
const descriptor = import_node_fs31.default.openSync(
|
|
13107
13274
|
full,
|
|
13108
|
-
|
|
13275
|
+
import_node_fs31.default.constants.O_RDONLY | import_node_fs31.default.constants.O_NONBLOCK | import_node_fs31.default.constants.O_NOFOLLOW
|
|
13109
13276
|
);
|
|
13110
13277
|
try {
|
|
13111
|
-
if (!
|
|
13278
|
+
if (!import_node_fs31.default.fstatSync(descriptor).isFile()) throw new Error();
|
|
13112
13279
|
const bytes = Buffer.alloc(NETWORK_CLAIMS_MAX_FILE_BYTES + 1);
|
|
13113
13280
|
let size = 0;
|
|
13114
13281
|
while (size < bytes.length) {
|
|
13115
|
-
const count =
|
|
13282
|
+
const count = import_node_fs31.default.readSync(descriptor, bytes, size, bytes.length - size, null);
|
|
13116
13283
|
if (!count) break;
|
|
13117
13284
|
size += count;
|
|
13118
13285
|
}
|
|
@@ -13122,7 +13289,7 @@ function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
|
|
|
13122
13289
|
} catch {
|
|
13123
13290
|
throw new Error("Retained network claim could not be read safely.");
|
|
13124
13291
|
} finally {
|
|
13125
|
-
|
|
13292
|
+
import_node_fs31.default.closeSync(descriptor);
|
|
13126
13293
|
}
|
|
13127
13294
|
if (typeof value.daemonId !== "string" || `${(0, import_node_crypto18.createHash)("sha256").update(value.daemonId).digest("hex")}.json` !== file)
|
|
13128
13295
|
throw new Error("Retained network daemon identity is invalid.");
|
|
@@ -13138,13 +13305,13 @@ function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
|
|
|
13138
13305
|
}
|
|
13139
13306
|
return found;
|
|
13140
13307
|
}
|
|
13141
|
-
var import_node_crypto18,
|
|
13308
|
+
var import_node_crypto18, import_node_fs31, import_node_path37;
|
|
13142
13309
|
var init_network_claim_lookup = __esm({
|
|
13143
13310
|
"src/core/network-claim-lookup.ts"() {
|
|
13144
13311
|
"use strict";
|
|
13145
13312
|
import_node_crypto18 = require("crypto");
|
|
13146
|
-
|
|
13147
|
-
|
|
13313
|
+
import_node_fs31 = __toESM(require("fs"));
|
|
13314
|
+
import_node_path37 = __toESM(require("path"));
|
|
13148
13315
|
init_network_claims();
|
|
13149
13316
|
}
|
|
13150
13317
|
});
|
|
@@ -13182,7 +13349,7 @@ var init_network_provider_effect = __esm({
|
|
|
13182
13349
|
|
|
13183
13350
|
// src/core/devsy-mutation.ts
|
|
13184
13351
|
function withMutationLock(activity, target, operation) {
|
|
13185
|
-
|
|
13352
|
+
import_node_fs32.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
13186
13353
|
return withFileLockSync(
|
|
13187
13354
|
DEVSY_MUTATION_LOCK_FILE,
|
|
13188
13355
|
{
|
|
@@ -13263,13 +13430,13 @@ function stopOwnedDevsyWorkspace(devsyId, worktreePath) {
|
|
|
13263
13430
|
function deleteOwnedDevsyWorkspace(devsyId, worktreePath) {
|
|
13264
13431
|
return mutateOwnedDevsyWorkspace("delete", devsyId, worktreePath);
|
|
13265
13432
|
}
|
|
13266
|
-
var import_node_child_process21,
|
|
13433
|
+
var import_node_child_process21, import_node_fs32, import_node_path38, DEVSY_MUTATION_LOCK_FILE, DEVSY_MUTATION_WAIT_MS;
|
|
13267
13434
|
var init_devsy_mutation = __esm({
|
|
13268
13435
|
"src/core/devsy-mutation.ts"() {
|
|
13269
13436
|
"use strict";
|
|
13270
13437
|
import_node_child_process21 = require("child_process");
|
|
13271
|
-
|
|
13272
|
-
|
|
13438
|
+
import_node_fs32 = __toESM(require("fs"));
|
|
13439
|
+
import_node_path38 = __toESM(require("path"));
|
|
13273
13440
|
init_devsy_agent();
|
|
13274
13441
|
init_devsy_workspaces();
|
|
13275
13442
|
init_file_lock();
|
|
@@ -13278,14 +13445,14 @@ var init_devsy_mutation = __esm({
|
|
|
13278
13445
|
init_network_provider_effect();
|
|
13279
13446
|
init_reliability_context();
|
|
13280
13447
|
init_router();
|
|
13281
|
-
DEVSY_MUTATION_LOCK_FILE =
|
|
13448
|
+
DEVSY_MUTATION_LOCK_FILE = import_node_path38.default.join(DEVROUTER_HOME, "devsy-mutation.lock");
|
|
13282
13449
|
DEVSY_MUTATION_WAIT_MS = 18e5;
|
|
13283
13450
|
}
|
|
13284
13451
|
});
|
|
13285
13452
|
|
|
13286
13453
|
// src/core/devpod-mutation.ts
|
|
13287
13454
|
function withMutationLock2(activity, target, operation) {
|
|
13288
|
-
|
|
13455
|
+
import_node_fs33.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
13289
13456
|
return withFileLockSync(
|
|
13290
13457
|
DEVPOD_MUTATION_LOCK_FILE,
|
|
13291
13458
|
{
|
|
@@ -13375,13 +13542,13 @@ function deleteOwnedDevpodWorkspace(devpodId, worktreePath) {
|
|
|
13375
13542
|
resetWorkspaceRuntimeCaches();
|
|
13376
13543
|
return result;
|
|
13377
13544
|
}
|
|
13378
|
-
var import_node_child_process22,
|
|
13545
|
+
var import_node_child_process22, import_node_fs33, import_node_path39, DEVPOD_MUTATION_LOCK_FILE, DEVPOD_MUTATION_WAIT_MS;
|
|
13379
13546
|
var init_devpod_mutation = __esm({
|
|
13380
13547
|
"src/core/devpod-mutation.ts"() {
|
|
13381
13548
|
"use strict";
|
|
13382
13549
|
import_node_child_process22 = require("child_process");
|
|
13383
|
-
|
|
13384
|
-
|
|
13550
|
+
import_node_fs33 = __toESM(require("fs"));
|
|
13551
|
+
import_node_path39 = __toESM(require("path"));
|
|
13385
13552
|
init_devpod_workspaces();
|
|
13386
13553
|
init_devsy_mutation();
|
|
13387
13554
|
init_file_lock();
|
|
@@ -13390,7 +13557,7 @@ var init_devpod_mutation = __esm({
|
|
|
13390
13557
|
init_reliability_context();
|
|
13391
13558
|
init_router();
|
|
13392
13559
|
init_workspace_runtime();
|
|
13393
|
-
DEVPOD_MUTATION_LOCK_FILE =
|
|
13560
|
+
DEVPOD_MUTATION_LOCK_FILE = import_node_path39.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
|
|
13394
13561
|
DEVPOD_MUTATION_WAIT_MS = 18e5;
|
|
13395
13562
|
}
|
|
13396
13563
|
});
|
|
@@ -13405,10 +13572,10 @@ function inRepositoryWorkspaceScope(repoPath, worktreePath, livePaths) {
|
|
|
13405
13572
|
if (livePaths.some((candidate) => sameWorkspacePath(candidate, worktreePath))) return true;
|
|
13406
13573
|
const comparableRepo = comparableWorkspacePath(repoPath);
|
|
13407
13574
|
const comparableWorktree = comparableWorkspacePath(worktreePath);
|
|
13408
|
-
const localRoot =
|
|
13575
|
+
const localRoot = import_node_path40.default.join(comparableRepo, "trees") + import_node_path40.default.sep;
|
|
13409
13576
|
if (comparableWorktree.startsWith(localRoot)) return true;
|
|
13410
|
-
const legacyPrefix = `${
|
|
13411
|
-
return
|
|
13577
|
+
const legacyPrefix = `${import_node_path40.default.basename(comparableRepo)}-`;
|
|
13578
|
+
return import_node_path40.default.dirname(comparableWorktree) === import_node_path40.default.dirname(comparableRepo) && import_node_path40.default.basename(comparableWorktree).startsWith(legacyPrefix);
|
|
13412
13579
|
}
|
|
13413
13580
|
function previewActions(devpodStatus, routeCount, includeRecord) {
|
|
13414
13581
|
const actions = [
|
|
@@ -13660,11 +13827,11 @@ function applyWorkspaceGc(plan) {
|
|
|
13660
13827
|
candidates
|
|
13661
13828
|
};
|
|
13662
13829
|
}
|
|
13663
|
-
var
|
|
13830
|
+
var import_node_path40;
|
|
13664
13831
|
var init_workspace_gc = __esm({
|
|
13665
13832
|
"src/core/workspace-gc.ts"() {
|
|
13666
13833
|
"use strict";
|
|
13667
|
-
|
|
13834
|
+
import_node_path40 = __toESM(require("path"));
|
|
13668
13835
|
init_devpod_mutation();
|
|
13669
13836
|
init_devpod_workspaces();
|
|
13670
13837
|
init_host_routes();
|
|
@@ -13734,11 +13901,11 @@ function inspectPostgresCredentials(repoPath, config) {
|
|
|
13734
13901
|
parseErrors.push(`${app.name}: ${composeFile} (${message})`);
|
|
13735
13902
|
continue;
|
|
13736
13903
|
}
|
|
13737
|
-
if (!
|
|
13904
|
+
if (!import_node_fs34.default.existsSync(absolutePath)) {
|
|
13738
13905
|
continue;
|
|
13739
13906
|
}
|
|
13740
13907
|
try {
|
|
13741
|
-
const raw =
|
|
13908
|
+
const raw = import_node_fs34.default.readFileSync(absolutePath, "utf-8");
|
|
13742
13909
|
const parsed = import_yaml5.default.parse(raw);
|
|
13743
13910
|
const root = asRecord2(parsed);
|
|
13744
13911
|
const services = asRecord2(root?.services);
|
|
@@ -14018,7 +14185,7 @@ async function buildDoctorReport(options = {}) {
|
|
|
14018
14185
|
const config = runtimeConfig.config;
|
|
14019
14186
|
loadedConfig = config;
|
|
14020
14187
|
loadedWorkspace = runtimeConfig.workspace;
|
|
14021
|
-
const cliVersion = true ? "0.0.
|
|
14188
|
+
const cliVersion = true ? "0.0.66" : "0.0.0-dev";
|
|
14022
14189
|
const configVersion = config.devrouter?.version;
|
|
14023
14190
|
if (configVersion && cliVersion !== "0.0.0-dev" && compareSemver(configVersion, cliVersion) > 0) {
|
|
14024
14191
|
addCheck(checks, {
|
|
@@ -14048,9 +14215,9 @@ async function buildDoctorReport(options = {}) {
|
|
|
14048
14215
|
(app) => app.docker.composeFiles.map((filePath) => ({
|
|
14049
14216
|
app: app.name,
|
|
14050
14217
|
filePath,
|
|
14051
|
-
absolutePath:
|
|
14218
|
+
absolutePath: import_node_path41.default.resolve(repo.path, filePath)
|
|
14052
14219
|
}))
|
|
14053
|
-
).filter((entry) => !
|
|
14220
|
+
).filter((entry) => !import_node_fs34.default.existsSync(entry.absolutePath));
|
|
14054
14221
|
addCheck(checks, {
|
|
14055
14222
|
id: "repo.compose-files",
|
|
14056
14223
|
level: missingComposeFiles.length === 0 ? "ok" : "error",
|
|
@@ -14093,8 +14260,8 @@ async function buildDoctorReport(options = {}) {
|
|
|
14093
14260
|
const missingHostCwds = config.apps.filter((app) => app.runtime === "host").map((app) => ({
|
|
14094
14261
|
app: app.name,
|
|
14095
14262
|
cwd: app.hostRun.cwd,
|
|
14096
|
-
absolutePath:
|
|
14097
|
-
})).filter((entry) => !
|
|
14263
|
+
absolutePath: import_node_path41.default.resolve(repo.path, app.hostRun.cwd)
|
|
14264
|
+
})).filter((entry) => !import_node_fs34.default.existsSync(entry.absolutePath));
|
|
14098
14265
|
addCheck(checks, {
|
|
14099
14266
|
id: "repo.host-cwd",
|
|
14100
14267
|
level: missingHostCwds.length === 0 ? "ok" : "error",
|
|
@@ -14238,12 +14405,12 @@ async function buildDoctorReport(options = {}) {
|
|
|
14238
14405
|
nextSteps
|
|
14239
14406
|
};
|
|
14240
14407
|
}
|
|
14241
|
-
var
|
|
14408
|
+
var import_node_fs34, import_node_path41, import_yaml5, POSTGRES_DEFAULTS;
|
|
14242
14409
|
var init_doctor = __esm({
|
|
14243
14410
|
"src/core/doctor.ts"() {
|
|
14244
14411
|
"use strict";
|
|
14245
|
-
|
|
14246
|
-
|
|
14412
|
+
import_node_fs34 = __toESM(require("fs"));
|
|
14413
|
+
import_node_path41 = __toESM(require("path"));
|
|
14247
14414
|
import_yaml5 = __toESM(require("yaml"));
|
|
14248
14415
|
init_docker();
|
|
14249
14416
|
init_host_routes();
|
|
@@ -14652,96 +14819,6 @@ var init_up = __esm({
|
|
|
14652
14819
|
}
|
|
14653
14820
|
});
|
|
14654
14821
|
|
|
14655
|
-
// src/core/devsy-exec-proof.ts
|
|
14656
|
-
function proveLocalDockerSelection(workspace) {
|
|
14657
|
-
const name = workspace.providerName;
|
|
14658
|
-
const context = workspace.context;
|
|
14659
|
-
if (!name || !context || !/^[a-z0-9][a-z0-9-]{0,31}$/.test(name) || !/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(context))
|
|
14660
|
-
throw new Error("Retained exec provider configuration identity is unavailable.");
|
|
14661
|
-
const file = import_node_path41.default.join(
|
|
14662
|
-
process.env.DEVSY_HOME || import_node_path41.default.join(import_node_os4.default.homedir(), ".devsy"),
|
|
14663
|
-
"contexts",
|
|
14664
|
-
context,
|
|
14665
|
-
"providers",
|
|
14666
|
-
name,
|
|
14667
|
-
"provider.json"
|
|
14668
|
-
);
|
|
14669
|
-
try {
|
|
14670
|
-
if (import_node_fs34.default.statSync(file).size > 1024 * 1024) throw new Error();
|
|
14671
|
-
const config = JSON.parse(import_node_fs34.default.readFileSync(file, "utf8"));
|
|
14672
|
-
const driver = config.agent?.driver ?? "docker";
|
|
14673
|
-
const configuredPath = config.agent?.docker?.path ?? "";
|
|
14674
|
-
if (typeof configuredPath !== "string") throw new Error();
|
|
14675
|
-
const command = configuredPath === "${DOCKER_PATH}" || configuredPath === "$DOCKER_PATH" ? workspace.dockerPathOption || process.env.DOCKER_PATH || "docker" : configuredPath || "docker";
|
|
14676
|
-
if (config.name !== name || driver !== "docker" || command !== "docker") throw new Error();
|
|
14677
|
-
} catch {
|
|
14678
|
-
throw new Error("Retained exec requires a provider configured for the local Docker command.");
|
|
14679
|
-
}
|
|
14680
|
-
return name;
|
|
14681
|
-
}
|
|
14682
|
-
function captureDevsyExecProof(repoPath) {
|
|
14683
|
-
repoPath = comparableWorkspacePath(repoPath);
|
|
14684
|
-
const registry = listDevsyWorkspaces();
|
|
14685
|
-
const workspace = selectDevsyWorkspace(registry, repoPath);
|
|
14686
|
-
if (!workspace?.uid || !workspace.context || workspace.source.container || inspectDevsyWorkspaceOwnership(registry, workspace.id, repoPath).status !== "owned" || inspectDevsyRuntimeStatus(workspace.id) !== "running")
|
|
14687
|
-
throw new Error("Retained exec requires one exact running Devsy registration.");
|
|
14688
|
-
const providerName = proveLocalDockerSelection(workspace);
|
|
14689
|
-
const endpoint = resolveManagedStopEndpoint();
|
|
14690
|
-
if (!supportsManagedStopBaseline(endpoint))
|
|
14691
|
-
throw new Error("Retained exec requires an exact local Docker endpoint.");
|
|
14692
|
-
const daemon = inspectManagedStopDaemon(endpoint);
|
|
14693
|
-
const candidates = workspaceAppContainers(inspectWorkspaceContainers(), repoPath).filter(
|
|
14694
|
-
(container) => container.state.Running
|
|
14695
|
-
);
|
|
14696
|
-
if (candidates.length !== 1)
|
|
14697
|
-
throw new Error("Retained exec requires one exact running primary container.");
|
|
14698
|
-
const candidate = candidates[0];
|
|
14699
|
-
const project = candidate.labels["com.docker.compose.project"];
|
|
14700
|
-
if (!project) throw new Error("Retained exec primary has no Compose identity.");
|
|
14701
|
-
const pinned = workspaceAppContainers(
|
|
14702
|
-
inspectManagedStopContainers(project, endpoint),
|
|
14703
|
-
repoPath
|
|
14704
|
-
).filter((container) => container.state.Running);
|
|
14705
|
-
if (pinned.length !== 1 || pinned[0].id !== candidate.id)
|
|
14706
|
-
throw new Error("Retained exec primary changed during inspection.");
|
|
14707
|
-
const mounts = pinned[0].mounts.filter(
|
|
14708
|
-
(mount) => mount.Type === "bind" && sameWorkspacePath(mount.Source, repoPath)
|
|
14709
|
-
);
|
|
14710
|
-
if (mounts.length !== 1 || !mounts[0].Destination.startsWith("/"))
|
|
14711
|
-
throw new Error("Retained exec requires one exact source mount.");
|
|
14712
|
-
const uidBytes = Buffer.byteLength(workspace.uid);
|
|
14713
|
-
const runner = uidBytes === 16 || uidBytes === 40 ? workspace.uid : workspace.id;
|
|
14714
|
-
const runnerContainers = inspectProviderRunnerContainers(endpoint, runner);
|
|
14715
|
-
if (runnerContainers.length !== 1 || runnerContainers[0] !== candidate.id)
|
|
14716
|
-
throw new Error("Retained exec runner does not select the exact primary container.");
|
|
14717
|
-
if (inspectManagedStopRunnerId(endpoint, candidate.id) !== runner || inspectManagedStopDaemon(endpoint) !== daemon)
|
|
14718
|
-
throw new Error("Retained exec provider or Docker identity changed.");
|
|
14719
|
-
return {
|
|
14720
|
-
repoPath,
|
|
14721
|
-
id: workspace.id,
|
|
14722
|
-
uid: workspace.uid,
|
|
14723
|
-
context: workspace.context,
|
|
14724
|
-
providerName,
|
|
14725
|
-
endpoint,
|
|
14726
|
-
daemon,
|
|
14727
|
-
containerId: candidate.id,
|
|
14728
|
-
workspacePath: mounts[0].Destination
|
|
14729
|
-
};
|
|
14730
|
-
}
|
|
14731
|
-
var import_node_fs34, import_node_os4, import_node_path41, import_node_util3;
|
|
14732
|
-
var init_devsy_exec_proof = __esm({
|
|
14733
|
-
"src/core/devsy-exec-proof.ts"() {
|
|
14734
|
-
"use strict";
|
|
14735
|
-
import_node_fs34 = __toESM(require("fs"));
|
|
14736
|
-
import_node_os4 = __toESM(require("os"));
|
|
14737
|
-
import_node_path41 = __toESM(require("path"));
|
|
14738
|
-
import_node_util3 = require("util");
|
|
14739
|
-
init_devpod_environment();
|
|
14740
|
-
init_devsy_workspaces();
|
|
14741
|
-
init_workspace();
|
|
14742
|
-
}
|
|
14743
|
-
});
|
|
14744
|
-
|
|
14745
14822
|
// src/core/reliability-model.ts
|
|
14746
14823
|
function record4(value) {
|
|
14747
14824
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -20862,7 +20939,7 @@ var init_version = __esm({
|
|
|
20862
20939
|
|
|
20863
20940
|
// src/cli.ts
|
|
20864
20941
|
var import_commander = require("commander");
|
|
20865
|
-
var CLI_VERSION = true ? "0.0.
|
|
20942
|
+
var CLI_VERSION = true ? "0.0.66" : "0.0.0-dev";
|
|
20866
20943
|
var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
|
|
20867
20944
|
function withErrorHandling(action2) {
|
|
20868
20945
|
return async (...args) => {
|
package/package.json
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Upgrade to devrouter 0.0.66
|
|
2
|
+
|
|
3
|
+
This patch release completes managed stop when Devsy replaces a workspace
|
|
4
|
+
registration UID after the workload is positively absent, releasing lifecycle
|
|
5
|
+
journals stuck in `stopping` so a later `ensure` is admitted again.
|
|
6
|
+
|
|
7
|
+
## Task
|
|
8
|
+
|
|
9
|
+
Install the published release and update `.devrouter.yml` devrouter.version to
|
|
10
|
+
0.0.66. No schema migration or configuration change is required. Do not change
|
|
11
|
+
the committed configuration, delete registrations, containers, or source files,
|
|
12
|
+
and do not reset lifecycle state manually: the recovery runs inside the existing
|
|
13
|
+
canonical stop command under its normal locks and proofs.
|
|
14
|
+
|
|
15
|
+
## Validation
|
|
16
|
+
|
|
17
|
+
On a workspace whose stop was previously rejected with a retained provider
|
|
18
|
+
identity error after a registration replacement, run the canonical
|
|
19
|
+
`devrouter stop <path>` and confirm it reports the workspace stopped with the
|
|
20
|
+
absence proof, then run `devrouter ensure <path>` and confirm admission
|
|
21
|
+
succeeds. Confirm `devrouter status --json` shows the expected ready state and
|
|
22
|
+
that no containers, volumes, or source files were removed beyond the normal
|
|
23
|
+
stop contract. For workspaces that never hit the replacement case, confirm
|
|
24
|
+
existing stop, ensure, and `devrouter doctor` behavior is unchanged.
|
|
25
|
+
|
|
26
|
+
## Report
|
|
27
|
+
|
|
28
|
+
Report the tested CLI version, the exact workspace path or synthetic fixture,
|
|
29
|
+
the stop command result, the ensure result, route state after the run, and any
|
|
30
|
+
diagnostic failure output verbatim. State explicitly whether the replacement
|
|
31
|
+
case was exercised or only the unchanged retained path.
|