@devrouter/cli 0.0.44 → 0.0.46
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.js +668 -249
- package/package.json +1 -1
- package/upgrade-prompts/0.0.45.md +33 -0
- package/upgrade-prompts/0.0.46.md +41 -0
package/dist/devrouter.js
CHANGED
|
@@ -316,13 +316,13 @@ Config-level \`envMap\` on dependency references aliases per-dep vars to app-exp
|
|
|
316
316
|
|
|
317
317
|
## Workspace isolation (parallel git worktrees / agents)
|
|
318
318
|
|
|
319
|
-
Run several worktrees of one repo in parallel without host/route collisions. A **workspace token** spans the
|
|
319
|
+
Run several worktrees of one repo in parallel without host/route collisions. A **workspace token** spans the workspace-runtime id, devrouter routes, \`\${WORKSPACE}\` proxy upstreams, and devcontainer aliases.
|
|
320
320
|
|
|
321
|
-
- **Identity**: each managed linked worktree stores a local token in Git metadata plus a durable owner record in the repository's Git common directory. The record survives linked-worktree removal and binds the exact path to its
|
|
321
|
+
- **Identity**: each managed linked worktree stores a local token in Git metadata plus a durable owner record in the repository's Git common directory. The record survives linked-worktree removal and binds the exact path to its workspace-runtime ID. First use reconciles persisted metadata, the exact-path owner record, and both DevPod and Devsy registries. It reuses an established agreement, keeps the readable sanitized branch/path slug when free, or claims a deterministic hash-suffixed fallback on collision before provider or route mutation. Later flags or \`DEVROUTER_WORKSPACE\` may repeat the identity but cannot rename it. Unreadable or conflicting evidence fails closed. The primary checkout remains non-namespaced.
|
|
322
322
|
- **When active**: hosts auto-namespace (\`web.localhost\` \u2192 \`web.<ws>.localhost\`), \`\${WORKSPACE}\` in \`upstream\` is substituted with the token, and the docker \`router\` key is suffixed per workspace. Managed \`ensure\` rejects every HTTP/TCP proxy upstream outside that exact alias namespace before it mutates DevPod or routes. The runtime config is computed in memory only \u2014 the committed \`.devrouter.yml\` is never rewritten.
|
|
323
323
|
- **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.
|
|
324
324
|
- **devcontainer integration**: managed scaffolds list the base compose file, then \`\${localEnv:DEVCONTAINER_COMPOSE_OVERLAY:docker-compose.default.yml}\`; custom repositories may keep another default overlay. Selecting \`.devcontainer/docker-compose.devrouter.yml\` for linked worktrees must pass \`WORKSPACE\` and \`DEVROUTER_WORKSPACE\` across the combined base/overlay config and bind-mount \`\${DEVROUTER_GIT_COMMON_DIR}\` to the same absolute app-container path. The app exposes \`\${WORKSPACE}-app\`; the proxy uses \`upstream: \${WORKSPACE}-app:<port>\`.
|
|
325
|
-
- **Lifecycle**: after one-time \`setup\`, use \`ensure .\` for both primary and linked checkouts; never branch manually on checkout kind or use live verify as startup. \`stop .\` is non-destructive; \`stop . --delete\` is explicit exact-owner cleanup without worktree removal; and \`exec . -- <command...>\` runs one-shot commands only in the exact running workspace runtime (DevPod or Devsy). Never substitute raw \`devpod up\`, \`stop\`, \`delete\`, or the Devsy equivalents: they bypass devrouter's machine-global ownership lock. Runtime selection is path-aware: \`DEVROUTER_WORKSPACE_RUNTIME=devpod|devsy\` forces one runtime, an exact-path registry owner wins next, then the machine preference from \`devrouter setup --workspace-runtime\`, then installed-CLI auto-detection. \`workspace up\` creates linked worktrees; destructive worktree removal and GC remain ledger-scoped. Dirty or locked full down fails before side effects.
|
|
325
|
+
- **Lifecycle**: after one-time \`setup\`, use \`ensure .\` for both primary and linked checkouts; never branch manually on checkout kind or use live verify as startup. \`stop .\` is non-destructive; \`stop . --delete\` is explicit exact-owner cleanup without worktree removal; and \`exec . -- <command...>\` runs one-shot commands only in the exact running workspace runtime (DevPod or Devsy). Never substitute raw \`devpod up\`, \`stop\`, \`delete\`, or the Devsy equivalents: they bypass devrouter's 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. Runtime selection is path-aware: \`DEVROUTER_WORKSPACE_RUNTIME=devpod|devsy\` forces one runtime, an exact-path registry owner wins next, then the machine preference from \`devrouter setup --workspace-runtime\`, then installed-CLI auto-detection. \`workspace up\` creates linked worktrees; destructive worktree removal and GC remain ledger-scoped. Dirty or locked full down fails before side effects. A failed Devsy start that reports the agent-binary acquisition failure names the \`DEVSY_AGENT_BINARY\` remediation.
|
|
326
326
|
- **Managed process identity**: \`ensure\` executes an exact captured adapter snapshot. Default reuse includes command argv, workspace, and adapter SHA-256. Set \`DEVROUTER_PROCESS_FINGERPRINT_ENV\` only to comma-separated non-secret environment names whose values affect runtime identity; secret-like names are rejected and raw values are never persisted.
|
|
327
327
|
- **Route state**: the versioned Traefik dynamic file is authoritative for both metadata and rendering. JSON is a compatibility mirror; valid headerless generations migrate automatically, while corrupt canonical metadata fails closed.
|
|
328
328
|
- **Cleanup**: \`workspace cleanup --repo . --inactive-for 30d --json\` is a report-only, no-\`--yes\` report for managed linked workspaces. It joins ownership (\`present|missing|locked|conflict\`), workspace runtime registration, runtime state (\`running|stopped|busy|not-found|absent|unknown\`), checkout, route, advisory activity, and integration evidence without mutating the workspace runtime, routes, ownership, Git, Docker, applications, worktrees, or branches. Local DevPod/Devsy list/status checks always run; \`--check-merged\` alone enables read-only origin and matching GitHub/GitLab checks. Treat \`not-found\` as stale runtime after Docker pruning; busy, unavailable, or conflicting evidence suppresses destructive suggestions. Explicit \`gc\`/\`down\` can remove exact stale registration only after expected-ID \`NotFound\` proof and ownership revalidation. GC never removes Git worktrees, branches, or prune state. Git has no worktree-removal hook.
|
|
@@ -898,16 +898,65 @@ function parseLockOwner(value) {
|
|
|
898
898
|
return { pid };
|
|
899
899
|
}
|
|
900
900
|
}
|
|
901
|
-
function
|
|
902
|
-
|
|
901
|
+
function parseCanonicalLockOwner(value) {
|
|
902
|
+
const trimmed = value.trim();
|
|
903
|
+
return CANONICAL_OWNER_RE.test(trimmed) ? parseLockOwner(trimmed) : void 0;
|
|
904
|
+
}
|
|
905
|
+
function parseLockRecord(value) {
|
|
906
|
+
const trimmed = value.trim();
|
|
907
|
+
const lastColon = trimmed.lastIndexOf(":");
|
|
908
|
+
if (lastColon >= 0 && CANONICAL_OWNER_RE.test(trimmed.slice(0, lastColon))) {
|
|
909
|
+
const tail = Number(trimmed.slice(lastColon + 1));
|
|
910
|
+
if (Number.isInteger(tail) && tail > 0) {
|
|
911
|
+
return { owner: parseLockOwner(trimmed.slice(0, lastColon)), acquiredAtMs: tail };
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
return { owner: parseLockOwner(trimmed) };
|
|
915
|
+
}
|
|
916
|
+
function isLockOwnerLive(owner, cache, now = Date.now()) {
|
|
917
|
+
const cacheKey = `${owner.pid}:${owner.processBirth ?? "legacy"}`;
|
|
918
|
+
if (!isProcessAlive(owner.pid)) {
|
|
919
|
+
cache.delete(cacheKey);
|
|
920
|
+
return false;
|
|
921
|
+
}
|
|
903
922
|
if (!owner.processBirth) return true;
|
|
923
|
+
const cached = cache.get(cacheKey);
|
|
924
|
+
if (cached && now - cached.checkedAtMs < PROCESS_BIRTH_RECHECK_INTERVAL_MS) {
|
|
925
|
+
return cached.live;
|
|
926
|
+
}
|
|
904
927
|
const currentBirth = processBirthIdentity(owner.pid);
|
|
905
|
-
|
|
928
|
+
const live = currentBirth === void 0 || currentBirth === owner.processBirth;
|
|
929
|
+
cache.set(cacheKey, { checkedAtMs: now, live });
|
|
930
|
+
return live;
|
|
906
931
|
}
|
|
907
932
|
function sameFile(left, right) {
|
|
908
933
|
return left.dev === right.dev && left.ino === right.ino;
|
|
909
934
|
}
|
|
910
|
-
function
|
|
935
|
+
function inspectFairQueue(lockPath, ownTicketPath, livenessCache) {
|
|
936
|
+
const directory = import_node_path4.default.dirname(lockPath);
|
|
937
|
+
const prefix = `${import_node_path4.default.basename(lockPath)}.queue.`;
|
|
938
|
+
for (; ; ) {
|
|
939
|
+
const names = import_node_fs4.default.readdirSync(directory).filter((name) => name.startsWith(prefix)).sort();
|
|
940
|
+
const ownName = import_node_path4.default.basename(ownTicketPath);
|
|
941
|
+
const position = names.indexOf(ownName);
|
|
942
|
+
if (position < 0) {
|
|
943
|
+
throw new Error("provider mutation queue ticket disappeared before acquisition");
|
|
944
|
+
}
|
|
945
|
+
const leaderPath = import_node_path4.default.join(directory, names[0]);
|
|
946
|
+
let leader;
|
|
947
|
+
try {
|
|
948
|
+
leader = parseCanonicalLockOwner(import_node_fs4.default.readFileSync(leaderPath, "utf-8"));
|
|
949
|
+
} catch (error) {
|
|
950
|
+
if (error.code === "ENOENT") continue;
|
|
951
|
+
throw error;
|
|
952
|
+
}
|
|
953
|
+
if (leader && isLockOwnerLive(leader, livenessCache)) {
|
|
954
|
+
return { leaderPid: leader.pid, position: position + 1 };
|
|
955
|
+
}
|
|
956
|
+
import_node_fs4.default.rmSync(leaderPath, { force: true });
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
function tryReclaimStaleLock(lockPath, staleLinkPath, livenessCache) {
|
|
911
960
|
let fd;
|
|
912
961
|
try {
|
|
913
962
|
fd = import_node_fs4.default.openSync(lockPath, "r");
|
|
@@ -918,9 +967,9 @@ function tryReclaimStaleLock(lockPath, staleLinkPath) {
|
|
|
918
967
|
throw error;
|
|
919
968
|
}
|
|
920
969
|
try {
|
|
921
|
-
const
|
|
922
|
-
if (owner && isLockOwnerLive(owner)) {
|
|
923
|
-
return { kind: "live", pid: owner.pid };
|
|
970
|
+
const record = parseLockRecord(import_node_fs4.default.readFileSync(fd, "utf-8"));
|
|
971
|
+
if (record.owner && isLockOwnerLive(record.owner, livenessCache)) {
|
|
972
|
+
return { kind: "live", pid: record.owner.pid, acquiredAtMs: record.acquiredAtMs };
|
|
924
973
|
}
|
|
925
974
|
const staleStat = import_node_fs4.default.fstatSync(fd);
|
|
926
975
|
if (staleStat.nlink !== 1) {
|
|
@@ -965,28 +1014,87 @@ function acquireFileLock(lockPath, options) {
|
|
|
965
1014
|
const owner = `${process.pid}:${Buffer.from(processBirth).toString("base64url")}:${ownerId}`;
|
|
966
1015
|
const candidatePath = `${lockPath}.${process.pid}.${ownerId}.candidate`;
|
|
967
1016
|
const staleLinkPath = `${candidatePath}.stale`;
|
|
1017
|
+
const enqueuedAtMs = Date.now();
|
|
1018
|
+
const queueTicketPath = `${lockPath}.queue.${String(enqueuedAtMs).padStart(13, "0")}.${String(process.pid).padStart(10, "0")}.${ownerId}`;
|
|
1019
|
+
const queueCandidatePath = `${queueTicketPath}.candidate`;
|
|
968
1020
|
const deadline = Date.now() + (options.waitMs ?? 0);
|
|
1021
|
+
const waitStartedAt = Date.now();
|
|
1022
|
+
const progressIntervalMs = options.progressIntervalMs ?? DEFAULT_WAIT_PROGRESS_INTERVAL_MS;
|
|
1023
|
+
let lastProgressAt = waitStartedAt;
|
|
969
1024
|
let reclaimAttempts = 0;
|
|
1025
|
+
const livenessCache = /* @__PURE__ */ new Map();
|
|
970
1026
|
import_node_fs4.default.writeFileSync(candidatePath, `${owner}
|
|
1027
|
+
`, {
|
|
1028
|
+
encoding: "utf-8",
|
|
1029
|
+
flag: "wx"
|
|
1030
|
+
});
|
|
1031
|
+
if (options.fair) {
|
|
1032
|
+
import_node_fs4.default.writeFileSync(queueCandidatePath, `${owner}
|
|
971
1033
|
`, { encoding: "utf-8", flag: "wx" });
|
|
1034
|
+
import_node_fs4.default.renameSync(queueCandidatePath, queueTicketPath);
|
|
1035
|
+
}
|
|
972
1036
|
try {
|
|
973
1037
|
for (; ; ) {
|
|
1038
|
+
let queueState;
|
|
1039
|
+
if (options.fair) {
|
|
1040
|
+
queueState = inspectFairQueue(lockPath, queueTicketPath, livenessCache);
|
|
1041
|
+
}
|
|
1042
|
+
if (queueState && queueState.position > 1) {
|
|
1043
|
+
const now = Date.now();
|
|
1044
|
+
if (now >= deadline) {
|
|
1045
|
+
const target = options.target ? ` for ${options.target}` : "";
|
|
1046
|
+
const waitedSeconds = Math.round((now - waitStartedAt) / 1e3);
|
|
1047
|
+
throw new Error(
|
|
1048
|
+
`${options.activity} is still queued${target} (position ${queueState.position}, ahead PID ${queueState.leaderPid}); gave up after waiting ${waitedSeconds}s`
|
|
1049
|
+
);
|
|
1050
|
+
}
|
|
1051
|
+
if (options.onWait && now - lastProgressAt >= progressIntervalMs) {
|
|
1052
|
+
lastProgressAt = now;
|
|
1053
|
+
options.onWait({
|
|
1054
|
+
waitingMs: now - waitStartedAt,
|
|
1055
|
+
holderPid: queueState.leaderPid,
|
|
1056
|
+
queuePosition: queueState.position,
|
|
1057
|
+
waitingOn: "queue"
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
sleepSync(20);
|
|
1061
|
+
continue;
|
|
1062
|
+
}
|
|
1063
|
+
const acquiredAtMs = Date.now();
|
|
1064
|
+
const record = `${owner}:${acquiredAtMs}`;
|
|
1065
|
+
import_node_fs4.default.writeFileSync(candidatePath, `${record}
|
|
1066
|
+
`, "utf-8");
|
|
974
1067
|
try {
|
|
975
1068
|
import_node_fs4.default.linkSync(candidatePath, lockPath);
|
|
976
|
-
return
|
|
1069
|
+
return record;
|
|
977
1070
|
} catch (error) {
|
|
978
1071
|
if (error.code !== "EEXIST") {
|
|
979
1072
|
throw error;
|
|
980
1073
|
}
|
|
981
1074
|
}
|
|
982
|
-
const state = tryReclaimStaleLock(lockPath, staleLinkPath);
|
|
1075
|
+
const state = tryReclaimStaleLock(lockPath, staleLinkPath, livenessCache);
|
|
983
1076
|
if (state.kind === "reclaimed") {
|
|
984
1077
|
continue;
|
|
985
1078
|
}
|
|
986
1079
|
if (state.kind === "live") {
|
|
987
|
-
|
|
1080
|
+
const now = Date.now();
|
|
1081
|
+
if (now >= deadline) {
|
|
988
1082
|
const target = options.target ? ` for ${options.target}` : "";
|
|
989
|
-
|
|
1083
|
+
const waitedSeconds = Math.round((now - waitStartedAt) / 1e3);
|
|
1084
|
+
const heldSeconds = state.acquiredAtMs !== void 0 ? `, held for ${Math.round((now - state.acquiredAtMs) / 1e3)}s` : "";
|
|
1085
|
+
throw new Error(
|
|
1086
|
+
`${options.activity} is already running${target} (PID ${state.pid}${heldSeconds}); gave up after waiting ${waitedSeconds}s`
|
|
1087
|
+
);
|
|
1088
|
+
}
|
|
1089
|
+
if (options.onWait && now - lastProgressAt >= progressIntervalMs) {
|
|
1090
|
+
lastProgressAt = now;
|
|
1091
|
+
options.onWait({
|
|
1092
|
+
waitingMs: now - waitStartedAt,
|
|
1093
|
+
holderPid: state.pid,
|
|
1094
|
+
holderHeldMs: state.acquiredAtMs !== void 0 ? Math.max(0, now - state.acquiredAtMs) : void 0,
|
|
1095
|
+
queuePosition: queueState?.position,
|
|
1096
|
+
waitingOn: "lock"
|
|
1097
|
+
});
|
|
990
1098
|
}
|
|
991
1099
|
sleepSync(20);
|
|
992
1100
|
continue;
|
|
@@ -1002,6 +1110,8 @@ function acquireFileLock(lockPath, options) {
|
|
|
1002
1110
|
} finally {
|
|
1003
1111
|
import_node_fs4.default.rmSync(candidatePath, { force: true });
|
|
1004
1112
|
import_node_fs4.default.rmSync(staleLinkPath, { force: true });
|
|
1113
|
+
import_node_fs4.default.rmSync(queueCandidatePath, { force: true });
|
|
1114
|
+
import_node_fs4.default.rmSync(queueTicketPath, { force: true });
|
|
1005
1115
|
}
|
|
1006
1116
|
}
|
|
1007
1117
|
function releaseFileLock(lockPath, owner) {
|
|
@@ -1031,19 +1141,33 @@ async function withFileLock(lockPath, options, operation) {
|
|
|
1031
1141
|
releaseFileLock(lockPath, owner);
|
|
1032
1142
|
}
|
|
1033
1143
|
}
|
|
1034
|
-
|
|
1144
|
+
function createStderrWaitReporter(activity, target) {
|
|
1145
|
+
return (progress) => {
|
|
1146
|
+
const heldSeconds = progress.holderHeldMs !== void 0 ? `, held for ${Math.round(progress.holderHeldMs / 1e3)}s` : "";
|
|
1147
|
+
const status = progress.waitingOn === "queue" ? `waiting in provider queue position ${progress.queuePosition} led by PID ${progress.holderPid}` : progress.queuePosition !== void 0 && progress.queuePosition > 1 ? `waiting in provider queue position ${progress.queuePosition}; provider lock held by PID ${progress.holderPid}${heldSeconds}` : `waiting for the provider lock held by PID ${progress.holderPid}${heldSeconds}`;
|
|
1148
|
+
process.stderr.write(
|
|
1149
|
+
`${activity} for ${target}: ${status}; waited ${Math.round(progress.waitingMs / 1e3)}s so far
|
|
1150
|
+
`
|
|
1151
|
+
);
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
var import_node_child_process2, import_node_crypto2, import_node_fs4, import_node_path4, DEFAULT_WAIT_PROGRESS_INTERVAL_MS, PROCESS_BIRTH_RECHECK_INTERVAL_MS, CANONICAL_OWNER_RE;
|
|
1035
1155
|
var init_file_lock = __esm({
|
|
1036
1156
|
"src/core/file-lock.ts"() {
|
|
1037
1157
|
"use strict";
|
|
1038
1158
|
import_node_child_process2 = require("child_process");
|
|
1039
1159
|
import_node_crypto2 = require("crypto");
|
|
1040
1160
|
import_node_fs4 = __toESM(require("fs"));
|
|
1161
|
+
import_node_path4 = __toESM(require("path"));
|
|
1162
|
+
DEFAULT_WAIT_PROGRESS_INTERVAL_MS = 1e4;
|
|
1163
|
+
PROCESS_BIRTH_RECHECK_INTERVAL_MS = 1e3;
|
|
1164
|
+
CANONICAL_OWNER_RE = /^[0-9]+:[A-Za-z0-9_-]+:[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
1041
1165
|
}
|
|
1042
1166
|
});
|
|
1043
1167
|
|
|
1044
1168
|
// src/core/workspace.ts
|
|
1045
1169
|
function comparableWorkspacePath(filePath) {
|
|
1046
|
-
const resolved =
|
|
1170
|
+
const resolved = import_node_path5.default.resolve(filePath);
|
|
1047
1171
|
try {
|
|
1048
1172
|
return import_node_fs5.default.realpathSync.native(resolved);
|
|
1049
1173
|
} catch {
|
|
@@ -1057,8 +1181,23 @@ function wsFromBranch(branch) {
|
|
|
1057
1181
|
const slug = branch.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+/, "").slice(0, MAX_WORKSPACE_LENGTH).replace(/-+$/, "");
|
|
1058
1182
|
return slug.length > 0 ? slug : void 0;
|
|
1059
1183
|
}
|
|
1184
|
+
function workspaceIdentityFallback(source, attempt) {
|
|
1185
|
+
const readablePrefix = (wsFromBranch(source) ?? "workspace").slice(0, WORKSPACE_IDENTITY_PREFIX_LENGTH).replace(/-+$/, "");
|
|
1186
|
+
const prefix = readablePrefix || "workspace";
|
|
1187
|
+
const hash = (0, import_node_crypto3.createHash)("sha256").update(WORKSPACE_IDENTITY_HASH_DOMAIN, "utf-8").update("\0", "utf-8").update(source, "utf-8").update("\0", "utf-8").update(String(attempt), "utf-8").digest("hex").slice(0, WORKSPACE_IDENTITY_HASH_LENGTH);
|
|
1188
|
+
return `${prefix}-${hash}`;
|
|
1189
|
+
}
|
|
1190
|
+
function workspaceIdentityCandidates(source) {
|
|
1191
|
+
const candidates = [];
|
|
1192
|
+
const legacy = wsFromBranch(source);
|
|
1193
|
+
if (legacy) candidates.push(legacy);
|
|
1194
|
+
for (let attempt = 0; candidates.length < WORKSPACE_IDENTITY_CANDIDATE_LIMIT; attempt += 1) {
|
|
1195
|
+
candidates.push(workspaceIdentityFallback(source, attempt));
|
|
1196
|
+
}
|
|
1197
|
+
return candidates;
|
|
1198
|
+
}
|
|
1060
1199
|
function isLinkedWorktree(repoPath) {
|
|
1061
|
-
const gitPath =
|
|
1200
|
+
const gitPath = import_node_path5.default.join(repoPath, ".git");
|
|
1062
1201
|
let stat;
|
|
1063
1202
|
try {
|
|
1064
1203
|
stat = import_node_fs5.default.statSync(gitPath);
|
|
@@ -1082,7 +1221,7 @@ function isLinkedWorktree(repoPath) {
|
|
|
1082
1221
|
return /(^|\/)worktrees\//.test(gitdir);
|
|
1083
1222
|
}
|
|
1084
1223
|
function gitMetadataDir(repoPath) {
|
|
1085
|
-
const gitPath =
|
|
1224
|
+
const gitPath = import_node_path5.default.join(repoPath, ".git");
|
|
1086
1225
|
let stat;
|
|
1087
1226
|
try {
|
|
1088
1227
|
stat = import_node_fs5.default.statSync(gitPath);
|
|
@@ -1099,7 +1238,7 @@ function gitMetadataDir(repoPath) {
|
|
|
1099
1238
|
if (!match) {
|
|
1100
1239
|
return void 0;
|
|
1101
1240
|
}
|
|
1102
|
-
return
|
|
1241
|
+
return import_node_path5.default.resolve(repoPath, match[1].trim());
|
|
1103
1242
|
}
|
|
1104
1243
|
function validatePersistedWorkspace(value) {
|
|
1105
1244
|
const workspace = value.trim();
|
|
@@ -1113,7 +1252,7 @@ function readPersistedWorkspace(repoPath) {
|
|
|
1113
1252
|
if (!gitDir) {
|
|
1114
1253
|
return void 0;
|
|
1115
1254
|
}
|
|
1116
|
-
const metadataPath =
|
|
1255
|
+
const metadataPath = import_node_path5.default.join(gitDir, WORKSPACE_METADATA_FILE);
|
|
1117
1256
|
try {
|
|
1118
1257
|
return validatePersistedWorkspace(import_node_fs5.default.readFileSync(metadataPath, "utf-8"));
|
|
1119
1258
|
} catch (error) {
|
|
@@ -1138,8 +1277,8 @@ function persistWorkspace(repoPath, value) {
|
|
|
1138
1277
|
if (!gitDir) {
|
|
1139
1278
|
throw new Error(`cannot persist workspace identity: '${repoPath}' is not a Git checkout`);
|
|
1140
1279
|
}
|
|
1141
|
-
|
|
1142
|
-
|
|
1280
|
+
writeFileAtomically(import_node_path5.default.join(gitDir, WORKSPACE_METADATA_FILE), `${workspace}
|
|
1281
|
+
`);
|
|
1143
1282
|
return workspace;
|
|
1144
1283
|
}
|
|
1145
1284
|
async function withWorkspaceLifecycleLock(repoPath, operation) {
|
|
@@ -1147,7 +1286,7 @@ async function withWorkspaceLifecycleLock(repoPath, operation) {
|
|
|
1147
1286
|
if (!gitDir) {
|
|
1148
1287
|
throw new Error(`cannot lock workspace lifecycle: '${repoPath}' is not a Git checkout`);
|
|
1149
1288
|
}
|
|
1150
|
-
const lockPath =
|
|
1289
|
+
const lockPath = import_node_path5.default.join(gitDir, WORKSPACE_LOCK_FILE);
|
|
1151
1290
|
return withFileLock(
|
|
1152
1291
|
lockPath,
|
|
1153
1292
|
{ activity: "workspace lifecycle", target: `'${repoPath}'` },
|
|
@@ -1175,7 +1314,7 @@ function deriveLinkedWorktreeWorkspace(repoPath, linkedWorktreeBranch) {
|
|
|
1175
1314
|
return void 0;
|
|
1176
1315
|
}
|
|
1177
1316
|
const branch = currentBranch(repoPath);
|
|
1178
|
-
return wsFromBranch(branch ??
|
|
1317
|
+
return wsFromBranch(branch ?? import_node_path5.default.basename(import_node_path5.default.resolve(repoPath)));
|
|
1179
1318
|
}
|
|
1180
1319
|
function resolveWorktreeWorkspace(repoPath, linkedWorktreeBranch) {
|
|
1181
1320
|
return readPersistedWorkspace(repoPath) ?? deriveLinkedWorktreeWorkspace(repoPath, linkedWorktreeBranch);
|
|
@@ -1198,15 +1337,21 @@ function resolveWorkspace(repoPath, override) {
|
|
|
1198
1337
|
}
|
|
1199
1338
|
return deriveLinkedWorktreeWorkspace(repoPath);
|
|
1200
1339
|
}
|
|
1201
|
-
var import_node_child_process3, import_node_fs5,
|
|
1340
|
+
var import_node_child_process3, import_node_crypto3, import_node_fs5, import_node_path5, MAX_WORKSPACE_LENGTH, WORKSPACE_IDENTITY_CANDIDATE_LIMIT, WORKSPACE_IDENTITY_HASH_LENGTH, WORKSPACE_IDENTITY_PREFIX_LENGTH, WORKSPACE_IDENTITY_HASH_DOMAIN, WORKSPACE_METADATA_FILE, WORKSPACE_LOCK_FILE;
|
|
1202
1341
|
var init_workspace = __esm({
|
|
1203
1342
|
"src/core/workspace.ts"() {
|
|
1204
1343
|
"use strict";
|
|
1205
1344
|
import_node_child_process3 = require("child_process");
|
|
1345
|
+
import_node_crypto3 = require("crypto");
|
|
1206
1346
|
import_node_fs5 = __toESM(require("fs"));
|
|
1207
|
-
|
|
1347
|
+
import_node_path5 = __toESM(require("path"));
|
|
1348
|
+
init_atomic_file();
|
|
1208
1349
|
init_file_lock();
|
|
1209
1350
|
MAX_WORKSPACE_LENGTH = 32;
|
|
1351
|
+
WORKSPACE_IDENTITY_CANDIDATE_LIMIT = 16;
|
|
1352
|
+
WORKSPACE_IDENTITY_HASH_LENGTH = 8;
|
|
1353
|
+
WORKSPACE_IDENTITY_PREFIX_LENGTH = MAX_WORKSPACE_LENGTH - WORKSPACE_IDENTITY_HASH_LENGTH - 1;
|
|
1354
|
+
WORKSPACE_IDENTITY_HASH_DOMAIN = "devrouter-workspace-identity-v1";
|
|
1210
1355
|
WORKSPACE_METADATA_FILE = "devrouter-workspace";
|
|
1211
1356
|
WORKSPACE_LOCK_FILE = "devrouter-workspace.lock";
|
|
1212
1357
|
}
|
|
@@ -1623,7 +1768,7 @@ function listHostRoutes(tlsEnabled) {
|
|
|
1623
1768
|
protocol: isTcp ? `tcp/${tcpProtocol}` : "http",
|
|
1624
1769
|
appName: route.name,
|
|
1625
1770
|
serviceName: route.name,
|
|
1626
|
-
projectName:
|
|
1771
|
+
projectName: import_node_path6.default.basename(route.repoPath),
|
|
1627
1772
|
hosts: [route.host],
|
|
1628
1773
|
// TCP routes are reached via an SNI-aware client on the shared protocol
|
|
1629
1774
|
// port; surface a protocol-scheme URL rather than http(s)://.
|
|
@@ -1638,12 +1783,12 @@ function listHostRoutes(tlsEnabled) {
|
|
|
1638
1783
|
};
|
|
1639
1784
|
});
|
|
1640
1785
|
}
|
|
1641
|
-
var import_node_fs6,
|
|
1786
|
+
var import_node_fs6, import_node_path6, import_node_util, import_yaml, LOOPBACK_HOSTS, UPSTREAM_RE, TCP_TLS_ALPN_PROTOCOLS, ROUTE_METADATA_PREFIX, ROUTE_METADATA_FAMILY_PREFIX, STATE_LOCK_FILE;
|
|
1642
1787
|
var init_host_routes = __esm({
|
|
1643
1788
|
"src/core/host-routes.ts"() {
|
|
1644
1789
|
"use strict";
|
|
1645
1790
|
import_node_fs6 = __toESM(require("fs"));
|
|
1646
|
-
|
|
1791
|
+
import_node_path6 = __toESM(require("path"));
|
|
1647
1792
|
import_node_util = require("util");
|
|
1648
1793
|
import_yaml = __toESM(require("yaml"));
|
|
1649
1794
|
init_atomic_file();
|
|
@@ -2491,10 +2636,10 @@ function appToNodeValue(app) {
|
|
|
2491
2636
|
return value;
|
|
2492
2637
|
}
|
|
2493
2638
|
function resolveRepoPath(repoPath) {
|
|
2494
|
-
return
|
|
2639
|
+
return import_node_path7.default.resolve(repoPath ?? process.cwd());
|
|
2495
2640
|
}
|
|
2496
2641
|
function getRepoConfigPath(repoPath) {
|
|
2497
|
-
return
|
|
2642
|
+
return import_node_path7.default.join(resolveRepoPath(repoPath), CONFIG_FILE_NAME);
|
|
2498
2643
|
}
|
|
2499
2644
|
function loadRepoConfig(repoPath) {
|
|
2500
2645
|
const resolvedRepoPath = resolveRepoPath(repoPath);
|
|
@@ -2509,7 +2654,7 @@ function loadRepoConfig(repoPath) {
|
|
|
2509
2654
|
const config = parseConfig(parsed ?? {}, configPath);
|
|
2510
2655
|
const requiredVersion = config.devrouter?.version;
|
|
2511
2656
|
if (requiredVersion && !hasWarnedVersionMismatch) {
|
|
2512
|
-
const cliVersion = true ? "0.0.
|
|
2657
|
+
const cliVersion = true ? "0.0.46" : "0.0.0-dev";
|
|
2513
2658
|
if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
|
|
2514
2659
|
hasWarnedVersionMismatch = true;
|
|
2515
2660
|
process.stderr.write(
|
|
@@ -2542,7 +2687,7 @@ function initRepoConfig(repoPath, options = {}) {
|
|
|
2542
2687
|
}
|
|
2543
2688
|
} : {},
|
|
2544
2689
|
project: {
|
|
2545
|
-
name:
|
|
2690
|
+
name: import_node_path7.default.basename(resolvedRepoPath)
|
|
2546
2691
|
},
|
|
2547
2692
|
apps: []
|
|
2548
2693
|
};
|
|
@@ -2771,7 +2916,7 @@ function namespaceHost(host, workspace) {
|
|
|
2771
2916
|
return `${base}.${workspace}${suffix}`;
|
|
2772
2917
|
}
|
|
2773
2918
|
function applyWorkspace(config, workspace, repoPath) {
|
|
2774
|
-
const defaultToken = wsFromBranch(config.project?.name ??
|
|
2919
|
+
const defaultToken = wsFromBranch(config.project?.name ?? import_node_path7.default.basename(import_node_path7.default.resolve(repoPath))) ?? "app";
|
|
2775
2920
|
const substitutionToken = workspace ?? defaultToken;
|
|
2776
2921
|
const next = structuredClone(config);
|
|
2777
2922
|
for (const app of next.apps) {
|
|
@@ -2853,12 +2998,12 @@ function resolveAppDependencies(config, app) {
|
|
|
2853
2998
|
}
|
|
2854
2999
|
return results;
|
|
2855
3000
|
}
|
|
2856
|
-
var import_node_fs7,
|
|
3001
|
+
var import_node_fs7, import_node_path7, import_yaml2, hasWarnedVersionMismatch, CONFIG_FILE_NAME, DEFAULT_TCP_PROTOCOL, VALID_HOSTNAME_RE, DEVROUTER_VERSION_RE, VALID_ENV_NAME_RE, VALID_ENV_VAR_RE, UPSTREAM_TEMPLATE_RE, MAX_COMMAND_LENGTH, DEFAULT_HOST_STRATEGY, PROFILE_NAME_RE, MAX_PROFILES;
|
|
2857
3002
|
var init_repo_config = __esm({
|
|
2858
3003
|
"src/core/repo-config.ts"() {
|
|
2859
3004
|
"use strict";
|
|
2860
3005
|
import_node_fs7 = __toESM(require("fs"));
|
|
2861
|
-
|
|
3006
|
+
import_node_path7 = __toESM(require("path"));
|
|
2862
3007
|
import_yaml2 = __toESM(require("yaml"));
|
|
2863
3008
|
init_capabilities();
|
|
2864
3009
|
init_host_routes();
|
|
@@ -3035,11 +3180,11 @@ function buildOnboardingPrompt(options = {}) {
|
|
|
3035
3180
|
"- Use `devrouter status --repo <REPO_PATH> --json` and `devrouter doctor --repo <REPO_PATH> --json` to inspect managed desired/active resources, fingerprints, transition state, and values-free drift.",
|
|
3036
3181
|
"",
|
|
3037
3182
|
"Workspace isolation (parallel git worktrees / agents):",
|
|
3038
|
-
`- A "workspace token" lets several worktrees of one repo run in parallel without host/route collisions. Each managed linked worktree has a local Git token plus a durable owner record in the repository's Git common directory spanning the
|
|
3039
|
-
"- The owner record survives linked-worktree removal and binds the exact path to its
|
|
3183
|
+
`- A "workspace token" lets several worktrees of one repo run in parallel without host/route collisions. Each managed linked worktree has a local Git token plus a durable owner record in the repository's Git common directory spanning the workspace-runtime id, devrouter routes, the \`${WORKSPACE_PLACEHOLDER}\` proxy upstream, and devcontainer aliases.`,
|
|
3184
|
+
"- The owner record survives linked-worktree removal and binds the exact path to its workspace-runtime ID. First use reconciles persisted metadata, the exact-path owner record, and both DevPod and Devsy registries. It preserves an established agreement, uses the readable sanitized identity when free, or claims a deterministic hash-suffixed fallback on collision before provider or route mutation. Later flags or `DEVROUTER_WORKSPACE` may repeat but cannot rename it. Unreadable or conflicting evidence fails closed. The primary checkout stays non-namespaced.",
|
|
3040
3185
|
`- 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.`,
|
|
3041
3186
|
"- 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.",
|
|
3042
|
-
"- 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. `workspace up` creates linked worktrees; destructive worktree removal and GC remain ledger-scoped.",
|
|
3187
|
+
"- 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.",
|
|
3043
3188
|
"- A managed adapter plus `postCreateCommand` requires `waitFor` exactly `postCreateCommand` or `postStartCommand`; managed selective config preserves lifecycle fields and changes only `runServices`.",
|
|
3044
3189
|
"- Managed process reuse fingerprints command argv, workspace identity, and the exact adapter snapshot. If a non-secret runtime value also affects reuse, set `DEVROUTER_PROCESS_FINGERPRINT_ENV` to its comma-separated environment names; secret-like names are rejected and raw values are never persisted.",
|
|
3045
3190
|
"- Owner status is `present`, `missing`, `locked`, or `conflict`. Dirty or locked full down fails before side effects. `workspace gc` is a dry run; only `--yes` deletes exact eligible missing resources and records, never Git worktrees, branches, or prune state.",
|
|
@@ -3766,7 +3911,7 @@ function readPromptDirectory(promptDirPath) {
|
|
|
3766
3911
|
continue;
|
|
3767
3912
|
}
|
|
3768
3913
|
const version = normalizeVersion(basename);
|
|
3769
|
-
const promptPath =
|
|
3914
|
+
const promptPath = import_node_path8.default.join(promptDirPath, file.name);
|
|
3770
3915
|
const prompt = import_node_fs8.default.readFileSync(promptPath, "utf-8").trim();
|
|
3771
3916
|
releases.push({ version, prompt, promptPath });
|
|
3772
3917
|
}
|
|
@@ -3788,17 +3933,17 @@ function listAvailableUpgradeTargets(currentVersion, releases) {
|
|
|
3788
3933
|
}
|
|
3789
3934
|
function resolvePromptDirectory(explicitPath) {
|
|
3790
3935
|
if (explicitPath) {
|
|
3791
|
-
return
|
|
3936
|
+
return import_node_path8.default.resolve(explicitPath);
|
|
3792
3937
|
}
|
|
3793
|
-
const entryFile = process.argv[1] ?
|
|
3794
|
-
const entryDir =
|
|
3795
|
-
const resolvedEntryDir = import_node_fs8.default.existsSync(entryFile) ?
|
|
3938
|
+
const entryFile = process.argv[1] ? import_node_path8.default.resolve(process.argv[1]) : __filename;
|
|
3939
|
+
const entryDir = import_node_path8.default.dirname(entryFile);
|
|
3940
|
+
const resolvedEntryDir = import_node_fs8.default.existsSync(entryFile) ? import_node_path8.default.dirname(import_node_fs8.default.realpathSync(entryFile)) : entryDir;
|
|
3796
3941
|
const candidates = [
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3942
|
+
import_node_path8.default.resolve(resolvedEntryDir, "..", UPGRADE_PROMPTS_DIR),
|
|
3943
|
+
import_node_path8.default.resolve(entryDir, "..", UPGRADE_PROMPTS_DIR),
|
|
3944
|
+
import_node_path8.default.resolve(__dirname, "..", UPGRADE_PROMPTS_DIR),
|
|
3945
|
+
import_node_path8.default.resolve(__dirname, "..", "..", UPGRADE_PROMPTS_DIR),
|
|
3946
|
+
import_node_path8.default.resolve(process.cwd(), UPGRADE_PROMPTS_DIR)
|
|
3802
3947
|
];
|
|
3803
3948
|
const existing = candidates.find((candidate) => import_node_fs8.default.existsSync(candidate));
|
|
3804
3949
|
return existing ?? candidates[0];
|
|
@@ -3816,12 +3961,12 @@ function loadUpgradeCatalog(options = {}) {
|
|
|
3816
3961
|
releases
|
|
3817
3962
|
};
|
|
3818
3963
|
}
|
|
3819
|
-
var import_node_fs8,
|
|
3964
|
+
var import_node_fs8, import_node_path8, SEMVER_RE, UPGRADE_PROMPTS_DIR;
|
|
3820
3965
|
var init_upgrade = __esm({
|
|
3821
3966
|
"src/core/upgrade.ts"() {
|
|
3822
3967
|
"use strict";
|
|
3823
3968
|
import_node_fs8 = __toESM(require("fs"));
|
|
3824
|
-
|
|
3969
|
+
import_node_path8 = __toESM(require("path"));
|
|
3825
3970
|
init_repo_config();
|
|
3826
3971
|
SEMVER_RE = /^v?(\d+)\.(\d+)\.(\d+)$/;
|
|
3827
3972
|
UPGRADE_PROMPTS_DIR = "upgrade-prompts";
|
|
@@ -4033,7 +4178,7 @@ function parseDevcontainerConfig(contents, sourcePath) {
|
|
|
4033
4178
|
return parsed;
|
|
4034
4179
|
}
|
|
4035
4180
|
function readDevcontainerConfig(repoPath) {
|
|
4036
|
-
const sourcePath =
|
|
4181
|
+
const sourcePath = import_node_path9.default.join(repoPath, ".devcontainer", "devcontainer.json");
|
|
4037
4182
|
if (!import_node_fs9.default.existsSync(sourcePath) || !import_node_fs9.default.lstatSync(sourcePath).isFile()) {
|
|
4038
4183
|
throw new Error(`Managed Dev Container source config does not exist: ${sourcePath}`);
|
|
4039
4184
|
}
|
|
@@ -4053,12 +4198,12 @@ function assertManagedDevcontainerLifecycle(repoPath) {
|
|
|
4053
4198
|
`Managed Dev Container source '${sourcePath}' defines postCreateCommand, but waitFor ${actual}. Set waitFor to 'postCreateCommand' or 'postStartCommand' before retrying devrouter ensure.`
|
|
4054
4199
|
);
|
|
4055
4200
|
}
|
|
4056
|
-
var import_node_fs9,
|
|
4201
|
+
var import_node_fs9, import_node_path9, import_jsonc_parser;
|
|
4057
4202
|
var init_devcontainer_config = __esm({
|
|
4058
4203
|
"src/core/devcontainer-config.ts"() {
|
|
4059
4204
|
"use strict";
|
|
4060
4205
|
import_node_fs9 = __toESM(require("fs"));
|
|
4061
|
-
|
|
4206
|
+
import_node_path9 = __toESM(require("path"));
|
|
4062
4207
|
import_jsonc_parser = require("jsonc-parser");
|
|
4063
4208
|
}
|
|
4064
4209
|
});
|
|
@@ -4068,7 +4213,7 @@ function renderDeliveryScript(targetPath) {
|
|
|
4068
4213
|
if (!/^\/tmp\/devrouter\/bin\/[a-zA-Z0-9._-]+$/.test(targetPath)) {
|
|
4069
4214
|
throw new Error(`Unsafe managed runtime delivery path: ${targetPath}`);
|
|
4070
4215
|
}
|
|
4071
|
-
const temporaryPrefix =
|
|
4216
|
+
const temporaryPrefix = import_node_path10.default.posix.basename(targetPath);
|
|
4072
4217
|
return `set -eu
|
|
4073
4218
|
umask 077
|
|
4074
4219
|
runtime_root=/tmp/devrouter
|
|
@@ -4110,12 +4255,12 @@ function readRegularFile(filePath) {
|
|
|
4110
4255
|
return readRegularFileBytes(filePath)?.toString("utf-8");
|
|
4111
4256
|
}
|
|
4112
4257
|
function adapterFingerprint(adapter) {
|
|
4113
|
-
return (0,
|
|
4258
|
+
return (0, import_node_crypto4.createHash)("sha256").update(adapter).digest("hex");
|
|
4114
4259
|
}
|
|
4115
4260
|
function resolveProcessHelperPath() {
|
|
4116
4261
|
const candidates = [
|
|
4117
|
-
|
|
4118
|
-
|
|
4262
|
+
import_node_path10.default.resolve(__dirname, "..", "bin", "devrouter-process"),
|
|
4263
|
+
import_node_path10.default.resolve(__dirname, "..", "..", "bin", "devrouter-process")
|
|
4119
4264
|
];
|
|
4120
4265
|
const helperPath = candidates.find((candidate) => import_node_fs10.default.existsSync(candidate));
|
|
4121
4266
|
if (!helperPath) {
|
|
@@ -4124,11 +4269,11 @@ function resolveProcessHelperPath() {
|
|
|
4124
4269
|
return helperPath;
|
|
4125
4270
|
}
|
|
4126
4271
|
function resolveManagedPostStartPlan(repoPath) {
|
|
4127
|
-
const adapterPath =
|
|
4272
|
+
const adapterPath = import_node_path10.default.join(repoPath, MANAGED_ADAPTER_PATH);
|
|
4128
4273
|
const adapterBytes = readRegularFileBytes(adapterPath);
|
|
4129
4274
|
const adapterText = adapterBytes?.toString("utf-8");
|
|
4130
|
-
const dockerfilePath =
|
|
4131
|
-
const devcontainerPath =
|
|
4275
|
+
const dockerfilePath = import_node_path10.default.join(repoPath, ".devcontainer", "Dockerfile");
|
|
4276
|
+
const devcontainerPath = import_node_path10.default.join(repoPath, ".devcontainer", "devcontainer.json");
|
|
4132
4277
|
const dockerfile = readRegularFile(dockerfilePath) ?? "";
|
|
4133
4278
|
const devcontainer = readRegularFile(devcontainerPath) ?? "";
|
|
4134
4279
|
const devrouterPattern = [adapterText, dockerfile, devcontainer].filter((value) => value !== void 0).some(
|
|
@@ -4257,14 +4402,14 @@ function runManagedProcessAction(options) {
|
|
|
4257
4402
|
}
|
|
4258
4403
|
return "drifted";
|
|
4259
4404
|
}
|
|
4260
|
-
var import_node_child_process5,
|
|
4405
|
+
var import_node_child_process5, import_node_crypto4, import_node_fs10, import_node_path10, MANAGED_MARKER, MANAGED_ADAPTER_PATH, RUNTIME_HELPER_PATH, ADAPTER_WRAPPER;
|
|
4261
4406
|
var init_managed_post_start = __esm({
|
|
4262
4407
|
"src/core/managed-post-start.ts"() {
|
|
4263
4408
|
"use strict";
|
|
4264
4409
|
import_node_child_process5 = require("child_process");
|
|
4265
|
-
|
|
4410
|
+
import_node_crypto4 = require("crypto");
|
|
4266
4411
|
import_node_fs10 = __toESM(require("fs"));
|
|
4267
|
-
|
|
4412
|
+
import_node_path10 = __toESM(require("path"));
|
|
4268
4413
|
init_devcontainer_config();
|
|
4269
4414
|
MANAGED_MARKER = "devrouter:managed devcontainer";
|
|
4270
4415
|
MANAGED_ADAPTER_PATH = ".devcontainer/post-start.sh";
|
|
@@ -4397,11 +4542,11 @@ function routedProxyApps(config) {
|
|
|
4397
4542
|
);
|
|
4398
4543
|
}
|
|
4399
4544
|
function buildDevcontainerChecks(repoPath, config, workspace) {
|
|
4400
|
-
const devcontainerDir =
|
|
4545
|
+
const devcontainerDir = import_node_path11.default.join(repoPath, ".devcontainer");
|
|
4401
4546
|
if (!import_node_fs11.default.existsSync(devcontainerDir)) {
|
|
4402
4547
|
return [];
|
|
4403
4548
|
}
|
|
4404
|
-
const compose = inspectCompose(
|
|
4549
|
+
const compose = inspectCompose(import_node_path11.default.join(devcontainerDir, "docker-compose.yml"), workspace);
|
|
4405
4550
|
const checks = [];
|
|
4406
4551
|
if (compose.parseError) {
|
|
4407
4552
|
checks.push({
|
|
@@ -4428,7 +4573,7 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
|
|
|
4428
4573
|
details: compose.publishedPorts.length > 0 ? compose.publishedPorts.join(", ") : void 0,
|
|
4429
4574
|
suggestion: compose.parseError || compose.publishedPorts.length > 0 ? "Remove published ports and route services through devnet aliases with devrouter proxy apps." : void 0
|
|
4430
4575
|
});
|
|
4431
|
-
const dockerfilePath =
|
|
4576
|
+
const dockerfilePath = import_node_path11.default.join(devcontainerDir, "Dockerfile");
|
|
4432
4577
|
const dockerfileExists = import_node_fs11.default.existsSync(dockerfilePath);
|
|
4433
4578
|
const dockerfile = dockerfileExists ? import_node_fs11.default.readFileSync(dockerfilePath, "utf-8") : "";
|
|
4434
4579
|
const devrouterArtifacts = ["@devrouter/cli", "devrouter-process"].filter(
|
|
@@ -4485,12 +4630,12 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
|
|
|
4485
4630
|
});
|
|
4486
4631
|
return checks;
|
|
4487
4632
|
}
|
|
4488
|
-
var import_node_fs11,
|
|
4633
|
+
var import_node_fs11, import_node_path11, import_yaml3;
|
|
4489
4634
|
var init_devcontainer_diagnostics = __esm({
|
|
4490
4635
|
"src/core/devcontainer-diagnostics.ts"() {
|
|
4491
4636
|
"use strict";
|
|
4492
4637
|
import_node_fs11 = __toESM(require("fs"));
|
|
4493
|
-
|
|
4638
|
+
import_node_path11 = __toESM(require("path"));
|
|
4494
4639
|
import_yaml3 = __toESM(require("yaml"));
|
|
4495
4640
|
init_managed_post_start();
|
|
4496
4641
|
}
|
|
@@ -4498,18 +4643,18 @@ var init_devcontainer_diagnostics = __esm({
|
|
|
4498
4643
|
|
|
4499
4644
|
// src/core/paths.ts
|
|
4500
4645
|
function assertPathWithinRepo(filePath, repoRoot, label) {
|
|
4501
|
-
const resolvedRoot =
|
|
4502
|
-
const resolved =
|
|
4503
|
-
if (resolved !== resolvedRoot && !resolved.startsWith(resolvedRoot +
|
|
4646
|
+
const resolvedRoot = import_node_path12.default.resolve(repoRoot);
|
|
4647
|
+
const resolved = import_node_path12.default.resolve(repoRoot, filePath);
|
|
4648
|
+
if (resolved !== resolvedRoot && !resolved.startsWith(resolvedRoot + import_node_path12.default.sep)) {
|
|
4504
4649
|
throw new Error(`${label} path '${filePath}' escapes the repository root.`);
|
|
4505
4650
|
}
|
|
4506
4651
|
return resolved;
|
|
4507
4652
|
}
|
|
4508
|
-
var
|
|
4653
|
+
var import_node_path12;
|
|
4509
4654
|
var init_paths = __esm({
|
|
4510
4655
|
"src/core/paths.ts"() {
|
|
4511
4656
|
"use strict";
|
|
4512
|
-
|
|
4657
|
+
import_node_path12 = __toESM(require("path"));
|
|
4513
4658
|
}
|
|
4514
4659
|
});
|
|
4515
4660
|
|
|
@@ -4764,10 +4909,10 @@ var init_routes = __esm({
|
|
|
4764
4909
|
|
|
4765
4910
|
// src/core/managed-runtime-state.ts
|
|
4766
4911
|
function stateKey(repoPath, workspace) {
|
|
4767
|
-
return (0,
|
|
4912
|
+
return (0, import_node_crypto5.createHash)("sha256").update(`${repoPath}\0${workspace ?? ""}`, "utf-8").digest("hex");
|
|
4768
4913
|
}
|
|
4769
4914
|
function managedRuntimeStatePath(repoPath, workspace) {
|
|
4770
|
-
return
|
|
4915
|
+
return import_node_path13.default.join(DEVROUTER_HOME, "managed-runtime", `${stateKey(repoPath, workspace)}.json`);
|
|
4771
4916
|
}
|
|
4772
4917
|
function isStringArray(value) {
|
|
4773
4918
|
return Array.isArray(value) && value.every((item) => typeof item === "string") && new Set(value).size === value.length;
|
|
@@ -4855,13 +5000,13 @@ function markManagedRuntimeDegraded(state, transitionPhase) {
|
|
|
4855
5000
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4856
5001
|
});
|
|
4857
5002
|
}
|
|
4858
|
-
var
|
|
5003
|
+
var import_node_crypto5, import_node_fs13, import_node_path13;
|
|
4859
5004
|
var init_managed_runtime_state = __esm({
|
|
4860
5005
|
"src/core/managed-runtime-state.ts"() {
|
|
4861
5006
|
"use strict";
|
|
4862
|
-
|
|
5007
|
+
import_node_crypto5 = require("crypto");
|
|
4863
5008
|
import_node_fs13 = __toESM(require("fs"));
|
|
4864
|
-
|
|
5009
|
+
import_node_path13 = __toESM(require("path"));
|
|
4865
5010
|
init_atomic_file();
|
|
4866
5011
|
init_router();
|
|
4867
5012
|
}
|
|
@@ -4912,7 +5057,7 @@ function resolveComposeFiles(source, repoPath, linked) {
|
|
|
4912
5057
|
"managedRuntime requires .devcontainer/devcontainer.json to define dockerComposeFile."
|
|
4913
5058
|
);
|
|
4914
5059
|
}
|
|
4915
|
-
const directory =
|
|
5060
|
+
const directory = import_node_path14.default.join(repoPath, ".devcontainer");
|
|
4916
5061
|
const files = references.map((reference) => {
|
|
4917
5062
|
const resolved2 = assertPathWithinRepo(
|
|
4918
5063
|
resolveComposeReference(reference, linked),
|
|
@@ -4992,7 +5137,7 @@ function isWildcard(values) {
|
|
|
4992
5137
|
return values?.length === 1 && values[0] === "*";
|
|
4993
5138
|
}
|
|
4994
5139
|
function sha256(contents) {
|
|
4995
|
-
return (0,
|
|
5140
|
+
return (0, import_node_crypto6.createHash)("sha256").update(contents, "utf-8").digest("hex");
|
|
4996
5141
|
}
|
|
4997
5142
|
function safeComposeProject(value) {
|
|
4998
5143
|
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(value)) {
|
|
@@ -5057,7 +5202,7 @@ function inspectManagedDevcontainerConfig(options) {
|
|
|
5057
5202
|
const contents = `${MANAGED_DEVCONTAINER_MARKER}
|
|
5058
5203
|
${JSON.stringify(effective, null, 2)}
|
|
5059
5204
|
`;
|
|
5060
|
-
const generatedPath =
|
|
5205
|
+
const generatedPath = import_node_path14.default.join(options.repoPath, MANAGED_DEVCONTAINER_PATH);
|
|
5061
5206
|
assertIgnoredGeneratedPath(options.repoPath, generatedPath);
|
|
5062
5207
|
assertGeneratedPathOwnership(generatedPath);
|
|
5063
5208
|
return {
|
|
@@ -5177,14 +5322,14 @@ function stopExactManagedService(containerId, service) {
|
|
|
5177
5322
|
throw new Error(`Could not stop exact managed service '${service}' (${containerId}).`);
|
|
5178
5323
|
}
|
|
5179
5324
|
}
|
|
5180
|
-
var import_node_child_process6,
|
|
5325
|
+
var import_node_child_process6, import_node_crypto6, import_node_fs14, import_node_path14, import_yaml4, MANAGED_DEVCONTAINER_PATH, MANAGED_DEVCONTAINER_MARKER;
|
|
5181
5326
|
var init_devcontainer_profile = __esm({
|
|
5182
5327
|
"src/core/devcontainer-profile.ts"() {
|
|
5183
5328
|
"use strict";
|
|
5184
5329
|
import_node_child_process6 = require("child_process");
|
|
5185
|
-
|
|
5330
|
+
import_node_crypto6 = require("crypto");
|
|
5186
5331
|
import_node_fs14 = __toESM(require("fs"));
|
|
5187
|
-
|
|
5332
|
+
import_node_path14 = __toESM(require("path"));
|
|
5188
5333
|
import_yaml4 = __toESM(require("yaml"));
|
|
5189
5334
|
init_atomic_file();
|
|
5190
5335
|
init_devcontainer_config();
|
|
@@ -5198,9 +5343,17 @@ var init_devcontainer_profile = __esm({
|
|
|
5198
5343
|
function inspectWorkspaceContainers(options) {
|
|
5199
5344
|
let ids = options?.ids;
|
|
5200
5345
|
if (!ids) {
|
|
5201
|
-
const listed = (0, import_node_child_process7.spawnSync)(
|
|
5202
|
-
|
|
5203
|
-
|
|
5346
|
+
const listed = (0, import_node_child_process7.spawnSync)(
|
|
5347
|
+
"docker",
|
|
5348
|
+
[
|
|
5349
|
+
"ps",
|
|
5350
|
+
"-a",
|
|
5351
|
+
...options?.composeProject ? ["--filter", `label=com.docker.compose.project=${options.composeProject}`] : [],
|
|
5352
|
+
"--format",
|
|
5353
|
+
"{{.ID}}"
|
|
5354
|
+
],
|
|
5355
|
+
{ encoding: "utf-8" }
|
|
5356
|
+
);
|
|
5204
5357
|
if (listed.status !== 0) {
|
|
5205
5358
|
throw new Error(
|
|
5206
5359
|
`docker ps failed: ${(listed.stderr || listed.stdout || listed.error?.message || "unknown error").trim()}`
|
|
@@ -5225,7 +5378,7 @@ function inspectWorkspaceContainers(options) {
|
|
|
5225
5378
|
function workspaceAppContainers(containers, repoPath) {
|
|
5226
5379
|
return containers.filter((container) => {
|
|
5227
5380
|
const workingDir = container.labels["com.docker.compose.project.working_dir"];
|
|
5228
|
-
return Boolean(workingDir && sameWorkspacePath(workingDir,
|
|
5381
|
+
return Boolean(workingDir && sameWorkspacePath(workingDir, import_node_path15.default.join(repoPath, ".devcontainer"))) && container.mounts.some(
|
|
5229
5382
|
(mount) => mount.Type === "bind" && sameWorkspacePath(mount.Source, repoPath)
|
|
5230
5383
|
);
|
|
5231
5384
|
});
|
|
@@ -5233,7 +5386,7 @@ function workspaceAppContainers(containers, repoPath) {
|
|
|
5233
5386
|
function hasExactComposeIdentity(container, options) {
|
|
5234
5387
|
if (options.composeProject !== void 0 && container.labels["com.docker.compose.project"] !== options.composeProject || container.labels["com.docker.compose.service"] !== options.service || !sameWorkspacePath(
|
|
5235
5388
|
container.labels["com.docker.compose.project.working_dir"] ?? "",
|
|
5236
|
-
|
|
5389
|
+
import_node_path15.default.join(options.repoPath, ".devcontainer")
|
|
5237
5390
|
)) {
|
|
5238
5391
|
return false;
|
|
5239
5392
|
}
|
|
@@ -5270,12 +5423,12 @@ function resolveRunningWorkspaceContainer(repoPath) {
|
|
|
5270
5423
|
}
|
|
5271
5424
|
return { id: container.id, workspacePath: repoMount.Destination };
|
|
5272
5425
|
}
|
|
5273
|
-
var import_node_child_process7,
|
|
5426
|
+
var import_node_child_process7, import_node_path15, SAFE_INSPECT_TEMPLATE, SIZE_INSPECT_TEMPLATE;
|
|
5274
5427
|
var init_devpod_environment = __esm({
|
|
5275
5428
|
"src/core/devpod-environment.ts"() {
|
|
5276
5429
|
"use strict";
|
|
5277
5430
|
import_node_child_process7 = require("child_process");
|
|
5278
|
-
|
|
5431
|
+
import_node_path15 = __toESM(require("path"));
|
|
5279
5432
|
init_workspace();
|
|
5280
5433
|
SAFE_INSPECT_TEMPLATE = '{"id":{{json .Id}},"state":{"Running":{{json .State.Running}},"Health":{{with (index .State "Health")}}{"Status":{{json .Status}}}{{else}}null{{end}}},"labels":{"com.docker.compose.project":{{json (index .Config.Labels "com.docker.compose.project")}},"com.docker.compose.service":{{json (index .Config.Labels "com.docker.compose.service")}},"com.docker.compose.project.working_dir":{{json (index .Config.Labels "com.docker.compose.project.working_dir")}},"com.docker.compose.project.config_files":{{json (index .Config.Labels "com.docker.compose.project.config_files")}}},"mounts":{{json .Mounts}},"networks":{{json .NetworkSettings.Networks}}}';
|
|
5281
5434
|
SIZE_INSPECT_TEMPLATE = SAFE_INSPECT_TEMPLATE.replace(
|
|
@@ -5920,7 +6073,7 @@ function tlsSetupCommand(repoPath) {
|
|
|
5920
6073
|
}
|
|
5921
6074
|
function getMkcertRootCAPath(options = {}) {
|
|
5922
6075
|
ensureMkcert();
|
|
5923
|
-
const rootCAPath =
|
|
6076
|
+
const rootCAPath = import_node_path16.default.join(runOrThrow("mkcert", ["-CAROOT"]), "rootCA.pem");
|
|
5924
6077
|
if (!import_node_fs16.default.existsSync(rootCAPath)) {
|
|
5925
6078
|
throw new Error(
|
|
5926
6079
|
`mkcert root CA was not found at '${rootCAPath}'. Run: ${tlsSetupCommand(options.repoPath)}`
|
|
@@ -5954,7 +6107,7 @@ function parseDnsHostsFromSubjectAltName(subjectAltName) {
|
|
|
5954
6107
|
return normalizeUniqueHosts(names);
|
|
5955
6108
|
}
|
|
5956
6109
|
function parseCertificateDnsHosts(pem) {
|
|
5957
|
-
const certificate = new
|
|
6110
|
+
const certificate = new import_node_crypto7.X509Certificate(pem);
|
|
5958
6111
|
const subjectAltName = certificate.subjectAltName ?? "";
|
|
5959
6112
|
if (subjectAltName.length === 0) {
|
|
5960
6113
|
return [];
|
|
@@ -6154,20 +6307,20 @@ Run: ${tlsSetupCommand(options.repoPath)}`
|
|
|
6154
6307
|
);
|
|
6155
6308
|
}
|
|
6156
6309
|
}
|
|
6157
|
-
var import_node_child_process8,
|
|
6310
|
+
var import_node_child_process8, import_node_crypto7, import_node_fs16, import_node_path16, DEFAULT_TLS_CERT_HOSTS, TLS_CERTIFICATE_LOCK_FILE, TLS_CERTIFICATE_LOCK_WAIT_MS, TLS_CERTIFICATE_WRITE_ATTEMPTS;
|
|
6158
6311
|
var init_tls = __esm({
|
|
6159
6312
|
"src/core/tls.ts"() {
|
|
6160
6313
|
"use strict";
|
|
6161
6314
|
import_node_child_process8 = require("child_process");
|
|
6162
|
-
|
|
6315
|
+
import_node_crypto7 = require("crypto");
|
|
6163
6316
|
import_node_fs16 = __toESM(require("fs"));
|
|
6164
|
-
|
|
6317
|
+
import_node_path16 = __toESM(require("path"));
|
|
6165
6318
|
init_docker();
|
|
6166
6319
|
init_file_lock();
|
|
6167
6320
|
init_host_routes();
|
|
6168
6321
|
init_router();
|
|
6169
6322
|
DEFAULT_TLS_CERT_HOSTS = ["localhost", "*.localhost"];
|
|
6170
|
-
TLS_CERTIFICATE_LOCK_FILE =
|
|
6323
|
+
TLS_CERTIFICATE_LOCK_FILE = import_node_path16.default.join(DEVROUTER_HOME, "tls-certificate.lock");
|
|
6171
6324
|
TLS_CERTIFICATE_LOCK_WAIT_MS = 6e4;
|
|
6172
6325
|
TLS_CERTIFICATE_WRITE_ATTEMPTS = 2;
|
|
6173
6326
|
}
|
|
@@ -6526,20 +6679,20 @@ function resetWorkspaceRuntimeCaches() {
|
|
|
6526
6679
|
cachedRuntime = void 0;
|
|
6527
6680
|
cachedSnapshots = void 0;
|
|
6528
6681
|
}
|
|
6529
|
-
var import_node_child_process11, import_node_fs17,
|
|
6682
|
+
var import_node_child_process11, import_node_fs17, import_node_path17, SUPPORTED_RUNTIMES2, RUNTIME_CONFIG_FILE, INACTIVITY_TIMEOUT_PATTERN, cachedRuntime, cachedSnapshots, UnsupportedWorkspaceRuntimeError, WorkspaceRuntimeOwnershipError;
|
|
6530
6683
|
var init_workspace_runtime = __esm({
|
|
6531
6684
|
"src/core/workspace-runtime.ts"() {
|
|
6532
6685
|
"use strict";
|
|
6533
6686
|
import_node_child_process11 = require("child_process");
|
|
6534
6687
|
import_node_fs17 = __toESM(require("fs"));
|
|
6535
|
-
|
|
6688
|
+
import_node_path17 = __toESM(require("path"));
|
|
6536
6689
|
init_atomic_file();
|
|
6537
6690
|
init_devpod_registry();
|
|
6538
6691
|
init_devsy_workspaces();
|
|
6539
6692
|
init_router();
|
|
6540
6693
|
init_workspace();
|
|
6541
6694
|
SUPPORTED_RUNTIMES2 = ["devpod", "devsy"];
|
|
6542
|
-
RUNTIME_CONFIG_FILE =
|
|
6695
|
+
RUNTIME_CONFIG_FILE = import_node_path17.default.join(DEVROUTER_HOME, "workspace-runtime.json");
|
|
6543
6696
|
INACTIVITY_TIMEOUT_PATTERN = /^(?:\d+(?:ms|s|m|h))+$/;
|
|
6544
6697
|
UnsupportedWorkspaceRuntimeError = class extends Error {
|
|
6545
6698
|
};
|
|
@@ -6617,7 +6770,7 @@ function parseMinimumNodeMajor(value) {
|
|
|
6617
6770
|
return Number(match[1]);
|
|
6618
6771
|
}
|
|
6619
6772
|
function readPackageJson(repoPath) {
|
|
6620
|
-
const packagePath =
|
|
6773
|
+
const packagePath = import_node_path18.default.join(repoPath, "package.json");
|
|
6621
6774
|
if (!import_node_fs18.default.existsSync(packagePath)) {
|
|
6622
6775
|
return void 0;
|
|
6623
6776
|
}
|
|
@@ -6762,13 +6915,13 @@ function buildGlobalToolChecks(repoPath) {
|
|
|
6762
6915
|
checks.push(nodeToolchainCheck(repoPath));
|
|
6763
6916
|
return checks;
|
|
6764
6917
|
}
|
|
6765
|
-
var import_node_child_process12, import_node_fs18,
|
|
6918
|
+
var import_node_child_process12, import_node_fs18, import_node_path18;
|
|
6766
6919
|
var init_tool_diagnostics = __esm({
|
|
6767
6920
|
"src/core/tool-diagnostics.ts"() {
|
|
6768
6921
|
"use strict";
|
|
6769
6922
|
import_node_child_process12 = require("child_process");
|
|
6770
6923
|
import_node_fs18 = __toESM(require("fs"));
|
|
6771
|
-
|
|
6924
|
+
import_node_path18 = __toESM(require("path"));
|
|
6772
6925
|
init_workspace_runtime();
|
|
6773
6926
|
}
|
|
6774
6927
|
});
|
|
@@ -6898,13 +7051,65 @@ function withMutationLock(activity, target, operation) {
|
|
|
6898
7051
|
import_node_fs19.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
6899
7052
|
return withFileLockSync(
|
|
6900
7053
|
DEVSY_MUTATION_LOCK_FILE,
|
|
6901
|
-
{
|
|
7054
|
+
{
|
|
7055
|
+
activity,
|
|
7056
|
+
target: `'${target}'`,
|
|
7057
|
+
waitMs: DEVSY_MUTATION_WAIT_MS,
|
|
7058
|
+
fair: true,
|
|
7059
|
+
onWait: createStderrWaitReporter(activity, `'${target}'`)
|
|
7060
|
+
},
|
|
7061
|
+
operation
|
|
7062
|
+
);
|
|
7063
|
+
}
|
|
7064
|
+
function withMutationLockAsync(activity, target, operation) {
|
|
7065
|
+
import_node_fs19.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
7066
|
+
return withFileLock(
|
|
7067
|
+
DEVSY_MUTATION_LOCK_FILE,
|
|
7068
|
+
{
|
|
7069
|
+
activity,
|
|
7070
|
+
target: `'${target}'`,
|
|
7071
|
+
waitMs: DEVSY_MUTATION_WAIT_MS,
|
|
7072
|
+
fair: true,
|
|
7073
|
+
onWait: createStderrWaitReporter(activity, `'${target}'`)
|
|
7074
|
+
},
|
|
6902
7075
|
operation
|
|
6903
7076
|
);
|
|
6904
7077
|
}
|
|
6905
7078
|
function commandFailure2(result) {
|
|
6906
7079
|
return [result.error?.message, result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
6907
7080
|
}
|
|
7081
|
+
function runDevsyUp(args, env, quiet) {
|
|
7082
|
+
return new Promise((resolve) => {
|
|
7083
|
+
const child = (0, import_node_child_process14.spawn)("devsy", args, {
|
|
7084
|
+
stdio: ["inherit", quiet ? 2 : "inherit", "pipe"],
|
|
7085
|
+
env
|
|
7086
|
+
});
|
|
7087
|
+
const stderr = child.stderr;
|
|
7088
|
+
if (!stderr) throw new Error("Devsy startup stderr pipe was not created.");
|
|
7089
|
+
let stderrTail = Buffer.alloc(0);
|
|
7090
|
+
let spawnError;
|
|
7091
|
+
stderr.on("data", (chunk) => {
|
|
7092
|
+
const value = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
7093
|
+
const writable = process.stderr.write(value);
|
|
7094
|
+
if (!writable) {
|
|
7095
|
+
stderr.pause();
|
|
7096
|
+
process.stderr.once("drain", () => stderr.resume());
|
|
7097
|
+
}
|
|
7098
|
+
if (value.length >= DEVSY_STDERR_TAIL_BYTES) {
|
|
7099
|
+
stderrTail = Buffer.from(value.subarray(value.length - DEVSY_STDERR_TAIL_BYTES));
|
|
7100
|
+
} else {
|
|
7101
|
+
const combined = Buffer.concat([stderrTail, value]);
|
|
7102
|
+
stderrTail = combined.length > DEVSY_STDERR_TAIL_BYTES ? combined.subarray(combined.length - DEVSY_STDERR_TAIL_BYTES) : combined;
|
|
7103
|
+
}
|
|
7104
|
+
});
|
|
7105
|
+
child.once("error", (error) => {
|
|
7106
|
+
spawnError = error;
|
|
7107
|
+
});
|
|
7108
|
+
child.once("close", (status) => {
|
|
7109
|
+
resolve({ status, error: spawnError, stderrTail: stderrTail.toString("utf-8") });
|
|
7110
|
+
});
|
|
7111
|
+
});
|
|
7112
|
+
}
|
|
6908
7113
|
function runDevsyAction(action2, devsyId, force = false) {
|
|
6909
7114
|
const args = action2 === "delete" ? ["delete", devsyId, ...force ? ["--force"] : [], "--ignore-not-found"] : ["stop", devsyId];
|
|
6910
7115
|
const result = (0, import_node_child_process14.spawnSync)("devsy", ["workspace", ...args], { encoding: "utf-8" });
|
|
@@ -6969,7 +7174,7 @@ function assertDevsyTarget(devsyId, repoPath) {
|
|
|
6969
7174
|
}
|
|
6970
7175
|
function startDevsyWorkspace(options) {
|
|
6971
7176
|
const activity = options.recreate ? "Devsy recreate" : "Devsy start";
|
|
6972
|
-
return
|
|
7177
|
+
return withMutationLockAsync(activity, options.repoPath, async () => {
|
|
6973
7178
|
let devsyId = assertDevsyTarget(options.devsyId, options.repoPath);
|
|
6974
7179
|
if (devsyId && options.recreate) {
|
|
6975
7180
|
const attached = listDevsyWorkspaces();
|
|
@@ -7009,12 +7214,13 @@ function startDevsyWorkspace(options) {
|
|
|
7009
7214
|
delete env.DEVROUTER_GIT_COMMON_DIR;
|
|
7010
7215
|
delete env.DEVCONTAINER_COMPOSE_OVERLAY;
|
|
7011
7216
|
}
|
|
7012
|
-
const result =
|
|
7013
|
-
stdio: options.quiet ? ["inherit", 2, "inherit"] : "inherit",
|
|
7014
|
-
env
|
|
7015
|
-
});
|
|
7217
|
+
const result = await runDevsyUp(args, env, options.quiet ?? false);
|
|
7016
7218
|
if (result.status !== 0) {
|
|
7017
|
-
|
|
7219
|
+
let message = `devsy workspace up failed for '${devsyId ?? options.repoPath}'.`;
|
|
7220
|
+
if (result.error?.message) message += ` ${result.error.message}`;
|
|
7221
|
+
if (AGENT_ACQUISITION_RE.test(result.stderrTail)) {
|
|
7222
|
+
message += " Devsy could not obtain its agent binary; allow Devsy to download it or set DEVSY_AGENT_BINARY to a verified official Devsy agent binary matching this platform and Devsy version.";
|
|
7223
|
+
}
|
|
7018
7224
|
if (failedStartMayHaveAttached(devsyId, options.repoPath)) {
|
|
7019
7225
|
throw new DevsyStartPostconditionError(message);
|
|
7020
7226
|
}
|
|
@@ -7040,20 +7246,22 @@ function startDevsyWorkspace(options) {
|
|
|
7040
7246
|
}
|
|
7041
7247
|
});
|
|
7042
7248
|
}
|
|
7043
|
-
var import_node_child_process14, import_node_fs19,
|
|
7249
|
+
var import_node_child_process14, import_node_fs19, import_node_path19, DEVSY_MUTATION_LOCK_FILE, DEVSY_MUTATION_WAIT_MS, DevsyStartPostconditionError, AGENT_ACQUISITION_RE, DEVSY_STDERR_TAIL_BYTES;
|
|
7044
7250
|
var init_devsy_mutation = __esm({
|
|
7045
7251
|
"src/core/devsy-mutation.ts"() {
|
|
7046
7252
|
"use strict";
|
|
7047
7253
|
import_node_child_process14 = require("child_process");
|
|
7048
7254
|
import_node_fs19 = __toESM(require("fs"));
|
|
7049
|
-
|
|
7255
|
+
import_node_path19 = __toESM(require("path"));
|
|
7050
7256
|
init_devsy_workspaces();
|
|
7051
7257
|
init_file_lock();
|
|
7052
7258
|
init_router();
|
|
7053
|
-
DEVSY_MUTATION_LOCK_FILE =
|
|
7054
|
-
DEVSY_MUTATION_WAIT_MS =
|
|
7259
|
+
DEVSY_MUTATION_LOCK_FILE = import_node_path19.default.join(DEVROUTER_HOME, "devsy-mutation.lock");
|
|
7260
|
+
DEVSY_MUTATION_WAIT_MS = 18e5;
|
|
7055
7261
|
DevsyStartPostconditionError = class extends Error {
|
|
7056
7262
|
};
|
|
7263
|
+
AGENT_ACQUISITION_RE = /inject agent.*agent binary not found/i;
|
|
7264
|
+
DEVSY_STDERR_TAIL_BYTES = 8192;
|
|
7057
7265
|
}
|
|
7058
7266
|
});
|
|
7059
7267
|
|
|
@@ -7062,7 +7270,13 @@ function withMutationLock2(activity, target, operation) {
|
|
|
7062
7270
|
import_node_fs20.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
7063
7271
|
return withFileLockSync(
|
|
7064
7272
|
DEVPOD_MUTATION_LOCK_FILE,
|
|
7065
|
-
{
|
|
7273
|
+
{
|
|
7274
|
+
activity,
|
|
7275
|
+
target: `'${target}'`,
|
|
7276
|
+
waitMs: DEVPOD_MUTATION_WAIT_MS,
|
|
7277
|
+
fair: true,
|
|
7278
|
+
onWait: createStderrWaitReporter(activity, `'${target}'`)
|
|
7279
|
+
},
|
|
7066
7280
|
operation
|
|
7067
7281
|
);
|
|
7068
7282
|
}
|
|
@@ -7137,10 +7351,10 @@ function deleteOwnedDevpodWorkspace(devpodId, worktreePath) {
|
|
|
7137
7351
|
resetWorkspaceRuntimeCaches();
|
|
7138
7352
|
return result;
|
|
7139
7353
|
}
|
|
7140
|
-
function startDevpodWorkspace(options) {
|
|
7354
|
+
async function startDevpodWorkspace(options) {
|
|
7141
7355
|
if (resolveWorkspaceRuntimeOrDefault(options.repoPath) === "devsy") {
|
|
7142
7356
|
try {
|
|
7143
|
-
const result = startDevsyWorkspace({
|
|
7357
|
+
const result = await startDevsyWorkspace({
|
|
7144
7358
|
repoPath: options.repoPath,
|
|
7145
7359
|
devsyId: options.devpodId,
|
|
7146
7360
|
devcontainerPath: options.devcontainerPath,
|
|
@@ -7227,20 +7441,20 @@ function startDevpodWorkspace(options) {
|
|
|
7227
7441
|
}
|
|
7228
7442
|
});
|
|
7229
7443
|
}
|
|
7230
|
-
var import_node_child_process15, import_node_fs20,
|
|
7444
|
+
var import_node_child_process15, import_node_fs20, import_node_path20, DEVPOD_MUTATION_LOCK_FILE, DEVPOD_MUTATION_WAIT_MS, DevpodStartPostconditionError;
|
|
7231
7445
|
var init_devpod_mutation = __esm({
|
|
7232
7446
|
"src/core/devpod-mutation.ts"() {
|
|
7233
7447
|
"use strict";
|
|
7234
7448
|
import_node_child_process15 = require("child_process");
|
|
7235
7449
|
import_node_fs20 = __toESM(require("fs"));
|
|
7236
|
-
|
|
7450
|
+
import_node_path20 = __toESM(require("path"));
|
|
7237
7451
|
init_devpod_workspaces();
|
|
7238
7452
|
init_devsy_mutation();
|
|
7239
7453
|
init_file_lock();
|
|
7240
7454
|
init_router();
|
|
7241
7455
|
init_workspace_runtime();
|
|
7242
|
-
DEVPOD_MUTATION_LOCK_FILE =
|
|
7243
|
-
DEVPOD_MUTATION_WAIT_MS =
|
|
7456
|
+
DEVPOD_MUTATION_LOCK_FILE = import_node_path20.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
|
|
7457
|
+
DEVPOD_MUTATION_WAIT_MS = 18e5;
|
|
7244
7458
|
DevpodStartPostconditionError = class extends Error {
|
|
7245
7459
|
};
|
|
7246
7460
|
}
|
|
@@ -7261,7 +7475,7 @@ function resolveGitCommonDir(repoPath) {
|
|
|
7261
7475
|
if (result.status !== 0 || !output2) {
|
|
7262
7476
|
throw commandError("Could not resolve the Git common directory", repoPath, result.stderr);
|
|
7263
7477
|
}
|
|
7264
|
-
return comparableWorkspacePath(
|
|
7478
|
+
return comparableWorkspacePath(import_node_path21.default.isAbsolute(output2) ? output2 : import_node_path21.default.resolve(repoPath, output2));
|
|
7265
7479
|
}
|
|
7266
7480
|
function resolveGitTopLevel(repoPath) {
|
|
7267
7481
|
const result = (0, import_node_child_process16.spawnSync)("git", ["-C", repoPath, "rev-parse", "--show-toplevel"], {
|
|
@@ -7312,7 +7526,7 @@ function listGitWorktrees(repoPath) {
|
|
|
7312
7526
|
return worktrees;
|
|
7313
7527
|
}
|
|
7314
7528
|
function ownershipDirectory(repoPath) {
|
|
7315
|
-
return
|
|
7529
|
+
return import_node_path21.default.join(resolveGitCommonDir(repoPath), OWNERSHIP_DIR);
|
|
7316
7530
|
}
|
|
7317
7531
|
function validateWorkspace(value, label) {
|
|
7318
7532
|
if (typeof value !== "string" || wsFromBranch(value) !== value) {
|
|
@@ -7340,7 +7554,7 @@ function validateRecord(value, expectedWorkspace) {
|
|
|
7340
7554
|
`workspace ownership file '${expectedWorkspace}' contains identity '${workspace}'`
|
|
7341
7555
|
);
|
|
7342
7556
|
}
|
|
7343
|
-
if (typeof candidate.worktreePath !== "string" || !
|
|
7557
|
+
if (typeof candidate.worktreePath !== "string" || !import_node_path21.default.isAbsolute(candidate.worktreePath)) {
|
|
7344
7558
|
throw new Error("invalid workspace ownership worktreePath");
|
|
7345
7559
|
}
|
|
7346
7560
|
if (candidate.branch !== null && typeof candidate.branch !== "string") {
|
|
@@ -7384,14 +7598,14 @@ function listWorkspaceOwnershipInDirectory(directory) {
|
|
|
7384
7598
|
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).sort((left, right) => left.name.localeCompare(right.name)).map((entry) => {
|
|
7385
7599
|
const workspace = entry.name.slice(0, -".json".length);
|
|
7386
7600
|
validateWorkspace(workspace, "filename");
|
|
7387
|
-
return readRecordFile(
|
|
7601
|
+
return readRecordFile(import_node_path21.default.join(directory, entry.name), workspace);
|
|
7388
7602
|
});
|
|
7389
7603
|
}
|
|
7390
7604
|
function writeWorkspaceOwnershipInDirectory(directory, input2) {
|
|
7391
7605
|
const workspace = validateWorkspace(input2.workspace, "workspace");
|
|
7392
7606
|
const devpodId = validateWorkspace(input2.devpodId, "devpodId");
|
|
7393
7607
|
const worktreePath = comparableWorkspacePath(input2.worktreePath);
|
|
7394
|
-
const filePath =
|
|
7608
|
+
const filePath = import_node_path21.default.join(directory, `${workspace}.json`);
|
|
7395
7609
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7396
7610
|
const records = listWorkspaceOwnershipInDirectory(directory);
|
|
7397
7611
|
const existing = records.find((record2) => record2.workspace === workspace);
|
|
@@ -7427,7 +7641,7 @@ function writeWorkspaceOwnershipInDirectory(directory, input2) {
|
|
|
7427
7641
|
return record;
|
|
7428
7642
|
}
|
|
7429
7643
|
function removeWorkspaceOwnershipInDirectory(directory, workspace) {
|
|
7430
|
-
const filePath =
|
|
7644
|
+
const filePath = import_node_path21.default.join(directory, `${validateWorkspace(workspace, "workspace")}.json`);
|
|
7431
7645
|
try {
|
|
7432
7646
|
import_node_fs21.default.rmSync(filePath);
|
|
7433
7647
|
return true;
|
|
@@ -7440,7 +7654,7 @@ function sameOwnershipRecord(left, right) {
|
|
|
7440
7654
|
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;
|
|
7441
7655
|
}
|
|
7442
7656
|
function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
|
|
7443
|
-
const filePath =
|
|
7657
|
+
const filePath = import_node_path21.default.join(
|
|
7444
7658
|
directory,
|
|
7445
7659
|
`${validateWorkspace(expected.workspace, "workspace")}.json`
|
|
7446
7660
|
);
|
|
@@ -7455,12 +7669,16 @@ function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
|
|
|
7455
7669
|
import_node_fs21.default.rmSync(filePath);
|
|
7456
7670
|
return "removed";
|
|
7457
7671
|
}
|
|
7458
|
-
function withWorkspaceOwnershipTransaction(repoPath, operation) {
|
|
7672
|
+
function withWorkspaceOwnershipTransaction(repoPath, operation, options = {}) {
|
|
7459
7673
|
const directory = ownershipDirectory(repoPath);
|
|
7460
7674
|
import_node_fs21.default.mkdirSync(directory, { recursive: true });
|
|
7461
7675
|
return withFileLockSync(
|
|
7462
|
-
|
|
7463
|
-
{
|
|
7676
|
+
import_node_path21.default.join(directory, ".lock"),
|
|
7677
|
+
{
|
|
7678
|
+
activity: "workspace ownership transaction",
|
|
7679
|
+
target: `'${repoPath}'`,
|
|
7680
|
+
waitMs: options.waitMs ?? 5e3
|
|
7681
|
+
},
|
|
7464
7682
|
() => operation({
|
|
7465
7683
|
list: () => listWorkspaceOwnershipInDirectory(directory),
|
|
7466
7684
|
write: (input2) => writeWorkspaceOwnershipInDirectory(directory, input2),
|
|
@@ -7469,8 +7687,169 @@ function withWorkspaceOwnershipTransaction(repoPath, operation) {
|
|
|
7469
7687
|
})
|
|
7470
7688
|
);
|
|
7471
7689
|
}
|
|
7472
|
-
function
|
|
7473
|
-
|
|
7690
|
+
function providerPathOwner(providerWorkspaces, worktreePath) {
|
|
7691
|
+
const owners = providerWorkspaces.filter(
|
|
7692
|
+
(workspace) => sameWorkspacePath(workspace.source.localFolder, worktreePath)
|
|
7693
|
+
);
|
|
7694
|
+
if (owners.length > 1) {
|
|
7695
|
+
throw new Error(
|
|
7696
|
+
`Worktree '${worktreePath}' is registered by multiple workspace runtimes; no identity was claimed.`
|
|
7697
|
+
);
|
|
7698
|
+
}
|
|
7699
|
+
return owners[0];
|
|
7700
|
+
}
|
|
7701
|
+
function persistedWorkspaceOwners(repoPath, worktreePath) {
|
|
7702
|
+
const owners = /* @__PURE__ */ new Map();
|
|
7703
|
+
for (const worktree of listGitWorktrees(repoPath)) {
|
|
7704
|
+
if (sameWorkspacePath(worktree.path, worktreePath)) continue;
|
|
7705
|
+
let workspace;
|
|
7706
|
+
try {
|
|
7707
|
+
workspace = readPersistedWorkspace(worktree.path);
|
|
7708
|
+
} catch (error) {
|
|
7709
|
+
if (!import_node_fs21.default.existsSync(worktree.path)) continue;
|
|
7710
|
+
throw error;
|
|
7711
|
+
}
|
|
7712
|
+
if (!workspace) continue;
|
|
7713
|
+
const existing = owners.get(workspace);
|
|
7714
|
+
if (existing && !sameWorkspacePath(existing, worktree.path)) {
|
|
7715
|
+
throw new Error(`Persisted workspace identity '${workspace}' belongs to multiple worktrees.`);
|
|
7716
|
+
}
|
|
7717
|
+
owners.set(workspace, worktree.path);
|
|
7718
|
+
}
|
|
7719
|
+
return owners;
|
|
7720
|
+
}
|
|
7721
|
+
function claimConflict(workspace, devpodId, worktreePath, records, providerWorkspaces, persistedOwners, exactRecord) {
|
|
7722
|
+
const recordOwner = records.find(
|
|
7723
|
+
(record) => record !== exactRecord && (record.workspace === workspace || record.devpodId === devpodId)
|
|
7724
|
+
);
|
|
7725
|
+
if (recordOwner) {
|
|
7726
|
+
return `workspace owner record '${recordOwner.workspace}' for '${recordOwner.worktreePath}'`;
|
|
7727
|
+
}
|
|
7728
|
+
const providerOwner = providerWorkspaces.find(
|
|
7729
|
+
(providerWorkspace) => providerWorkspace.id === devpodId && !sameWorkspacePath(providerWorkspace.source.localFolder, worktreePath)
|
|
7730
|
+
);
|
|
7731
|
+
if (providerOwner) {
|
|
7732
|
+
return `workspace runtime identity '${providerOwner.id}' already belongs to '${providerOwner.source.localFolder}'`;
|
|
7733
|
+
}
|
|
7734
|
+
const persistedOwner = persistedOwners.get(workspace);
|
|
7735
|
+
if (persistedOwner) {
|
|
7736
|
+
return `persisted checkout metadata for '${persistedOwner}'`;
|
|
7737
|
+
}
|
|
7738
|
+
return void 0;
|
|
7739
|
+
}
|
|
7740
|
+
function claimWorkspaceIdentity(repoPath, input2) {
|
|
7741
|
+
const worktreePath = comparableWorkspacePath(repoPath);
|
|
7742
|
+
const exactProvider = providerPathOwner(input2.providerWorkspaces, worktreePath);
|
|
7743
|
+
return withWorkspaceOwnershipTransaction(repoPath, (transaction) => {
|
|
7744
|
+
const records = transaction.list();
|
|
7745
|
+
const exactRecords = records.filter(
|
|
7746
|
+
(record) => sameWorkspacePath(record.worktreePath, worktreePath)
|
|
7747
|
+
);
|
|
7748
|
+
if (exactRecords.length > 1) {
|
|
7749
|
+
throw new Error(
|
|
7750
|
+
`Worktree '${worktreePath}' has multiple workspace owner records; no identity was claimed.`
|
|
7751
|
+
);
|
|
7752
|
+
}
|
|
7753
|
+
const exactRecord = exactRecords[0];
|
|
7754
|
+
const persisted = readPersistedWorkspace(worktreePath);
|
|
7755
|
+
const persistedOwners = persistedWorkspaceOwners(repoPath, worktreePath);
|
|
7756
|
+
if (exactRecord) {
|
|
7757
|
+
if (persisted && persisted !== exactRecord.workspace) {
|
|
7758
|
+
throw new Error(
|
|
7759
|
+
`Persisted workspace identity '${persisted}' disagrees with owner record '${exactRecord.workspace}'.`
|
|
7760
|
+
);
|
|
7761
|
+
}
|
|
7762
|
+
if (exactProvider && exactProvider.id !== exactRecord.devpodId) {
|
|
7763
|
+
throw new Error(
|
|
7764
|
+
`Workspace runtime '${exactProvider.id}' disagrees with owner record '${exactRecord.devpodId}'.`
|
|
7765
|
+
);
|
|
7766
|
+
}
|
|
7767
|
+
const conflict2 = claimConflict(
|
|
7768
|
+
exactRecord.workspace,
|
|
7769
|
+
exactRecord.devpodId,
|
|
7770
|
+
worktreePath,
|
|
7771
|
+
records,
|
|
7772
|
+
input2.providerWorkspaces,
|
|
7773
|
+
persistedOwners,
|
|
7774
|
+
exactRecord
|
|
7775
|
+
);
|
|
7776
|
+
if (conflict2) {
|
|
7777
|
+
throw new Error(
|
|
7778
|
+
`Workspace '${exactRecord.workspace}' conflicts with ${conflict2}; no identity was claimed.`
|
|
7779
|
+
);
|
|
7780
|
+
}
|
|
7781
|
+
if (!persisted) persistWorkspace(worktreePath, exactRecord.workspace);
|
|
7782
|
+
return transaction.write({
|
|
7783
|
+
workspace: exactRecord.workspace,
|
|
7784
|
+
worktreePath,
|
|
7785
|
+
branch: input2.branch ?? null,
|
|
7786
|
+
devpodId: exactRecord.devpodId
|
|
7787
|
+
});
|
|
7788
|
+
}
|
|
7789
|
+
if (persisted && exactProvider && persisted !== exactProvider.id) {
|
|
7790
|
+
throw new Error(
|
|
7791
|
+
`Persisted workspace identity '${persisted}' disagrees with workspace runtime '${exactProvider.id}'.`
|
|
7792
|
+
);
|
|
7793
|
+
}
|
|
7794
|
+
let workspace = persisted ?? exactProvider?.id;
|
|
7795
|
+
let devpodId = exactProvider?.id ?? persisted;
|
|
7796
|
+
if (!workspace || !devpodId) {
|
|
7797
|
+
if (input2.unavailableRuntimes.length > 0) {
|
|
7798
|
+
throw new Error(
|
|
7799
|
+
`Cannot claim a new workspace identity because these runtime registries are unavailable: ${input2.unavailableRuntimes.join(", ")}.`
|
|
7800
|
+
);
|
|
7801
|
+
}
|
|
7802
|
+
const candidate = workspaceIdentityCandidates(input2.source).find(
|
|
7803
|
+
(next) => !claimConflict(
|
|
7804
|
+
next,
|
|
7805
|
+
next,
|
|
7806
|
+
worktreePath,
|
|
7807
|
+
records,
|
|
7808
|
+
input2.providerWorkspaces,
|
|
7809
|
+
persistedOwners
|
|
7810
|
+
)
|
|
7811
|
+
);
|
|
7812
|
+
if (!candidate) {
|
|
7813
|
+
throw new Error(
|
|
7814
|
+
`Could not allocate a collision-safe workspace identity for '${worktreePath}'.`
|
|
7815
|
+
);
|
|
7816
|
+
}
|
|
7817
|
+
workspace = candidate;
|
|
7818
|
+
devpodId = candidate;
|
|
7819
|
+
}
|
|
7820
|
+
const conflict = claimConflict(
|
|
7821
|
+
workspace,
|
|
7822
|
+
devpodId,
|
|
7823
|
+
worktreePath,
|
|
7824
|
+
records,
|
|
7825
|
+
input2.providerWorkspaces,
|
|
7826
|
+
persistedOwners
|
|
7827
|
+
);
|
|
7828
|
+
if (conflict) {
|
|
7829
|
+
throw new Error(
|
|
7830
|
+
`Workspace '${workspace}' conflicts with ${conflict}; no identity was claimed.`
|
|
7831
|
+
);
|
|
7832
|
+
}
|
|
7833
|
+
const written = transaction.write({
|
|
7834
|
+
workspace,
|
|
7835
|
+
worktreePath,
|
|
7836
|
+
branch: input2.branch ?? null,
|
|
7837
|
+
devpodId
|
|
7838
|
+
});
|
|
7839
|
+
try {
|
|
7840
|
+
persistWorkspace(worktreePath, workspace);
|
|
7841
|
+
} catch (error) {
|
|
7842
|
+
const cleanup = transaction.removeIfMatches(written);
|
|
7843
|
+
if (cleanup !== "removed") {
|
|
7844
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
7845
|
+
throw new Error(
|
|
7846
|
+
`Could not persist workspace identity and owner-record rollback was '${cleanup}': ${detail}`
|
|
7847
|
+
);
|
|
7848
|
+
}
|
|
7849
|
+
throw error;
|
|
7850
|
+
}
|
|
7851
|
+
return written;
|
|
7852
|
+
});
|
|
7474
7853
|
}
|
|
7475
7854
|
function removeWorkspaceOwnership(repoPath, workspace) {
|
|
7476
7855
|
return withWorkspaceOwnershipTransaction(
|
|
@@ -7514,20 +7893,20 @@ function listMissingWorkspaceOwnership(repoPath) {
|
|
|
7514
7893
|
(record) => inspectWorkspaceOwnership(record, worktrees, void 0).ownerStatus === "missing"
|
|
7515
7894
|
);
|
|
7516
7895
|
}
|
|
7517
|
-
var import_node_child_process16, import_node_fs21,
|
|
7896
|
+
var import_node_child_process16, import_node_fs21, import_node_path21, READ_ONLY_GIT_ENV, OWNERSHIP_VERSION, OWNERSHIP_DIR;
|
|
7518
7897
|
var init_workspace_ownership = __esm({
|
|
7519
7898
|
"src/core/workspace-ownership.ts"() {
|
|
7520
7899
|
"use strict";
|
|
7521
7900
|
import_node_child_process16 = require("child_process");
|
|
7522
7901
|
import_node_fs21 = __toESM(require("fs"));
|
|
7523
|
-
|
|
7902
|
+
import_node_path21 = __toESM(require("path"));
|
|
7524
7903
|
init_atomic_file();
|
|
7525
7904
|
init_devpod_workspaces();
|
|
7526
7905
|
init_file_lock();
|
|
7527
7906
|
init_workspace();
|
|
7528
7907
|
READ_ONLY_GIT_ENV = { ...process.env, GIT_OPTIONAL_LOCKS: "0" };
|
|
7529
7908
|
OWNERSHIP_VERSION = 1;
|
|
7530
|
-
OWNERSHIP_DIR =
|
|
7909
|
+
OWNERSHIP_DIR = import_node_path21.default.join("devrouter", "workspaces");
|
|
7531
7910
|
}
|
|
7532
7911
|
});
|
|
7533
7912
|
|
|
@@ -7541,10 +7920,10 @@ function inRepositoryWorkspaceScope(repoPath, worktreePath, livePaths) {
|
|
|
7541
7920
|
if (livePaths.some((candidate) => sameWorkspacePath(candidate, worktreePath))) return true;
|
|
7542
7921
|
const comparableRepo = comparableWorkspacePath(repoPath);
|
|
7543
7922
|
const comparableWorktree = comparableWorkspacePath(worktreePath);
|
|
7544
|
-
const localRoot =
|
|
7923
|
+
const localRoot = import_node_path22.default.join(comparableRepo, "trees") + import_node_path22.default.sep;
|
|
7545
7924
|
if (comparableWorktree.startsWith(localRoot)) return true;
|
|
7546
|
-
const legacyPrefix = `${
|
|
7547
|
-
return
|
|
7925
|
+
const legacyPrefix = `${import_node_path22.default.basename(comparableRepo)}-`;
|
|
7926
|
+
return import_node_path22.default.dirname(comparableWorktree) === import_node_path22.default.dirname(comparableRepo) && import_node_path22.default.basename(comparableWorktree).startsWith(legacyPrefix);
|
|
7548
7927
|
}
|
|
7549
7928
|
function previewActions(devpodStatus, routeCount, includeRecord) {
|
|
7550
7929
|
const actions = [
|
|
@@ -7796,11 +8175,11 @@ function applyWorkspaceGc(plan) {
|
|
|
7796
8175
|
candidates
|
|
7797
8176
|
};
|
|
7798
8177
|
}
|
|
7799
|
-
var
|
|
8178
|
+
var import_node_path22;
|
|
7800
8179
|
var init_workspace_gc = __esm({
|
|
7801
8180
|
"src/core/workspace-gc.ts"() {
|
|
7802
8181
|
"use strict";
|
|
7803
|
-
|
|
8182
|
+
import_node_path22 = __toESM(require("path"));
|
|
7804
8183
|
init_devpod_mutation();
|
|
7805
8184
|
init_devpod_workspaces();
|
|
7806
8185
|
init_host_routes();
|
|
@@ -8153,7 +8532,7 @@ async function buildDoctorReport(options = {}) {
|
|
|
8153
8532
|
const config = runtimeConfig.config;
|
|
8154
8533
|
loadedConfig = config;
|
|
8155
8534
|
loadedWorkspace = runtimeConfig.workspace;
|
|
8156
|
-
const cliVersion = true ? "0.0.
|
|
8535
|
+
const cliVersion = true ? "0.0.46" : "0.0.0-dev";
|
|
8157
8536
|
const configVersion = config.devrouter?.version;
|
|
8158
8537
|
if (configVersion && cliVersion !== "0.0.0-dev" && compareSemver(configVersion, cliVersion) > 0) {
|
|
8159
8538
|
addCheck(checks, {
|
|
@@ -8183,7 +8562,7 @@ async function buildDoctorReport(options = {}) {
|
|
|
8183
8562
|
(app) => app.docker.composeFiles.map((filePath) => ({
|
|
8184
8563
|
app: app.name,
|
|
8185
8564
|
filePath,
|
|
8186
|
-
absolutePath:
|
|
8565
|
+
absolutePath: import_node_path23.default.resolve(repo.path, filePath)
|
|
8187
8566
|
}))
|
|
8188
8567
|
).filter((entry) => !import_node_fs22.default.existsSync(entry.absolutePath));
|
|
8189
8568
|
addCheck(checks, {
|
|
@@ -8228,7 +8607,7 @@ async function buildDoctorReport(options = {}) {
|
|
|
8228
8607
|
const missingHostCwds = config.apps.filter((app) => app.runtime === "host").map((app) => ({
|
|
8229
8608
|
app: app.name,
|
|
8230
8609
|
cwd: app.hostRun.cwd,
|
|
8231
|
-
absolutePath:
|
|
8610
|
+
absolutePath: import_node_path23.default.resolve(repo.path, app.hostRun.cwd)
|
|
8232
8611
|
})).filter((entry) => !import_node_fs22.default.existsSync(entry.absolutePath));
|
|
8233
8612
|
addCheck(checks, {
|
|
8234
8613
|
id: "repo.host-cwd",
|
|
@@ -8373,12 +8752,12 @@ async function buildDoctorReport(options = {}) {
|
|
|
8373
8752
|
nextSteps
|
|
8374
8753
|
};
|
|
8375
8754
|
}
|
|
8376
|
-
var import_node_fs22,
|
|
8755
|
+
var import_node_fs22, import_node_path23, import_yaml5, POSTGRES_DEFAULTS;
|
|
8377
8756
|
var init_doctor = __esm({
|
|
8378
8757
|
"src/core/doctor.ts"() {
|
|
8379
8758
|
"use strict";
|
|
8380
8759
|
import_node_fs22 = __toESM(require("fs"));
|
|
8381
|
-
|
|
8760
|
+
import_node_path23 = __toESM(require("path"));
|
|
8382
8761
|
import_yaml5 = __toESM(require("yaml"));
|
|
8383
8762
|
init_docker();
|
|
8384
8763
|
init_host_routes();
|
|
@@ -8873,11 +9252,11 @@ var init_route_publication = __esm({
|
|
|
8873
9252
|
// src/core/workspace-ensure.ts
|
|
8874
9253
|
function assertOverlay(container, repoPath) {
|
|
8875
9254
|
const workingDir = container.labels["com.docker.compose.project.working_dir"];
|
|
8876
|
-
if (!workingDir || !sameWorkspacePath(workingDir,
|
|
9255
|
+
if (!workingDir || !sameWorkspacePath(workingDir, import_node_path24.default.join(repoPath, ".devcontainer"))) {
|
|
8877
9256
|
throw new Error(`Container '${container.id}' does not belong to the exact worktree.`);
|
|
8878
9257
|
}
|
|
8879
9258
|
const configFiles = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").filter(Boolean);
|
|
8880
|
-
const expectedOverlay =
|
|
9259
|
+
const expectedOverlay = import_node_path24.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
|
|
8881
9260
|
if (!configFiles.some((configFile) => sameWorkspacePath(configFile, expectedOverlay))) {
|
|
8882
9261
|
throw new Error(`Container '${container.id}' was not started with ${DEVCONTAINER_OVERLAY}.`);
|
|
8883
9262
|
}
|
|
@@ -9053,6 +9432,20 @@ function isWarmWorkspaceActive(repoPath) {
|
|
|
9053
9432
|
return true;
|
|
9054
9433
|
}
|
|
9055
9434
|
}
|
|
9435
|
+
function hasExactManagedComposeProject(repoPath, state) {
|
|
9436
|
+
try {
|
|
9437
|
+
return inspectWorkspaceContainers({
|
|
9438
|
+
composeProject: state.composeProject
|
|
9439
|
+
}).some((container) => {
|
|
9440
|
+
const workingDir = container.labels["com.docker.compose.project.working_dir"];
|
|
9441
|
+
return Boolean(
|
|
9442
|
+
workingDir && sameWorkspacePath(workingDir, import_node_path24.default.join(repoPath, ".devcontainer"))
|
|
9443
|
+
);
|
|
9444
|
+
});
|
|
9445
|
+
} catch {
|
|
9446
|
+
return true;
|
|
9447
|
+
}
|
|
9448
|
+
}
|
|
9056
9449
|
function captureFirstTransitionBaseline(options) {
|
|
9057
9450
|
const containers = inspectWorkspaceContainers();
|
|
9058
9451
|
const runningPrimary = workspaceAppContainers(containers, options.repoPath).filter(
|
|
@@ -9229,25 +9622,30 @@ async function waitForHttpRoutes(repoPath, apps, timeoutMs) {
|
|
|
9229
9622
|
);
|
|
9230
9623
|
}
|
|
9231
9624
|
function resolveLinkedTarget(repoPath) {
|
|
9232
|
-
const
|
|
9233
|
-
const
|
|
9234
|
-
|
|
9235
|
-
|
|
9236
|
-
|
|
9237
|
-
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
|
|
9625
|
+
const snapshots = getWorkspaceRegistrySnapshots();
|
|
9626
|
+
const providerWorkspaces = [
|
|
9627
|
+
...snapshots.devpod ?? [],
|
|
9628
|
+
...snapshots.devsy?.map((workspace) => ({
|
|
9629
|
+
id: workspace.id,
|
|
9630
|
+
source: workspace.source,
|
|
9631
|
+
...workspace.lastUsed ? { lastUsed: workspace.lastUsed } : {},
|
|
9632
|
+
...workspace.lastUsedMalformed ? { lastUsedMalformed: true } : {}
|
|
9633
|
+
})) ?? []
|
|
9634
|
+
];
|
|
9635
|
+
const branch = currentBranch(repoPath);
|
|
9636
|
+
const claim = claimWorkspaceIdentity(repoPath, {
|
|
9637
|
+
source: branch ?? repoPath,
|
|
9638
|
+
branch: branch ?? null,
|
|
9639
|
+
providerWorkspaces,
|
|
9640
|
+
unavailableRuntimes: snapshots.unavailable
|
|
9641
|
+
});
|
|
9642
|
+
const existingDevpod = providerWorkspaces.find(
|
|
9643
|
+
(workspace) => workspace.id === claim.devpodId && sameWorkspacePath(workspace.source.localFolder, repoPath)
|
|
9241
9644
|
);
|
|
9242
|
-
if (otherOwner) {
|
|
9243
|
-
throw new Error(
|
|
9244
|
-
`DevPod identity '${candidate}' already belongs to '${otherOwner.source.localFolder}'.`
|
|
9245
|
-
);
|
|
9246
|
-
}
|
|
9247
9645
|
return {
|
|
9248
9646
|
kind: "linked",
|
|
9249
|
-
workspace:
|
|
9250
|
-
devpodId:
|
|
9647
|
+
workspace: claim.workspace,
|
|
9648
|
+
devpodId: claim.devpodId,
|
|
9251
9649
|
hadExactDevpod: Boolean(existingDevpod),
|
|
9252
9650
|
gitCommonDir: resolveGitCommonDir(repoPath)
|
|
9253
9651
|
};
|
|
@@ -9262,7 +9660,7 @@ function resolvePrimaryTarget(repoPath) {
|
|
|
9262
9660
|
}
|
|
9263
9661
|
function isPrimaryCheckout(repoPath) {
|
|
9264
9662
|
try {
|
|
9265
|
-
return import_node_fs23.default.statSync(
|
|
9663
|
+
return import_node_fs23.default.statSync(import_node_path24.default.join(repoPath, ".git")).isDirectory();
|
|
9266
9664
|
} catch {
|
|
9267
9665
|
return false;
|
|
9268
9666
|
}
|
|
@@ -9312,7 +9710,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
9312
9710
|
const target = linked ? resolveLinkedTarget(repoPath) : resolvePrimaryTarget(repoPath);
|
|
9313
9711
|
let devpodId = target.devpodId;
|
|
9314
9712
|
if (target.kind === "linked") {
|
|
9315
|
-
const overlayPath =
|
|
9713
|
+
const overlayPath = import_node_path24.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
|
|
9316
9714
|
if (!import_node_fs23.default.existsSync(overlayPath)) {
|
|
9317
9715
|
throw new Error(`Missing required DevPod compose overlay: ${overlayPath}`);
|
|
9318
9716
|
}
|
|
@@ -9330,6 +9728,9 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
9330
9728
|
const desiredProcesses = managedRuntime ? desiredManagedProcesses(managedRuntime, runtime.resolvedProfile) : [];
|
|
9331
9729
|
if (managedRuntime) {
|
|
9332
9730
|
previousManagedState = readManagedRuntimeState(repoPath, target.workspace);
|
|
9731
|
+
if (previousManagedState && !hasExactManagedComposeProject(repoPath, previousManagedState)) {
|
|
9732
|
+
previousManagedState = void 0;
|
|
9733
|
+
}
|
|
9333
9734
|
if (previousManagedState?.status === "degraded") {
|
|
9334
9735
|
throw new Error(
|
|
9335
9736
|
"Managed runtime state is degraded; refusing a new profile transition until drift is repaired."
|
|
@@ -9380,7 +9781,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
9380
9781
|
}
|
|
9381
9782
|
const apps = proxyAppsFromConfig(runtime.config);
|
|
9382
9783
|
const parsedUpstreams = apps.map((app) => parseUpstream(app.upstream));
|
|
9383
|
-
const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ??
|
|
9784
|
+
const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ?? import_node_path24.default.basename(repoPath)) ?? "app";
|
|
9384
9785
|
for (const [index, app] of apps.entries()) {
|
|
9385
9786
|
if (!parsedUpstreams[index].host.startsWith(`${aliasPrefix}-`)) {
|
|
9386
9787
|
const owner = target.kind === "linked" ? "workspace" : "checkout";
|
|
@@ -9394,20 +9795,11 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
9394
9795
|
managedConfigWritten = true;
|
|
9395
9796
|
}
|
|
9396
9797
|
const upstreamHosts = parsedUpstreams.map((upstream) => upstream.host);
|
|
9397
|
-
const ownership = target.kind === "linked" ? {
|
|
9398
|
-
workspace: target.workspace,
|
|
9399
|
-
worktreePath: repoPath,
|
|
9400
|
-
branch: currentBranch(repoPath),
|
|
9401
|
-
devpodId: target.devpodId
|
|
9402
|
-
} : void 0;
|
|
9403
|
-
if (ownership) {
|
|
9404
|
-
writeWorkspaceOwnership(repoPath, ownership);
|
|
9405
|
-
}
|
|
9406
9798
|
const currentTarget = () => target.kind === "linked" ? target : { ...target, devpodId };
|
|
9407
|
-
const startAndProveAttachment = (recreate = false) => {
|
|
9799
|
+
const startAndProveAttachment = async (recreate = false) => {
|
|
9408
9800
|
const requestedTarget = currentTarget();
|
|
9409
9801
|
try {
|
|
9410
|
-
devpodId = startDevpodWorkspace({
|
|
9802
|
+
devpodId = await startDevpodWorkspace({
|
|
9411
9803
|
repoPath,
|
|
9412
9804
|
devpodId: requestedTarget.devpodId,
|
|
9413
9805
|
devcontainerPath: managedPlan?.generatedRelativePath,
|
|
@@ -9425,19 +9817,16 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
9425
9817
|
if (error instanceof DevpodStartPostconditionError) environmentStarted = true;
|
|
9426
9818
|
throw error;
|
|
9427
9819
|
}
|
|
9428
|
-
if (ownership) {
|
|
9429
|
-
writeWorkspaceOwnership(repoPath, ownership);
|
|
9430
|
-
}
|
|
9431
9820
|
};
|
|
9432
9821
|
const preflight = (timeoutMs) => waitForContainerPreflight(repoPath, currentTarget(), upstreamHosts, timeoutMs);
|
|
9433
9822
|
const recreateAndPreflight = async () => {
|
|
9434
|
-
startAndProveAttachment(true);
|
|
9823
|
+
await startAndProveAttachment(true);
|
|
9435
9824
|
return preflight(options.containerTimeoutMs ?? DEFAULT_READINESS_TIMEOUT_MS);
|
|
9436
9825
|
};
|
|
9437
9826
|
let recreated = false;
|
|
9438
9827
|
let container;
|
|
9439
9828
|
try {
|
|
9440
|
-
startAndProveAttachment();
|
|
9829
|
+
await startAndProveAttachment();
|
|
9441
9830
|
} catch (error) {
|
|
9442
9831
|
if (managedPlan || !target.hadExactDevpod) {
|
|
9443
9832
|
throw error;
|
|
@@ -9824,13 +10213,13 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
9824
10213
|
}
|
|
9825
10214
|
});
|
|
9826
10215
|
}
|
|
9827
|
-
var import_node_child_process19, import_node_fs23,
|
|
10216
|
+
var import_node_child_process19, import_node_fs23, import_node_path24, DEVCONTAINER_OVERLAY, DEFAULT_READINESS_TIMEOUT_MS, POLL_INTERVAL_MS;
|
|
9828
10217
|
var init_workspace_ensure = __esm({
|
|
9829
10218
|
"src/core/workspace-ensure.ts"() {
|
|
9830
10219
|
"use strict";
|
|
9831
10220
|
import_node_child_process19 = require("child_process");
|
|
9832
10221
|
import_node_fs23 = __toESM(require("fs"));
|
|
9833
|
-
|
|
10222
|
+
import_node_path24 = __toESM(require("path"));
|
|
9834
10223
|
init_devcontainer_profile();
|
|
9835
10224
|
init_devpod_environment();
|
|
9836
10225
|
init_devpod_mutation();
|
|
@@ -9845,6 +10234,7 @@ var init_workspace_ensure = __esm({
|
|
|
9845
10234
|
init_router();
|
|
9846
10235
|
init_workspace();
|
|
9847
10236
|
init_workspace_ownership();
|
|
10237
|
+
init_workspace_runtime();
|
|
9848
10238
|
DEVCONTAINER_OVERLAY = "docker-compose.devrouter.yml";
|
|
9849
10239
|
DEFAULT_READINESS_TIMEOUT_MS = 12e4;
|
|
9850
10240
|
POLL_INTERVAL_MS = 1e3;
|
|
@@ -9901,6 +10291,11 @@ var init_ensure = __esm({
|
|
|
9901
10291
|
});
|
|
9902
10292
|
|
|
9903
10293
|
// src/core/workspace-lifecycle.ts
|
|
10294
|
+
function withWorkspaceAllocationTransaction(repoPath, operation) {
|
|
10295
|
+
return withWorkspaceOwnershipTransaction(repoPath, operation, {
|
|
10296
|
+
waitMs: WORKSPACE_ALLOCATION_LOCK_WAIT_MS
|
|
10297
|
+
});
|
|
10298
|
+
}
|
|
9904
10299
|
function warnMissingWorkspaceOwnership(repoPath) {
|
|
9905
10300
|
const missing = listMissingWorkspaceOwnership(repoPath);
|
|
9906
10301
|
if (missing.length === 0) return;
|
|
@@ -9910,7 +10305,7 @@ function warnMissingWorkspaceOwnership(repoPath) {
|
|
|
9910
10305
|
);
|
|
9911
10306
|
}
|
|
9912
10307
|
function defaultWorktreePath(mainRepo, ws) {
|
|
9913
|
-
return
|
|
10308
|
+
return import_node_path25.default.join(mainRepo, "trees", ws);
|
|
9914
10309
|
}
|
|
9915
10310
|
function assertDefaultWorktreeRootIgnored(mainRepo) {
|
|
9916
10311
|
const ignored = (0, import_node_child_process20.spawnSync)("git", ["-C", mainRepo, "check-ignore", "-q", "--no-index", "trees/"], {
|
|
@@ -9918,12 +10313,12 @@ function assertDefaultWorktreeRootIgnored(mainRepo) {
|
|
|
9918
10313
|
});
|
|
9919
10314
|
if (ignored.status !== 0) {
|
|
9920
10315
|
throw new Error(
|
|
9921
|
-
`Default worktree root '${
|
|
10316
|
+
`Default worktree root '${import_node_path25.default.join(mainRepo, "trees")}' is not ignored. Add 'trees/' to '${import_node_path25.default.join(mainRepo, ".gitignore")}' or use --path.`
|
|
9922
10317
|
);
|
|
9923
10318
|
}
|
|
9924
10319
|
}
|
|
9925
10320
|
function legacyDefaultWorktreePath(mainRepo, ws) {
|
|
9926
|
-
return
|
|
10321
|
+
return import_node_path25.default.join(import_node_path25.default.dirname(mainRepo), `${import_node_path25.default.basename(mainRepo)}-${ws}`);
|
|
9927
10322
|
}
|
|
9928
10323
|
function teardownFallbackPath(mainRepo, workspace) {
|
|
9929
10324
|
const candidates = [
|
|
@@ -10062,44 +10457,67 @@ async function workspaceUp(branch, opts = {}) {
|
|
|
10062
10457
|
if (!ws) {
|
|
10063
10458
|
throw new Error(`Branch '${branch}' does not yield a valid workspace token.`);
|
|
10064
10459
|
}
|
|
10065
|
-
const worktreePath =
|
|
10066
|
-
|
|
10067
|
-
const
|
|
10068
|
-
|
|
10460
|
+
const worktreePath = withWorkspaceAllocationTransaction(mainRepo, () => {
|
|
10461
|
+
const worktrees = listGitWorktrees(mainRepo);
|
|
10462
|
+
const branchWorktrees = worktrees.filter((worktree) => worktree.branch === branch);
|
|
10463
|
+
if (branchWorktrees.length > 1) {
|
|
10464
|
+
throw new Error(
|
|
10465
|
+
`Branch '${branch}' is checked out in multiple worktrees: ${branchWorktrees.map((worktree) => worktree.path).join(", ")}`
|
|
10466
|
+
);
|
|
10467
|
+
}
|
|
10468
|
+
const existingBranch = branchWorktrees[0];
|
|
10469
|
+
const candidatePaths = workspaceIdentityCandidates(branch).map(
|
|
10470
|
+
(candidate) => defaultWorktreePath(mainRepo, candidate)
|
|
10471
|
+
);
|
|
10472
|
+
const generatedPath = candidatePaths.find(
|
|
10473
|
+
(candidate) => !import_node_fs24.default.existsSync(candidate) && !worktrees.some((worktree) => sameWorkspacePath(worktree.path, candidate))
|
|
10069
10474
|
);
|
|
10070
|
-
|
|
10071
|
-
|
|
10475
|
+
const selectedPath = opts.path ? import_node_path25.default.resolve(opts.path) : existingBranch?.path ?? generatedPath;
|
|
10476
|
+
if (!selectedPath) {
|
|
10477
|
+
throw new Error(`Could not allocate a collision-safe worktree path for branch '${branch}'.`);
|
|
10072
10478
|
}
|
|
10073
|
-
if (
|
|
10479
|
+
if (existingBranch && !sameWorkspacePath(existingBranch.path, selectedPath)) {
|
|
10074
10480
|
throw new Error(
|
|
10075
|
-
`
|
|
10481
|
+
`Branch '${branch}' already uses worktree '${existingBranch.path}', not '${selectedPath}'.`
|
|
10076
10482
|
);
|
|
10077
10483
|
}
|
|
10078
|
-
|
|
10484
|
+
if (import_node_fs24.default.existsSync(selectedPath)) {
|
|
10485
|
+
const registered = worktrees.find(
|
|
10486
|
+
(worktree) => sameWorkspacePath(worktree.path, selectedPath)
|
|
10487
|
+
);
|
|
10488
|
+
if (!registered || sameWorkspacePath(registered.path, mainRepo)) {
|
|
10489
|
+
throw new Error(
|
|
10490
|
+
`Existing path '${selectedPath}' is not a linked worktree of '${mainRepo}'.`
|
|
10491
|
+
);
|
|
10492
|
+
}
|
|
10493
|
+
if (registered.branch !== branch) {
|
|
10494
|
+
throw new Error(
|
|
10495
|
+
`Existing worktree '${selectedPath}' uses branch '${registered.branch ?? "detached"}', not '${branch}'.`
|
|
10496
|
+
);
|
|
10497
|
+
}
|
|
10498
|
+
process.stdout.write(`Worktree already exists: ${selectedPath}
|
|
10079
10499
|
`);
|
|
10080
|
-
|
|
10081
|
-
if (!opts.path) {
|
|
10082
|
-
assertDefaultWorktreeRootIgnored(mainRepo);
|
|
10500
|
+
return selectedPath;
|
|
10083
10501
|
}
|
|
10084
|
-
|
|
10502
|
+
if (!opts.path) assertDefaultWorktreeRootIgnored(mainRepo);
|
|
10503
|
+
const add = (0, import_node_child_process20.spawnSync)("git", ["-C", mainRepo, "worktree", "add", selectedPath, branch], {
|
|
10085
10504
|
encoding: "utf-8"
|
|
10086
10505
|
});
|
|
10087
10506
|
if (add.status !== 0) {
|
|
10088
10507
|
const addNew = (0, import_node_child_process20.spawnSync)(
|
|
10089
10508
|
"git",
|
|
10090
|
-
["-C", mainRepo, "worktree", "add", "-b", branch,
|
|
10091
|
-
{
|
|
10092
|
-
encoding: "utf-8"
|
|
10093
|
-
}
|
|
10509
|
+
["-C", mainRepo, "worktree", "add", "-b", branch, selectedPath],
|
|
10510
|
+
{ encoding: "utf-8" }
|
|
10094
10511
|
);
|
|
10095
10512
|
if (addNew.status !== 0) {
|
|
10096
|
-
const detail = [add.stderr, addNew.stderr].map((
|
|
10513
|
+
const detail = [add.stderr, addNew.stderr].map((stderr) => stderr?.trim()).filter(Boolean).join("; ");
|
|
10097
10514
|
throw new Error(`git worktree add failed: ${detail || "unknown error"}`);
|
|
10098
10515
|
}
|
|
10099
10516
|
}
|
|
10100
|
-
process.stdout.write(`Created worktree ${
|
|
10517
|
+
process.stdout.write(`Created worktree ${selectedPath}
|
|
10101
10518
|
`);
|
|
10102
|
-
|
|
10519
|
+
return selectedPath;
|
|
10520
|
+
});
|
|
10103
10521
|
if (opts.noDevpod) {
|
|
10104
10522
|
warnMissingWorkspaceOwnership(mainRepo);
|
|
10105
10523
|
process.stdout.write("Skipped environment startup; no routes were changed.\n");
|
|
@@ -10242,13 +10660,13 @@ async function workspaceStop(target, opts = {}) {
|
|
|
10242
10660
|
async function workspaceDown(target, opts = {}) {
|
|
10243
10661
|
return runWorkspaceLifecycle("down", target, opts);
|
|
10244
10662
|
}
|
|
10245
|
-
var import_node_child_process20, import_node_fs24,
|
|
10663
|
+
var import_node_child_process20, import_node_fs24, import_node_path25, WORKSPACE_ALLOCATION_LOCK_WAIT_MS;
|
|
10246
10664
|
var init_workspace_lifecycle = __esm({
|
|
10247
10665
|
"src/core/workspace-lifecycle.ts"() {
|
|
10248
10666
|
"use strict";
|
|
10249
10667
|
import_node_child_process20 = require("child_process");
|
|
10250
10668
|
import_node_fs24 = __toESM(require("fs"));
|
|
10251
|
-
|
|
10669
|
+
import_node_path25 = __toESM(require("path"));
|
|
10252
10670
|
init_devpod_mutation();
|
|
10253
10671
|
init_devpod_workspaces();
|
|
10254
10672
|
init_repo_config();
|
|
@@ -10256,6 +10674,7 @@ var init_workspace_lifecycle = __esm({
|
|
|
10256
10674
|
init_workspace();
|
|
10257
10675
|
init_workspace_ensure();
|
|
10258
10676
|
init_workspace_ownership();
|
|
10677
|
+
WORKSPACE_ALLOCATION_LOCK_WAIT_MS = 6e4;
|
|
10259
10678
|
}
|
|
10260
10679
|
});
|
|
10261
10680
|
|
|
@@ -10449,7 +10868,7 @@ async function devpodExec(repoPath, command) {
|
|
|
10449
10868
|
}
|
|
10450
10869
|
assertRunningDevpod(devpod.id, repoPath);
|
|
10451
10870
|
const workspaceDirectory = resolveWorkspaceDirectory(repoPath);
|
|
10452
|
-
const statusMarker = `__DEVROUTER_EXIT_${(0,
|
|
10871
|
+
const statusMarker = `__DEVROUTER_EXIT_${(0, import_node_crypto8.randomUUID)()}__:`;
|
|
10453
10872
|
const statusMarkerBytes = Buffer.from(statusMarker, "ascii");
|
|
10454
10873
|
const literalCommand = command.map(quotePosixArg).join(" ");
|
|
10455
10874
|
const wrappedCommand = `${literalCommand}; __devrouter_status=$?; printf '${statusMarker}%s\\n' "$__devrouter_status" >&2; exit 0`;
|
|
@@ -10526,12 +10945,12 @@ async function devpodExec(repoPath, command) {
|
|
|
10526
10945
|
});
|
|
10527
10946
|
});
|
|
10528
10947
|
}
|
|
10529
|
-
var import_node_child_process22,
|
|
10948
|
+
var import_node_child_process22, import_node_crypto8, DEVPOD_MISSING_EXIT_STATUS_DIAGNOSTIC;
|
|
10530
10949
|
var init_devpod_exec = __esm({
|
|
10531
10950
|
"src/core/devpod-exec.ts"() {
|
|
10532
10951
|
"use strict";
|
|
10533
10952
|
import_node_child_process22 = require("child_process");
|
|
10534
|
-
|
|
10953
|
+
import_node_crypto8 = require("crypto");
|
|
10535
10954
|
init_devpod_environment();
|
|
10536
10955
|
init_devpod_workspaces();
|
|
10537
10956
|
init_devsy_exec();
|
|
@@ -10826,7 +11245,7 @@ function readJson(filePath) {
|
|
|
10826
11245
|
}
|
|
10827
11246
|
}
|
|
10828
11247
|
function relative(repoPath, filePath) {
|
|
10829
|
-
return
|
|
11248
|
+
return import_node_path26.default.relative(repoPath, filePath) || ".";
|
|
10830
11249
|
}
|
|
10831
11250
|
function redactEnvAssignments(value) {
|
|
10832
11251
|
return value.replace(
|
|
@@ -10867,7 +11286,7 @@ function inspectPackageManager(repoPath, pkg) {
|
|
|
10867
11286
|
["bun.lock", "bun"]
|
|
10868
11287
|
];
|
|
10869
11288
|
for (const [fileName, name] of lockfiles) {
|
|
10870
|
-
if (import_node_fs25.default.existsSync(
|
|
11289
|
+
if (import_node_fs25.default.existsSync(import_node_path26.default.join(repoPath, fileName))) {
|
|
10871
11290
|
return { name, source: fileName };
|
|
10872
11291
|
}
|
|
10873
11292
|
}
|
|
@@ -10882,7 +11301,7 @@ function inspectNode(repoPath, pkg) {
|
|
|
10882
11301
|
if (typeof engines?.node === "string") {
|
|
10883
11302
|
return { version: engines.node, source: "package.json:engines.node" };
|
|
10884
11303
|
}
|
|
10885
|
-
const nvmrc =
|
|
11304
|
+
const nvmrc = import_node_path26.default.join(repoPath, ".nvmrc");
|
|
10886
11305
|
if (import_node_fs25.default.existsSync(nvmrc)) {
|
|
10887
11306
|
const version = import_node_fs25.default.readFileSync(nvmrc, "utf-8").trim();
|
|
10888
11307
|
return { version, source: ".nvmrc" };
|
|
@@ -10945,7 +11364,7 @@ function configuredComposeFiles(repoPath) {
|
|
|
10945
11364
|
const files = config.apps.filter(
|
|
10946
11365
|
(app) => app.runtime === "docker"
|
|
10947
11366
|
).flatMap((app) => app.docker.composeFiles).filter(
|
|
10948
|
-
(fileName) => !
|
|
11367
|
+
(fileName) => !import_node_path26.default.isAbsolute(fileName) && !import_node_path26.default.normalize(fileName).startsWith("..")
|
|
10949
11368
|
);
|
|
10950
11369
|
return Array.from(new Set(files));
|
|
10951
11370
|
} catch {
|
|
@@ -10963,7 +11382,7 @@ function composeFiles(repoPath) {
|
|
|
10963
11382
|
...configuredComposeFiles(repoPath)
|
|
10964
11383
|
];
|
|
10965
11384
|
return Array.from(new Set(candidates)).filter(
|
|
10966
|
-
(fileName) => import_node_fs25.default.existsSync(
|
|
11385
|
+
(fileName) => import_node_fs25.default.existsSync(import_node_path26.default.join(repoPath, fileName))
|
|
10967
11386
|
);
|
|
10968
11387
|
}
|
|
10969
11388
|
function stringArray2(value) {
|
|
@@ -11001,7 +11420,7 @@ function inspectServices(repoPath) {
|
|
|
11001
11420
|
const services = [];
|
|
11002
11421
|
for (const fileName of composeFiles(repoPath)) {
|
|
11003
11422
|
try {
|
|
11004
|
-
const parsed = import_yaml6.default.parse(import_node_fs25.default.readFileSync(
|
|
11423
|
+
const parsed = import_yaml6.default.parse(import_node_fs25.default.readFileSync(import_node_path26.default.join(repoPath, fileName), "utf-8"));
|
|
11005
11424
|
const serviceMap = asRecord3(asRecord3(parsed)?.services);
|
|
11006
11425
|
for (const [name, value] of Object.entries(serviceMap ?? {})) {
|
|
11007
11426
|
const service = asRecord3(value);
|
|
@@ -11037,7 +11456,7 @@ function inspectServices(repoPath) {
|
|
|
11037
11456
|
}
|
|
11038
11457
|
function inspectEnvFiles(repoPath) {
|
|
11039
11458
|
const files = import_node_fs25.default.readdirSync(repoPath).filter((fileName) => /^\.env(\.|$)/.test(fileName)).sort().map((fileName) => {
|
|
11040
|
-
const content = import_node_fs25.default.readFileSync(
|
|
11459
|
+
const content = import_node_fs25.default.readFileSync(import_node_path26.default.join(repoPath, fileName), "utf-8");
|
|
11041
11460
|
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();
|
|
11042
11461
|
return { path: fileName, names };
|
|
11043
11462
|
});
|
|
@@ -11051,13 +11470,13 @@ function inspectEnvFiles(repoPath) {
|
|
|
11051
11470
|
};
|
|
11052
11471
|
}
|
|
11053
11472
|
function inspectDevcontainer(repoPath) {
|
|
11054
|
-
const dir =
|
|
11473
|
+
const dir = import_node_path26.default.join(repoPath, ".devcontainer");
|
|
11055
11474
|
if (!import_node_fs25.default.existsSync(dir)) {
|
|
11056
11475
|
return { exists: false, files: [] };
|
|
11057
11476
|
}
|
|
11058
11477
|
return {
|
|
11059
11478
|
exists: true,
|
|
11060
|
-
files: import_node_fs25.default.readdirSync(dir).filter((fileName) => import_node_fs25.default.statSync(
|
|
11479
|
+
files: import_node_fs25.default.readdirSync(dir).filter((fileName) => import_node_fs25.default.statSync(import_node_path26.default.join(dir, fileName)).isFile()).sort().map((fileName) => `.devcontainer/${fileName}`)
|
|
11061
11480
|
};
|
|
11062
11481
|
}
|
|
11063
11482
|
function inspectDevrouter(repoPath) {
|
|
@@ -11106,14 +11525,14 @@ function inspectAgentGuidance(repoPath) {
|
|
|
11106
11525
|
["AGENTS.md", "agents"],
|
|
11107
11526
|
["CLAUDE.md", "claude"]
|
|
11108
11527
|
]) {
|
|
11109
|
-
if (import_node_fs25.default.existsSync(
|
|
11528
|
+
if (import_node_fs25.default.existsSync(import_node_path26.default.join(repoPath, fileName))) {
|
|
11110
11529
|
results.push({ path: fileName, kind });
|
|
11111
11530
|
}
|
|
11112
11531
|
}
|
|
11113
|
-
const skillsDir =
|
|
11532
|
+
const skillsDir = import_node_path26.default.join(repoPath, ".agents", "skills");
|
|
11114
11533
|
if (import_node_fs25.default.existsSync(skillsDir)) {
|
|
11115
11534
|
for (const name of import_node_fs25.default.readdirSync(skillsDir).sort()) {
|
|
11116
|
-
const skillPath =
|
|
11535
|
+
const skillPath = import_node_path26.default.join(skillsDir, name, "SKILL.md");
|
|
11117
11536
|
if (import_node_fs25.default.existsSync(skillPath)) {
|
|
11118
11537
|
results.push({ path: relative(repoPath, skillPath), kind: "skill" });
|
|
11119
11538
|
}
|
|
@@ -11157,7 +11576,7 @@ function buildIssues(report) {
|
|
|
11157
11576
|
}
|
|
11158
11577
|
function inspectRepo(options = {}) {
|
|
11159
11578
|
const repoPath = resolveRepoPath(options.repo);
|
|
11160
|
-
const pkg = readJson(
|
|
11579
|
+
const pkg = readJson(import_node_path26.default.join(repoPath, "package.json"));
|
|
11161
11580
|
const scripts = inspectScripts(pkg);
|
|
11162
11581
|
const reportWithoutIssues = {
|
|
11163
11582
|
repoPath,
|
|
@@ -11176,12 +11595,12 @@ function inspectRepo(options = {}) {
|
|
|
11176
11595
|
issues: buildIssues(reportWithoutIssues)
|
|
11177
11596
|
};
|
|
11178
11597
|
}
|
|
11179
|
-
var import_node_fs25,
|
|
11598
|
+
var import_node_fs25, import_node_path26, import_yaml6;
|
|
11180
11599
|
var init_repo_inspect = __esm({
|
|
11181
11600
|
"src/core/repo-inspect.ts"() {
|
|
11182
11601
|
"use strict";
|
|
11183
11602
|
import_node_fs25 = __toESM(require("fs"));
|
|
11184
|
-
|
|
11603
|
+
import_node_path26 = __toESM(require("path"));
|
|
11185
11604
|
import_yaml6 = __toESM(require("yaml"));
|
|
11186
11605
|
init_repo_config();
|
|
11187
11606
|
}
|
|
@@ -11286,7 +11705,7 @@ function requiredFileChecks(repoPath) {
|
|
|
11286
11705
|
".devcontainer/docker-compose.yml",
|
|
11287
11706
|
".devrouter.yml"
|
|
11288
11707
|
];
|
|
11289
|
-
const missing = required.filter((fileName) => !import_node_fs26.default.existsSync(
|
|
11708
|
+
const missing = required.filter((fileName) => !import_node_fs26.default.existsSync(import_node_path27.default.join(repoPath, fileName)));
|
|
11290
11709
|
return {
|
|
11291
11710
|
id: "repo.devcontainer.verify-files",
|
|
11292
11711
|
level: missing.length === 0 ? "ok" : "error",
|
|
@@ -11505,12 +11924,12 @@ async function verifyDevcontainer(options = {}) {
|
|
|
11505
11924
|
nextSteps: collectNextSteps3(checks)
|
|
11506
11925
|
};
|
|
11507
11926
|
}
|
|
11508
|
-
var import_node_fs26,
|
|
11927
|
+
var import_node_fs26, import_node_path27;
|
|
11509
11928
|
var init_devcontainer_verify = __esm({
|
|
11510
11929
|
"src/core/devcontainer-verify.ts"() {
|
|
11511
11930
|
"use strict";
|
|
11512
11931
|
import_node_fs26 = __toESM(require("fs"));
|
|
11513
|
-
|
|
11932
|
+
import_node_path27 = __toESM(require("path"));
|
|
11514
11933
|
init_capabilities();
|
|
11515
11934
|
init_doctor();
|
|
11516
11935
|
init_host_routes();
|
|
@@ -11807,7 +12226,7 @@ function packageManagerIssues(repo) {
|
|
|
11807
12226
|
}
|
|
11808
12227
|
function plannedFiles(repoPath, version) {
|
|
11809
12228
|
const repo = inspectRepo({ repo: repoPath });
|
|
11810
|
-
const projectName = sanitizeProjectName(
|
|
12229
|
+
const projectName = sanitizeProjectName(import_node_path28.default.basename(repo.repoPath));
|
|
11811
12230
|
const nodeMajor = majorVersion(repo.node?.version, "24");
|
|
11812
12231
|
const pnpmVersion = repo.packageManager?.name === "pnpm" && repo.packageManager.version ? repo.packageManager.version : DEFAULT_PNPM_VERSION;
|
|
11813
12232
|
const port = inferPort2(repo);
|
|
@@ -11852,7 +12271,7 @@ function plannedFiles(repoPath, version) {
|
|
|
11852
12271
|
};
|
|
11853
12272
|
}
|
|
11854
12273
|
function classifyFile(repoPath, file) {
|
|
11855
|
-
const absolutePath =
|
|
12274
|
+
const absolutePath = import_node_path28.default.join(repoPath, file.relativePath);
|
|
11856
12275
|
if (!import_node_fs27.default.existsSync(absolutePath)) {
|
|
11857
12276
|
return {
|
|
11858
12277
|
path: file.relativePath,
|
|
@@ -11918,8 +12337,8 @@ function buildPlan(repoPath, dryRun, version) {
|
|
|
11918
12337
|
};
|
|
11919
12338
|
}
|
|
11920
12339
|
function writeFile(repoPath, file) {
|
|
11921
|
-
const absolutePath =
|
|
11922
|
-
import_node_fs27.default.mkdirSync(
|
|
12340
|
+
const absolutePath = import_node_path28.default.join(repoPath, file.relativePath);
|
|
12341
|
+
import_node_fs27.default.mkdirSync(import_node_path28.default.dirname(absolutePath), { recursive: true });
|
|
11923
12342
|
import_node_fs27.default.writeFileSync(absolutePath, file.content, "utf-8");
|
|
11924
12343
|
if (file.executable) {
|
|
11925
12344
|
import_node_fs27.default.chmodSync(absolutePath, 493);
|
|
@@ -11960,12 +12379,12 @@ function writeDevcontainer(options = {}) {
|
|
|
11960
12379
|
nextSteps: postWriteNextSteps(repoPath)
|
|
11961
12380
|
};
|
|
11962
12381
|
}
|
|
11963
|
-
var import_node_fs27,
|
|
12382
|
+
var import_node_fs27, import_node_path28, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
|
|
11964
12383
|
var init_devcontainer_write = __esm({
|
|
11965
12384
|
"src/core/devcontainer-write.ts"() {
|
|
11966
12385
|
"use strict";
|
|
11967
12386
|
import_node_fs27 = __toESM(require("fs"));
|
|
11968
|
-
|
|
12387
|
+
import_node_path28 = __toESM(require("path"));
|
|
11969
12388
|
init_repo_config();
|
|
11970
12389
|
init_repo_inspect();
|
|
11971
12390
|
MANAGED_MARKER2 = "devrouter:managed devcontainer";
|
|
@@ -12356,7 +12775,7 @@ function sanitizeRouterId(value) {
|
|
|
12356
12775
|
return value.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
12357
12776
|
}
|
|
12358
12777
|
function repoHash(repoPath) {
|
|
12359
|
-
return (0,
|
|
12778
|
+
return (0, import_node_crypto9.createHash)("sha1").update(import_node_path29.default.resolve(repoPath)).digest("hex").slice(0, 12);
|
|
12360
12779
|
}
|
|
12361
12780
|
function asDockerApp(app) {
|
|
12362
12781
|
return app.runtime === "docker";
|
|
@@ -12435,9 +12854,9 @@ function prepareDockerOverlay(repoPath, appName, apps, publishTcpPorts = false)
|
|
|
12435
12854
|
if (dockerApps.length === 0) {
|
|
12436
12855
|
throw new Error("No docker apps selected to prepare compose overlay.");
|
|
12437
12856
|
}
|
|
12438
|
-
const cachePath =
|
|
12857
|
+
const cachePath = import_node_path29.default.join(CACHE_DIR, repoHash(repoPath), sanitizeRouterId(appName));
|
|
12439
12858
|
import_node_fs28.default.mkdirSync(cachePath, { recursive: true });
|
|
12440
|
-
const overlayPath =
|
|
12859
|
+
const overlayPath = import_node_path29.default.join(cachePath, "compose.devrouter.yml");
|
|
12441
12860
|
const overlayDocument = buildOverlayDocument(dockerApps, publishTcpPorts);
|
|
12442
12861
|
import_node_fs28.default.writeFileSync(overlayPath, import_yaml7.default.stringify(overlayDocument, { lineWidth: 0 }), "utf-8");
|
|
12443
12862
|
return {
|
|
@@ -12559,14 +12978,14 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
|
|
|
12559
12978
|
const port = Number(match[1]);
|
|
12560
12979
|
return Number.isInteger(port) && port > 0 ? port : void 0;
|
|
12561
12980
|
}
|
|
12562
|
-
var import_node_child_process25,
|
|
12981
|
+
var import_node_child_process25, import_node_crypto9, import_node_fs28, import_node_path29, import_yaml7;
|
|
12563
12982
|
var init_docker_run = __esm({
|
|
12564
12983
|
"src/core/docker-run.ts"() {
|
|
12565
12984
|
"use strict";
|
|
12566
12985
|
import_node_child_process25 = require("child_process");
|
|
12567
|
-
|
|
12986
|
+
import_node_crypto9 = require("crypto");
|
|
12568
12987
|
import_node_fs28 = __toESM(require("fs"));
|
|
12569
|
-
|
|
12988
|
+
import_node_path29 = __toESM(require("path"));
|
|
12570
12989
|
import_yaml7 = __toESM(require("yaml"));
|
|
12571
12990
|
init_docker_error_guidance();
|
|
12572
12991
|
init_paths();
|
|
@@ -13420,7 +13839,7 @@ function measureWorktreeConsumption(worktreePath, options) {
|
|
|
13420
13839
|
timedOut = true;
|
|
13421
13840
|
break;
|
|
13422
13841
|
}
|
|
13423
|
-
const entryPath =
|
|
13842
|
+
const entryPath = import_node_path30.default.join(dirPath, entry.name);
|
|
13424
13843
|
let entryStat;
|
|
13425
13844
|
try {
|
|
13426
13845
|
entryStat = import_node_fs29.default.lstatSync(entryPath);
|
|
@@ -13497,12 +13916,12 @@ function describeError(error, worktreePath) {
|
|
|
13497
13916
|
}
|
|
13498
13917
|
return `could not stat worktree path '${worktreePath}': ${error?.message ?? String(error)}`;
|
|
13499
13918
|
}
|
|
13500
|
-
var import_node_fs29,
|
|
13919
|
+
var import_node_fs29, import_node_path30, DEFAULT_DEADLINE_MS, BLOCK_SIZE_BYTES;
|
|
13501
13920
|
var init_workspace_consumption = __esm({
|
|
13502
13921
|
"src/core/workspace-consumption.ts"() {
|
|
13503
13922
|
"use strict";
|
|
13504
13923
|
import_node_fs29 = __toESM(require("fs"));
|
|
13505
|
-
|
|
13924
|
+
import_node_path30 = __toESM(require("path"));
|
|
13506
13925
|
init_devpod_environment();
|
|
13507
13926
|
DEFAULT_DEADLINE_MS = 1e4;
|
|
13508
13927
|
BLOCK_SIZE_BYTES = 512;
|
|
@@ -14128,7 +14547,7 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
|
|
|
14128
14547
|
containers = measureContainersFn(worktreePaths);
|
|
14129
14548
|
} catch (error) {
|
|
14130
14549
|
const reason = `container measurement failed: ${describeCause(error)}`;
|
|
14131
|
-
containers = new Map(worktreePaths.map((
|
|
14550
|
+
containers = new Map(worktreePaths.map((path30) => [path30, unknownContainers(reason)]));
|
|
14132
14551
|
}
|
|
14133
14552
|
}
|
|
14134
14553
|
const rows = records.map((record) => {
|
|
@@ -14359,7 +14778,7 @@ var init_version = __esm({
|
|
|
14359
14778
|
|
|
14360
14779
|
// src/cli.ts
|
|
14361
14780
|
var import_commander = require("commander");
|
|
14362
|
-
var CLI_VERSION = true ? "0.0.
|
|
14781
|
+
var CLI_VERSION = true ? "0.0.46" : "0.0.0-dev";
|
|
14363
14782
|
var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
|
|
14364
14783
|
function withErrorHandling(action2) {
|
|
14365
14784
|
return async (...args) => {
|