@devrouter/cli 0.0.45 → 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 +391 -181
- package/package.json +1 -1
- package/upgrade-prompts/0.0.46.md +41 -0
package/dist/devrouter.js
CHANGED
|
@@ -322,7 +322,7 @@ Run several worktrees of one repo in parallel without host/route collisions. A *
|
|
|
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 {
|
|
@@ -1073,7 +1197,7 @@ function workspaceIdentityCandidates(source) {
|
|
|
1073
1197
|
return candidates;
|
|
1074
1198
|
}
|
|
1075
1199
|
function isLinkedWorktree(repoPath) {
|
|
1076
|
-
const gitPath =
|
|
1200
|
+
const gitPath = import_node_path5.default.join(repoPath, ".git");
|
|
1077
1201
|
let stat;
|
|
1078
1202
|
try {
|
|
1079
1203
|
stat = import_node_fs5.default.statSync(gitPath);
|
|
@@ -1097,7 +1221,7 @@ function isLinkedWorktree(repoPath) {
|
|
|
1097
1221
|
return /(^|\/)worktrees\//.test(gitdir);
|
|
1098
1222
|
}
|
|
1099
1223
|
function gitMetadataDir(repoPath) {
|
|
1100
|
-
const gitPath =
|
|
1224
|
+
const gitPath = import_node_path5.default.join(repoPath, ".git");
|
|
1101
1225
|
let stat;
|
|
1102
1226
|
try {
|
|
1103
1227
|
stat = import_node_fs5.default.statSync(gitPath);
|
|
@@ -1114,7 +1238,7 @@ function gitMetadataDir(repoPath) {
|
|
|
1114
1238
|
if (!match) {
|
|
1115
1239
|
return void 0;
|
|
1116
1240
|
}
|
|
1117
|
-
return
|
|
1241
|
+
return import_node_path5.default.resolve(repoPath, match[1].trim());
|
|
1118
1242
|
}
|
|
1119
1243
|
function validatePersistedWorkspace(value) {
|
|
1120
1244
|
const workspace = value.trim();
|
|
@@ -1128,7 +1252,7 @@ function readPersistedWorkspace(repoPath) {
|
|
|
1128
1252
|
if (!gitDir) {
|
|
1129
1253
|
return void 0;
|
|
1130
1254
|
}
|
|
1131
|
-
const metadataPath =
|
|
1255
|
+
const metadataPath = import_node_path5.default.join(gitDir, WORKSPACE_METADATA_FILE);
|
|
1132
1256
|
try {
|
|
1133
1257
|
return validatePersistedWorkspace(import_node_fs5.default.readFileSync(metadataPath, "utf-8"));
|
|
1134
1258
|
} catch (error) {
|
|
@@ -1153,7 +1277,7 @@ function persistWorkspace(repoPath, value) {
|
|
|
1153
1277
|
if (!gitDir) {
|
|
1154
1278
|
throw new Error(`cannot persist workspace identity: '${repoPath}' is not a Git checkout`);
|
|
1155
1279
|
}
|
|
1156
|
-
writeFileAtomically(
|
|
1280
|
+
writeFileAtomically(import_node_path5.default.join(gitDir, WORKSPACE_METADATA_FILE), `${workspace}
|
|
1157
1281
|
`);
|
|
1158
1282
|
return workspace;
|
|
1159
1283
|
}
|
|
@@ -1162,7 +1286,7 @@ async function withWorkspaceLifecycleLock(repoPath, operation) {
|
|
|
1162
1286
|
if (!gitDir) {
|
|
1163
1287
|
throw new Error(`cannot lock workspace lifecycle: '${repoPath}' is not a Git checkout`);
|
|
1164
1288
|
}
|
|
1165
|
-
const lockPath =
|
|
1289
|
+
const lockPath = import_node_path5.default.join(gitDir, WORKSPACE_LOCK_FILE);
|
|
1166
1290
|
return withFileLock(
|
|
1167
1291
|
lockPath,
|
|
1168
1292
|
{ activity: "workspace lifecycle", target: `'${repoPath}'` },
|
|
@@ -1190,7 +1314,7 @@ function deriveLinkedWorktreeWorkspace(repoPath, linkedWorktreeBranch) {
|
|
|
1190
1314
|
return void 0;
|
|
1191
1315
|
}
|
|
1192
1316
|
const branch = currentBranch(repoPath);
|
|
1193
|
-
return wsFromBranch(branch ??
|
|
1317
|
+
return wsFromBranch(branch ?? import_node_path5.default.basename(import_node_path5.default.resolve(repoPath)));
|
|
1194
1318
|
}
|
|
1195
1319
|
function resolveWorktreeWorkspace(repoPath, linkedWorktreeBranch) {
|
|
1196
1320
|
return readPersistedWorkspace(repoPath) ?? deriveLinkedWorktreeWorkspace(repoPath, linkedWorktreeBranch);
|
|
@@ -1213,14 +1337,14 @@ function resolveWorkspace(repoPath, override) {
|
|
|
1213
1337
|
}
|
|
1214
1338
|
return deriveLinkedWorktreeWorkspace(repoPath);
|
|
1215
1339
|
}
|
|
1216
|
-
var import_node_child_process3, import_node_crypto3, 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;
|
|
1217
1341
|
var init_workspace = __esm({
|
|
1218
1342
|
"src/core/workspace.ts"() {
|
|
1219
1343
|
"use strict";
|
|
1220
1344
|
import_node_child_process3 = require("child_process");
|
|
1221
1345
|
import_node_crypto3 = require("crypto");
|
|
1222
1346
|
import_node_fs5 = __toESM(require("fs"));
|
|
1223
|
-
|
|
1347
|
+
import_node_path5 = __toESM(require("path"));
|
|
1224
1348
|
init_atomic_file();
|
|
1225
1349
|
init_file_lock();
|
|
1226
1350
|
MAX_WORKSPACE_LENGTH = 32;
|
|
@@ -1644,7 +1768,7 @@ function listHostRoutes(tlsEnabled) {
|
|
|
1644
1768
|
protocol: isTcp ? `tcp/${tcpProtocol}` : "http",
|
|
1645
1769
|
appName: route.name,
|
|
1646
1770
|
serviceName: route.name,
|
|
1647
|
-
projectName:
|
|
1771
|
+
projectName: import_node_path6.default.basename(route.repoPath),
|
|
1648
1772
|
hosts: [route.host],
|
|
1649
1773
|
// TCP routes are reached via an SNI-aware client on the shared protocol
|
|
1650
1774
|
// port; surface a protocol-scheme URL rather than http(s)://.
|
|
@@ -1659,12 +1783,12 @@ function listHostRoutes(tlsEnabled) {
|
|
|
1659
1783
|
};
|
|
1660
1784
|
});
|
|
1661
1785
|
}
|
|
1662
|
-
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;
|
|
1663
1787
|
var init_host_routes = __esm({
|
|
1664
1788
|
"src/core/host-routes.ts"() {
|
|
1665
1789
|
"use strict";
|
|
1666
1790
|
import_node_fs6 = __toESM(require("fs"));
|
|
1667
|
-
|
|
1791
|
+
import_node_path6 = __toESM(require("path"));
|
|
1668
1792
|
import_node_util = require("util");
|
|
1669
1793
|
import_yaml = __toESM(require("yaml"));
|
|
1670
1794
|
init_atomic_file();
|
|
@@ -2512,10 +2636,10 @@ function appToNodeValue(app) {
|
|
|
2512
2636
|
return value;
|
|
2513
2637
|
}
|
|
2514
2638
|
function resolveRepoPath(repoPath) {
|
|
2515
|
-
return
|
|
2639
|
+
return import_node_path7.default.resolve(repoPath ?? process.cwd());
|
|
2516
2640
|
}
|
|
2517
2641
|
function getRepoConfigPath(repoPath) {
|
|
2518
|
-
return
|
|
2642
|
+
return import_node_path7.default.join(resolveRepoPath(repoPath), CONFIG_FILE_NAME);
|
|
2519
2643
|
}
|
|
2520
2644
|
function loadRepoConfig(repoPath) {
|
|
2521
2645
|
const resolvedRepoPath = resolveRepoPath(repoPath);
|
|
@@ -2530,7 +2654,7 @@ function loadRepoConfig(repoPath) {
|
|
|
2530
2654
|
const config = parseConfig(parsed ?? {}, configPath);
|
|
2531
2655
|
const requiredVersion = config.devrouter?.version;
|
|
2532
2656
|
if (requiredVersion && !hasWarnedVersionMismatch) {
|
|
2533
|
-
const cliVersion = true ? "0.0.
|
|
2657
|
+
const cliVersion = true ? "0.0.46" : "0.0.0-dev";
|
|
2534
2658
|
if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
|
|
2535
2659
|
hasWarnedVersionMismatch = true;
|
|
2536
2660
|
process.stderr.write(
|
|
@@ -2563,7 +2687,7 @@ function initRepoConfig(repoPath, options = {}) {
|
|
|
2563
2687
|
}
|
|
2564
2688
|
} : {},
|
|
2565
2689
|
project: {
|
|
2566
|
-
name:
|
|
2690
|
+
name: import_node_path7.default.basename(resolvedRepoPath)
|
|
2567
2691
|
},
|
|
2568
2692
|
apps: []
|
|
2569
2693
|
};
|
|
@@ -2792,7 +2916,7 @@ function namespaceHost(host, workspace) {
|
|
|
2792
2916
|
return `${base}.${workspace}${suffix}`;
|
|
2793
2917
|
}
|
|
2794
2918
|
function applyWorkspace(config, workspace, repoPath) {
|
|
2795
|
-
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";
|
|
2796
2920
|
const substitutionToken = workspace ?? defaultToken;
|
|
2797
2921
|
const next = structuredClone(config);
|
|
2798
2922
|
for (const app of next.apps) {
|
|
@@ -2874,12 +2998,12 @@ function resolveAppDependencies(config, app) {
|
|
|
2874
2998
|
}
|
|
2875
2999
|
return results;
|
|
2876
3000
|
}
|
|
2877
|
-
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;
|
|
2878
3002
|
var init_repo_config = __esm({
|
|
2879
3003
|
"src/core/repo-config.ts"() {
|
|
2880
3004
|
"use strict";
|
|
2881
3005
|
import_node_fs7 = __toESM(require("fs"));
|
|
2882
|
-
|
|
3006
|
+
import_node_path7 = __toESM(require("path"));
|
|
2883
3007
|
import_yaml2 = __toESM(require("yaml"));
|
|
2884
3008
|
init_capabilities();
|
|
2885
3009
|
init_host_routes();
|
|
@@ -3060,7 +3184,7 @@ function buildOnboardingPrompt(options = {}) {
|
|
|
3060
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.",
|
|
3061
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.`,
|
|
3062
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.",
|
|
3063
|
-
"- 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.",
|
|
3064
3188
|
"- A managed adapter plus `postCreateCommand` requires `waitFor` exactly `postCreateCommand` or `postStartCommand`; managed selective config preserves lifecycle fields and changes only `runServices`.",
|
|
3065
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.",
|
|
3066
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.",
|
|
@@ -3787,7 +3911,7 @@ function readPromptDirectory(promptDirPath) {
|
|
|
3787
3911
|
continue;
|
|
3788
3912
|
}
|
|
3789
3913
|
const version = normalizeVersion(basename);
|
|
3790
|
-
const promptPath =
|
|
3914
|
+
const promptPath = import_node_path8.default.join(promptDirPath, file.name);
|
|
3791
3915
|
const prompt = import_node_fs8.default.readFileSync(promptPath, "utf-8").trim();
|
|
3792
3916
|
releases.push({ version, prompt, promptPath });
|
|
3793
3917
|
}
|
|
@@ -3809,17 +3933,17 @@ function listAvailableUpgradeTargets(currentVersion, releases) {
|
|
|
3809
3933
|
}
|
|
3810
3934
|
function resolvePromptDirectory(explicitPath) {
|
|
3811
3935
|
if (explicitPath) {
|
|
3812
|
-
return
|
|
3936
|
+
return import_node_path8.default.resolve(explicitPath);
|
|
3813
3937
|
}
|
|
3814
|
-
const entryFile = process.argv[1] ?
|
|
3815
|
-
const entryDir =
|
|
3816
|
-
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;
|
|
3817
3941
|
const candidates = [
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
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)
|
|
3823
3947
|
];
|
|
3824
3948
|
const existing = candidates.find((candidate) => import_node_fs8.default.existsSync(candidate));
|
|
3825
3949
|
return existing ?? candidates[0];
|
|
@@ -3837,12 +3961,12 @@ function loadUpgradeCatalog(options = {}) {
|
|
|
3837
3961
|
releases
|
|
3838
3962
|
};
|
|
3839
3963
|
}
|
|
3840
|
-
var import_node_fs8,
|
|
3964
|
+
var import_node_fs8, import_node_path8, SEMVER_RE, UPGRADE_PROMPTS_DIR;
|
|
3841
3965
|
var init_upgrade = __esm({
|
|
3842
3966
|
"src/core/upgrade.ts"() {
|
|
3843
3967
|
"use strict";
|
|
3844
3968
|
import_node_fs8 = __toESM(require("fs"));
|
|
3845
|
-
|
|
3969
|
+
import_node_path8 = __toESM(require("path"));
|
|
3846
3970
|
init_repo_config();
|
|
3847
3971
|
SEMVER_RE = /^v?(\d+)\.(\d+)\.(\d+)$/;
|
|
3848
3972
|
UPGRADE_PROMPTS_DIR = "upgrade-prompts";
|
|
@@ -4054,7 +4178,7 @@ function parseDevcontainerConfig(contents, sourcePath) {
|
|
|
4054
4178
|
return parsed;
|
|
4055
4179
|
}
|
|
4056
4180
|
function readDevcontainerConfig(repoPath) {
|
|
4057
|
-
const sourcePath =
|
|
4181
|
+
const sourcePath = import_node_path9.default.join(repoPath, ".devcontainer", "devcontainer.json");
|
|
4058
4182
|
if (!import_node_fs9.default.existsSync(sourcePath) || !import_node_fs9.default.lstatSync(sourcePath).isFile()) {
|
|
4059
4183
|
throw new Error(`Managed Dev Container source config does not exist: ${sourcePath}`);
|
|
4060
4184
|
}
|
|
@@ -4074,12 +4198,12 @@ function assertManagedDevcontainerLifecycle(repoPath) {
|
|
|
4074
4198
|
`Managed Dev Container source '${sourcePath}' defines postCreateCommand, but waitFor ${actual}. Set waitFor to 'postCreateCommand' or 'postStartCommand' before retrying devrouter ensure.`
|
|
4075
4199
|
);
|
|
4076
4200
|
}
|
|
4077
|
-
var import_node_fs9,
|
|
4201
|
+
var import_node_fs9, import_node_path9, import_jsonc_parser;
|
|
4078
4202
|
var init_devcontainer_config = __esm({
|
|
4079
4203
|
"src/core/devcontainer-config.ts"() {
|
|
4080
4204
|
"use strict";
|
|
4081
4205
|
import_node_fs9 = __toESM(require("fs"));
|
|
4082
|
-
|
|
4206
|
+
import_node_path9 = __toESM(require("path"));
|
|
4083
4207
|
import_jsonc_parser = require("jsonc-parser");
|
|
4084
4208
|
}
|
|
4085
4209
|
});
|
|
@@ -4089,7 +4213,7 @@ function renderDeliveryScript(targetPath) {
|
|
|
4089
4213
|
if (!/^\/tmp\/devrouter\/bin\/[a-zA-Z0-9._-]+$/.test(targetPath)) {
|
|
4090
4214
|
throw new Error(`Unsafe managed runtime delivery path: ${targetPath}`);
|
|
4091
4215
|
}
|
|
4092
|
-
const temporaryPrefix =
|
|
4216
|
+
const temporaryPrefix = import_node_path10.default.posix.basename(targetPath);
|
|
4093
4217
|
return `set -eu
|
|
4094
4218
|
umask 077
|
|
4095
4219
|
runtime_root=/tmp/devrouter
|
|
@@ -4135,8 +4259,8 @@ function adapterFingerprint(adapter) {
|
|
|
4135
4259
|
}
|
|
4136
4260
|
function resolveProcessHelperPath() {
|
|
4137
4261
|
const candidates = [
|
|
4138
|
-
|
|
4139
|
-
|
|
4262
|
+
import_node_path10.default.resolve(__dirname, "..", "bin", "devrouter-process"),
|
|
4263
|
+
import_node_path10.default.resolve(__dirname, "..", "..", "bin", "devrouter-process")
|
|
4140
4264
|
];
|
|
4141
4265
|
const helperPath = candidates.find((candidate) => import_node_fs10.default.existsSync(candidate));
|
|
4142
4266
|
if (!helperPath) {
|
|
@@ -4145,11 +4269,11 @@ function resolveProcessHelperPath() {
|
|
|
4145
4269
|
return helperPath;
|
|
4146
4270
|
}
|
|
4147
4271
|
function resolveManagedPostStartPlan(repoPath) {
|
|
4148
|
-
const adapterPath =
|
|
4272
|
+
const adapterPath = import_node_path10.default.join(repoPath, MANAGED_ADAPTER_PATH);
|
|
4149
4273
|
const adapterBytes = readRegularFileBytes(adapterPath);
|
|
4150
4274
|
const adapterText = adapterBytes?.toString("utf-8");
|
|
4151
|
-
const dockerfilePath =
|
|
4152
|
-
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");
|
|
4153
4277
|
const dockerfile = readRegularFile(dockerfilePath) ?? "";
|
|
4154
4278
|
const devcontainer = readRegularFile(devcontainerPath) ?? "";
|
|
4155
4279
|
const devrouterPattern = [adapterText, dockerfile, devcontainer].filter((value) => value !== void 0).some(
|
|
@@ -4278,14 +4402,14 @@ function runManagedProcessAction(options) {
|
|
|
4278
4402
|
}
|
|
4279
4403
|
return "drifted";
|
|
4280
4404
|
}
|
|
4281
|
-
var import_node_child_process5, import_node_crypto4, import_node_fs10,
|
|
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;
|
|
4282
4406
|
var init_managed_post_start = __esm({
|
|
4283
4407
|
"src/core/managed-post-start.ts"() {
|
|
4284
4408
|
"use strict";
|
|
4285
4409
|
import_node_child_process5 = require("child_process");
|
|
4286
4410
|
import_node_crypto4 = require("crypto");
|
|
4287
4411
|
import_node_fs10 = __toESM(require("fs"));
|
|
4288
|
-
|
|
4412
|
+
import_node_path10 = __toESM(require("path"));
|
|
4289
4413
|
init_devcontainer_config();
|
|
4290
4414
|
MANAGED_MARKER = "devrouter:managed devcontainer";
|
|
4291
4415
|
MANAGED_ADAPTER_PATH = ".devcontainer/post-start.sh";
|
|
@@ -4418,11 +4542,11 @@ function routedProxyApps(config) {
|
|
|
4418
4542
|
);
|
|
4419
4543
|
}
|
|
4420
4544
|
function buildDevcontainerChecks(repoPath, config, workspace) {
|
|
4421
|
-
const devcontainerDir =
|
|
4545
|
+
const devcontainerDir = import_node_path11.default.join(repoPath, ".devcontainer");
|
|
4422
4546
|
if (!import_node_fs11.default.existsSync(devcontainerDir)) {
|
|
4423
4547
|
return [];
|
|
4424
4548
|
}
|
|
4425
|
-
const compose = inspectCompose(
|
|
4549
|
+
const compose = inspectCompose(import_node_path11.default.join(devcontainerDir, "docker-compose.yml"), workspace);
|
|
4426
4550
|
const checks = [];
|
|
4427
4551
|
if (compose.parseError) {
|
|
4428
4552
|
checks.push({
|
|
@@ -4449,7 +4573,7 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
|
|
|
4449
4573
|
details: compose.publishedPorts.length > 0 ? compose.publishedPorts.join(", ") : void 0,
|
|
4450
4574
|
suggestion: compose.parseError || compose.publishedPorts.length > 0 ? "Remove published ports and route services through devnet aliases with devrouter proxy apps." : void 0
|
|
4451
4575
|
});
|
|
4452
|
-
const dockerfilePath =
|
|
4576
|
+
const dockerfilePath = import_node_path11.default.join(devcontainerDir, "Dockerfile");
|
|
4453
4577
|
const dockerfileExists = import_node_fs11.default.existsSync(dockerfilePath);
|
|
4454
4578
|
const dockerfile = dockerfileExists ? import_node_fs11.default.readFileSync(dockerfilePath, "utf-8") : "";
|
|
4455
4579
|
const devrouterArtifacts = ["@devrouter/cli", "devrouter-process"].filter(
|
|
@@ -4506,12 +4630,12 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
|
|
|
4506
4630
|
});
|
|
4507
4631
|
return checks;
|
|
4508
4632
|
}
|
|
4509
|
-
var import_node_fs11,
|
|
4633
|
+
var import_node_fs11, import_node_path11, import_yaml3;
|
|
4510
4634
|
var init_devcontainer_diagnostics = __esm({
|
|
4511
4635
|
"src/core/devcontainer-diagnostics.ts"() {
|
|
4512
4636
|
"use strict";
|
|
4513
4637
|
import_node_fs11 = __toESM(require("fs"));
|
|
4514
|
-
|
|
4638
|
+
import_node_path11 = __toESM(require("path"));
|
|
4515
4639
|
import_yaml3 = __toESM(require("yaml"));
|
|
4516
4640
|
init_managed_post_start();
|
|
4517
4641
|
}
|
|
@@ -4519,18 +4643,18 @@ var init_devcontainer_diagnostics = __esm({
|
|
|
4519
4643
|
|
|
4520
4644
|
// src/core/paths.ts
|
|
4521
4645
|
function assertPathWithinRepo(filePath, repoRoot, label) {
|
|
4522
|
-
const resolvedRoot =
|
|
4523
|
-
const resolved =
|
|
4524
|
-
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)) {
|
|
4525
4649
|
throw new Error(`${label} path '${filePath}' escapes the repository root.`);
|
|
4526
4650
|
}
|
|
4527
4651
|
return resolved;
|
|
4528
4652
|
}
|
|
4529
|
-
var
|
|
4653
|
+
var import_node_path12;
|
|
4530
4654
|
var init_paths = __esm({
|
|
4531
4655
|
"src/core/paths.ts"() {
|
|
4532
4656
|
"use strict";
|
|
4533
|
-
|
|
4657
|
+
import_node_path12 = __toESM(require("path"));
|
|
4534
4658
|
}
|
|
4535
4659
|
});
|
|
4536
4660
|
|
|
@@ -4788,7 +4912,7 @@ function stateKey(repoPath, workspace) {
|
|
|
4788
4912
|
return (0, import_node_crypto5.createHash)("sha256").update(`${repoPath}\0${workspace ?? ""}`, "utf-8").digest("hex");
|
|
4789
4913
|
}
|
|
4790
4914
|
function managedRuntimeStatePath(repoPath, workspace) {
|
|
4791
|
-
return
|
|
4915
|
+
return import_node_path13.default.join(DEVROUTER_HOME, "managed-runtime", `${stateKey(repoPath, workspace)}.json`);
|
|
4792
4916
|
}
|
|
4793
4917
|
function isStringArray(value) {
|
|
4794
4918
|
return Array.isArray(value) && value.every((item) => typeof item === "string") && new Set(value).size === value.length;
|
|
@@ -4876,13 +5000,13 @@ function markManagedRuntimeDegraded(state, transitionPhase) {
|
|
|
4876
5000
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4877
5001
|
});
|
|
4878
5002
|
}
|
|
4879
|
-
var import_node_crypto5, import_node_fs13,
|
|
5003
|
+
var import_node_crypto5, import_node_fs13, import_node_path13;
|
|
4880
5004
|
var init_managed_runtime_state = __esm({
|
|
4881
5005
|
"src/core/managed-runtime-state.ts"() {
|
|
4882
5006
|
"use strict";
|
|
4883
5007
|
import_node_crypto5 = require("crypto");
|
|
4884
5008
|
import_node_fs13 = __toESM(require("fs"));
|
|
4885
|
-
|
|
5009
|
+
import_node_path13 = __toESM(require("path"));
|
|
4886
5010
|
init_atomic_file();
|
|
4887
5011
|
init_router();
|
|
4888
5012
|
}
|
|
@@ -4933,7 +5057,7 @@ function resolveComposeFiles(source, repoPath, linked) {
|
|
|
4933
5057
|
"managedRuntime requires .devcontainer/devcontainer.json to define dockerComposeFile."
|
|
4934
5058
|
);
|
|
4935
5059
|
}
|
|
4936
|
-
const directory =
|
|
5060
|
+
const directory = import_node_path14.default.join(repoPath, ".devcontainer");
|
|
4937
5061
|
const files = references.map((reference) => {
|
|
4938
5062
|
const resolved2 = assertPathWithinRepo(
|
|
4939
5063
|
resolveComposeReference(reference, linked),
|
|
@@ -5078,7 +5202,7 @@ function inspectManagedDevcontainerConfig(options) {
|
|
|
5078
5202
|
const contents = `${MANAGED_DEVCONTAINER_MARKER}
|
|
5079
5203
|
${JSON.stringify(effective, null, 2)}
|
|
5080
5204
|
`;
|
|
5081
|
-
const generatedPath =
|
|
5205
|
+
const generatedPath = import_node_path14.default.join(options.repoPath, MANAGED_DEVCONTAINER_PATH);
|
|
5082
5206
|
assertIgnoredGeneratedPath(options.repoPath, generatedPath);
|
|
5083
5207
|
assertGeneratedPathOwnership(generatedPath);
|
|
5084
5208
|
return {
|
|
@@ -5198,14 +5322,14 @@ function stopExactManagedService(containerId, service) {
|
|
|
5198
5322
|
throw new Error(`Could not stop exact managed service '${service}' (${containerId}).`);
|
|
5199
5323
|
}
|
|
5200
5324
|
}
|
|
5201
|
-
var import_node_child_process6, import_node_crypto6, import_node_fs14,
|
|
5325
|
+
var import_node_child_process6, import_node_crypto6, import_node_fs14, import_node_path14, import_yaml4, MANAGED_DEVCONTAINER_PATH, MANAGED_DEVCONTAINER_MARKER;
|
|
5202
5326
|
var init_devcontainer_profile = __esm({
|
|
5203
5327
|
"src/core/devcontainer-profile.ts"() {
|
|
5204
5328
|
"use strict";
|
|
5205
5329
|
import_node_child_process6 = require("child_process");
|
|
5206
5330
|
import_node_crypto6 = require("crypto");
|
|
5207
5331
|
import_node_fs14 = __toESM(require("fs"));
|
|
5208
|
-
|
|
5332
|
+
import_node_path14 = __toESM(require("path"));
|
|
5209
5333
|
import_yaml4 = __toESM(require("yaml"));
|
|
5210
5334
|
init_atomic_file();
|
|
5211
5335
|
init_devcontainer_config();
|
|
@@ -5219,9 +5343,17 @@ var init_devcontainer_profile = __esm({
|
|
|
5219
5343
|
function inspectWorkspaceContainers(options) {
|
|
5220
5344
|
let ids = options?.ids;
|
|
5221
5345
|
if (!ids) {
|
|
5222
|
-
const listed = (0, import_node_child_process7.spawnSync)(
|
|
5223
|
-
|
|
5224
|
-
|
|
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
|
+
);
|
|
5225
5357
|
if (listed.status !== 0) {
|
|
5226
5358
|
throw new Error(
|
|
5227
5359
|
`docker ps failed: ${(listed.stderr || listed.stdout || listed.error?.message || "unknown error").trim()}`
|
|
@@ -5246,7 +5378,7 @@ function inspectWorkspaceContainers(options) {
|
|
|
5246
5378
|
function workspaceAppContainers(containers, repoPath) {
|
|
5247
5379
|
return containers.filter((container) => {
|
|
5248
5380
|
const workingDir = container.labels["com.docker.compose.project.working_dir"];
|
|
5249
|
-
return Boolean(workingDir && sameWorkspacePath(workingDir,
|
|
5381
|
+
return Boolean(workingDir && sameWorkspacePath(workingDir, import_node_path15.default.join(repoPath, ".devcontainer"))) && container.mounts.some(
|
|
5250
5382
|
(mount) => mount.Type === "bind" && sameWorkspacePath(mount.Source, repoPath)
|
|
5251
5383
|
);
|
|
5252
5384
|
});
|
|
@@ -5254,7 +5386,7 @@ function workspaceAppContainers(containers, repoPath) {
|
|
|
5254
5386
|
function hasExactComposeIdentity(container, options) {
|
|
5255
5387
|
if (options.composeProject !== void 0 && container.labels["com.docker.compose.project"] !== options.composeProject || container.labels["com.docker.compose.service"] !== options.service || !sameWorkspacePath(
|
|
5256
5388
|
container.labels["com.docker.compose.project.working_dir"] ?? "",
|
|
5257
|
-
|
|
5389
|
+
import_node_path15.default.join(options.repoPath, ".devcontainer")
|
|
5258
5390
|
)) {
|
|
5259
5391
|
return false;
|
|
5260
5392
|
}
|
|
@@ -5291,12 +5423,12 @@ function resolveRunningWorkspaceContainer(repoPath) {
|
|
|
5291
5423
|
}
|
|
5292
5424
|
return { id: container.id, workspacePath: repoMount.Destination };
|
|
5293
5425
|
}
|
|
5294
|
-
var import_node_child_process7,
|
|
5426
|
+
var import_node_child_process7, import_node_path15, SAFE_INSPECT_TEMPLATE, SIZE_INSPECT_TEMPLATE;
|
|
5295
5427
|
var init_devpod_environment = __esm({
|
|
5296
5428
|
"src/core/devpod-environment.ts"() {
|
|
5297
5429
|
"use strict";
|
|
5298
5430
|
import_node_child_process7 = require("child_process");
|
|
5299
|
-
|
|
5431
|
+
import_node_path15 = __toESM(require("path"));
|
|
5300
5432
|
init_workspace();
|
|
5301
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}}}';
|
|
5302
5434
|
SIZE_INSPECT_TEMPLATE = SAFE_INSPECT_TEMPLATE.replace(
|
|
@@ -5941,7 +6073,7 @@ function tlsSetupCommand(repoPath) {
|
|
|
5941
6073
|
}
|
|
5942
6074
|
function getMkcertRootCAPath(options = {}) {
|
|
5943
6075
|
ensureMkcert();
|
|
5944
|
-
const rootCAPath =
|
|
6076
|
+
const rootCAPath = import_node_path16.default.join(runOrThrow("mkcert", ["-CAROOT"]), "rootCA.pem");
|
|
5945
6077
|
if (!import_node_fs16.default.existsSync(rootCAPath)) {
|
|
5946
6078
|
throw new Error(
|
|
5947
6079
|
`mkcert root CA was not found at '${rootCAPath}'. Run: ${tlsSetupCommand(options.repoPath)}`
|
|
@@ -6175,20 +6307,20 @@ Run: ${tlsSetupCommand(options.repoPath)}`
|
|
|
6175
6307
|
);
|
|
6176
6308
|
}
|
|
6177
6309
|
}
|
|
6178
|
-
var import_node_child_process8, import_node_crypto7, import_node_fs16,
|
|
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;
|
|
6179
6311
|
var init_tls = __esm({
|
|
6180
6312
|
"src/core/tls.ts"() {
|
|
6181
6313
|
"use strict";
|
|
6182
6314
|
import_node_child_process8 = require("child_process");
|
|
6183
6315
|
import_node_crypto7 = require("crypto");
|
|
6184
6316
|
import_node_fs16 = __toESM(require("fs"));
|
|
6185
|
-
|
|
6317
|
+
import_node_path16 = __toESM(require("path"));
|
|
6186
6318
|
init_docker();
|
|
6187
6319
|
init_file_lock();
|
|
6188
6320
|
init_host_routes();
|
|
6189
6321
|
init_router();
|
|
6190
6322
|
DEFAULT_TLS_CERT_HOSTS = ["localhost", "*.localhost"];
|
|
6191
|
-
TLS_CERTIFICATE_LOCK_FILE =
|
|
6323
|
+
TLS_CERTIFICATE_LOCK_FILE = import_node_path16.default.join(DEVROUTER_HOME, "tls-certificate.lock");
|
|
6192
6324
|
TLS_CERTIFICATE_LOCK_WAIT_MS = 6e4;
|
|
6193
6325
|
TLS_CERTIFICATE_WRITE_ATTEMPTS = 2;
|
|
6194
6326
|
}
|
|
@@ -6547,20 +6679,20 @@ function resetWorkspaceRuntimeCaches() {
|
|
|
6547
6679
|
cachedRuntime = void 0;
|
|
6548
6680
|
cachedSnapshots = void 0;
|
|
6549
6681
|
}
|
|
6550
|
-
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;
|
|
6551
6683
|
var init_workspace_runtime = __esm({
|
|
6552
6684
|
"src/core/workspace-runtime.ts"() {
|
|
6553
6685
|
"use strict";
|
|
6554
6686
|
import_node_child_process11 = require("child_process");
|
|
6555
6687
|
import_node_fs17 = __toESM(require("fs"));
|
|
6556
|
-
|
|
6688
|
+
import_node_path17 = __toESM(require("path"));
|
|
6557
6689
|
init_atomic_file();
|
|
6558
6690
|
init_devpod_registry();
|
|
6559
6691
|
init_devsy_workspaces();
|
|
6560
6692
|
init_router();
|
|
6561
6693
|
init_workspace();
|
|
6562
6694
|
SUPPORTED_RUNTIMES2 = ["devpod", "devsy"];
|
|
6563
|
-
RUNTIME_CONFIG_FILE =
|
|
6695
|
+
RUNTIME_CONFIG_FILE = import_node_path17.default.join(DEVROUTER_HOME, "workspace-runtime.json");
|
|
6564
6696
|
INACTIVITY_TIMEOUT_PATTERN = /^(?:\d+(?:ms|s|m|h))+$/;
|
|
6565
6697
|
UnsupportedWorkspaceRuntimeError = class extends Error {
|
|
6566
6698
|
};
|
|
@@ -6638,7 +6770,7 @@ function parseMinimumNodeMajor(value) {
|
|
|
6638
6770
|
return Number(match[1]);
|
|
6639
6771
|
}
|
|
6640
6772
|
function readPackageJson(repoPath) {
|
|
6641
|
-
const packagePath =
|
|
6773
|
+
const packagePath = import_node_path18.default.join(repoPath, "package.json");
|
|
6642
6774
|
if (!import_node_fs18.default.existsSync(packagePath)) {
|
|
6643
6775
|
return void 0;
|
|
6644
6776
|
}
|
|
@@ -6783,13 +6915,13 @@ function buildGlobalToolChecks(repoPath) {
|
|
|
6783
6915
|
checks.push(nodeToolchainCheck(repoPath));
|
|
6784
6916
|
return checks;
|
|
6785
6917
|
}
|
|
6786
|
-
var import_node_child_process12, import_node_fs18,
|
|
6918
|
+
var import_node_child_process12, import_node_fs18, import_node_path18;
|
|
6787
6919
|
var init_tool_diagnostics = __esm({
|
|
6788
6920
|
"src/core/tool-diagnostics.ts"() {
|
|
6789
6921
|
"use strict";
|
|
6790
6922
|
import_node_child_process12 = require("child_process");
|
|
6791
6923
|
import_node_fs18 = __toESM(require("fs"));
|
|
6792
|
-
|
|
6924
|
+
import_node_path18 = __toESM(require("path"));
|
|
6793
6925
|
init_workspace_runtime();
|
|
6794
6926
|
}
|
|
6795
6927
|
});
|
|
@@ -6919,13 +7051,65 @@ function withMutationLock(activity, target, operation) {
|
|
|
6919
7051
|
import_node_fs19.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
6920
7052
|
return withFileLockSync(
|
|
6921
7053
|
DEVSY_MUTATION_LOCK_FILE,
|
|
6922
|
-
{
|
|
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
|
+
},
|
|
6923
7075
|
operation
|
|
6924
7076
|
);
|
|
6925
7077
|
}
|
|
6926
7078
|
function commandFailure2(result) {
|
|
6927
7079
|
return [result.error?.message, result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
6928
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
|
+
}
|
|
6929
7113
|
function runDevsyAction(action2, devsyId, force = false) {
|
|
6930
7114
|
const args = action2 === "delete" ? ["delete", devsyId, ...force ? ["--force"] : [], "--ignore-not-found"] : ["stop", devsyId];
|
|
6931
7115
|
const result = (0, import_node_child_process14.spawnSync)("devsy", ["workspace", ...args], { encoding: "utf-8" });
|
|
@@ -6990,7 +7174,7 @@ function assertDevsyTarget(devsyId, repoPath) {
|
|
|
6990
7174
|
}
|
|
6991
7175
|
function startDevsyWorkspace(options) {
|
|
6992
7176
|
const activity = options.recreate ? "Devsy recreate" : "Devsy start";
|
|
6993
|
-
return
|
|
7177
|
+
return withMutationLockAsync(activity, options.repoPath, async () => {
|
|
6994
7178
|
let devsyId = assertDevsyTarget(options.devsyId, options.repoPath);
|
|
6995
7179
|
if (devsyId && options.recreate) {
|
|
6996
7180
|
const attached = listDevsyWorkspaces();
|
|
@@ -7030,12 +7214,13 @@ function startDevsyWorkspace(options) {
|
|
|
7030
7214
|
delete env.DEVROUTER_GIT_COMMON_DIR;
|
|
7031
7215
|
delete env.DEVCONTAINER_COMPOSE_OVERLAY;
|
|
7032
7216
|
}
|
|
7033
|
-
const result =
|
|
7034
|
-
stdio: options.quiet ? ["inherit", 2, "inherit"] : "inherit",
|
|
7035
|
-
env
|
|
7036
|
-
});
|
|
7217
|
+
const result = await runDevsyUp(args, env, options.quiet ?? false);
|
|
7037
7218
|
if (result.status !== 0) {
|
|
7038
|
-
|
|
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
|
+
}
|
|
7039
7224
|
if (failedStartMayHaveAttached(devsyId, options.repoPath)) {
|
|
7040
7225
|
throw new DevsyStartPostconditionError(message);
|
|
7041
7226
|
}
|
|
@@ -7061,20 +7246,22 @@ function startDevsyWorkspace(options) {
|
|
|
7061
7246
|
}
|
|
7062
7247
|
});
|
|
7063
7248
|
}
|
|
7064
|
-
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;
|
|
7065
7250
|
var init_devsy_mutation = __esm({
|
|
7066
7251
|
"src/core/devsy-mutation.ts"() {
|
|
7067
7252
|
"use strict";
|
|
7068
7253
|
import_node_child_process14 = require("child_process");
|
|
7069
7254
|
import_node_fs19 = __toESM(require("fs"));
|
|
7070
|
-
|
|
7255
|
+
import_node_path19 = __toESM(require("path"));
|
|
7071
7256
|
init_devsy_workspaces();
|
|
7072
7257
|
init_file_lock();
|
|
7073
7258
|
init_router();
|
|
7074
|
-
DEVSY_MUTATION_LOCK_FILE =
|
|
7075
|
-
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;
|
|
7076
7261
|
DevsyStartPostconditionError = class extends Error {
|
|
7077
7262
|
};
|
|
7263
|
+
AGENT_ACQUISITION_RE = /inject agent.*agent binary not found/i;
|
|
7264
|
+
DEVSY_STDERR_TAIL_BYTES = 8192;
|
|
7078
7265
|
}
|
|
7079
7266
|
});
|
|
7080
7267
|
|
|
@@ -7083,7 +7270,13 @@ function withMutationLock2(activity, target, operation) {
|
|
|
7083
7270
|
import_node_fs20.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
7084
7271
|
return withFileLockSync(
|
|
7085
7272
|
DEVPOD_MUTATION_LOCK_FILE,
|
|
7086
|
-
{
|
|
7273
|
+
{
|
|
7274
|
+
activity,
|
|
7275
|
+
target: `'${target}'`,
|
|
7276
|
+
waitMs: DEVPOD_MUTATION_WAIT_MS,
|
|
7277
|
+
fair: true,
|
|
7278
|
+
onWait: createStderrWaitReporter(activity, `'${target}'`)
|
|
7279
|
+
},
|
|
7087
7280
|
operation
|
|
7088
7281
|
);
|
|
7089
7282
|
}
|
|
@@ -7158,10 +7351,10 @@ function deleteOwnedDevpodWorkspace(devpodId, worktreePath) {
|
|
|
7158
7351
|
resetWorkspaceRuntimeCaches();
|
|
7159
7352
|
return result;
|
|
7160
7353
|
}
|
|
7161
|
-
function startDevpodWorkspace(options) {
|
|
7354
|
+
async function startDevpodWorkspace(options) {
|
|
7162
7355
|
if (resolveWorkspaceRuntimeOrDefault(options.repoPath) === "devsy") {
|
|
7163
7356
|
try {
|
|
7164
|
-
const result = startDevsyWorkspace({
|
|
7357
|
+
const result = await startDevsyWorkspace({
|
|
7165
7358
|
repoPath: options.repoPath,
|
|
7166
7359
|
devsyId: options.devpodId,
|
|
7167
7360
|
devcontainerPath: options.devcontainerPath,
|
|
@@ -7248,20 +7441,20 @@ function startDevpodWorkspace(options) {
|
|
|
7248
7441
|
}
|
|
7249
7442
|
});
|
|
7250
7443
|
}
|
|
7251
|
-
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;
|
|
7252
7445
|
var init_devpod_mutation = __esm({
|
|
7253
7446
|
"src/core/devpod-mutation.ts"() {
|
|
7254
7447
|
"use strict";
|
|
7255
7448
|
import_node_child_process15 = require("child_process");
|
|
7256
7449
|
import_node_fs20 = __toESM(require("fs"));
|
|
7257
|
-
|
|
7450
|
+
import_node_path20 = __toESM(require("path"));
|
|
7258
7451
|
init_devpod_workspaces();
|
|
7259
7452
|
init_devsy_mutation();
|
|
7260
7453
|
init_file_lock();
|
|
7261
7454
|
init_router();
|
|
7262
7455
|
init_workspace_runtime();
|
|
7263
|
-
DEVPOD_MUTATION_LOCK_FILE =
|
|
7264
|
-
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;
|
|
7265
7458
|
DevpodStartPostconditionError = class extends Error {
|
|
7266
7459
|
};
|
|
7267
7460
|
}
|
|
@@ -7282,7 +7475,7 @@ function resolveGitCommonDir(repoPath) {
|
|
|
7282
7475
|
if (result.status !== 0 || !output2) {
|
|
7283
7476
|
throw commandError("Could not resolve the Git common directory", repoPath, result.stderr);
|
|
7284
7477
|
}
|
|
7285
|
-
return comparableWorkspacePath(
|
|
7478
|
+
return comparableWorkspacePath(import_node_path21.default.isAbsolute(output2) ? output2 : import_node_path21.default.resolve(repoPath, output2));
|
|
7286
7479
|
}
|
|
7287
7480
|
function resolveGitTopLevel(repoPath) {
|
|
7288
7481
|
const result = (0, import_node_child_process16.spawnSync)("git", ["-C", repoPath, "rev-parse", "--show-toplevel"], {
|
|
@@ -7333,7 +7526,7 @@ function listGitWorktrees(repoPath) {
|
|
|
7333
7526
|
return worktrees;
|
|
7334
7527
|
}
|
|
7335
7528
|
function ownershipDirectory(repoPath) {
|
|
7336
|
-
return
|
|
7529
|
+
return import_node_path21.default.join(resolveGitCommonDir(repoPath), OWNERSHIP_DIR);
|
|
7337
7530
|
}
|
|
7338
7531
|
function validateWorkspace(value, label) {
|
|
7339
7532
|
if (typeof value !== "string" || wsFromBranch(value) !== value) {
|
|
@@ -7361,7 +7554,7 @@ function validateRecord(value, expectedWorkspace) {
|
|
|
7361
7554
|
`workspace ownership file '${expectedWorkspace}' contains identity '${workspace}'`
|
|
7362
7555
|
);
|
|
7363
7556
|
}
|
|
7364
|
-
if (typeof candidate.worktreePath !== "string" || !
|
|
7557
|
+
if (typeof candidate.worktreePath !== "string" || !import_node_path21.default.isAbsolute(candidate.worktreePath)) {
|
|
7365
7558
|
throw new Error("invalid workspace ownership worktreePath");
|
|
7366
7559
|
}
|
|
7367
7560
|
if (candidate.branch !== null && typeof candidate.branch !== "string") {
|
|
@@ -7405,14 +7598,14 @@ function listWorkspaceOwnershipInDirectory(directory) {
|
|
|
7405
7598
|
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).sort((left, right) => left.name.localeCompare(right.name)).map((entry) => {
|
|
7406
7599
|
const workspace = entry.name.slice(0, -".json".length);
|
|
7407
7600
|
validateWorkspace(workspace, "filename");
|
|
7408
|
-
return readRecordFile(
|
|
7601
|
+
return readRecordFile(import_node_path21.default.join(directory, entry.name), workspace);
|
|
7409
7602
|
});
|
|
7410
7603
|
}
|
|
7411
7604
|
function writeWorkspaceOwnershipInDirectory(directory, input2) {
|
|
7412
7605
|
const workspace = validateWorkspace(input2.workspace, "workspace");
|
|
7413
7606
|
const devpodId = validateWorkspace(input2.devpodId, "devpodId");
|
|
7414
7607
|
const worktreePath = comparableWorkspacePath(input2.worktreePath);
|
|
7415
|
-
const filePath =
|
|
7608
|
+
const filePath = import_node_path21.default.join(directory, `${workspace}.json`);
|
|
7416
7609
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7417
7610
|
const records = listWorkspaceOwnershipInDirectory(directory);
|
|
7418
7611
|
const existing = records.find((record2) => record2.workspace === workspace);
|
|
@@ -7448,7 +7641,7 @@ function writeWorkspaceOwnershipInDirectory(directory, input2) {
|
|
|
7448
7641
|
return record;
|
|
7449
7642
|
}
|
|
7450
7643
|
function removeWorkspaceOwnershipInDirectory(directory, workspace) {
|
|
7451
|
-
const filePath =
|
|
7644
|
+
const filePath = import_node_path21.default.join(directory, `${validateWorkspace(workspace, "workspace")}.json`);
|
|
7452
7645
|
try {
|
|
7453
7646
|
import_node_fs21.default.rmSync(filePath);
|
|
7454
7647
|
return true;
|
|
@@ -7461,7 +7654,7 @@ function sameOwnershipRecord(left, right) {
|
|
|
7461
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;
|
|
7462
7655
|
}
|
|
7463
7656
|
function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
|
|
7464
|
-
const filePath =
|
|
7657
|
+
const filePath = import_node_path21.default.join(
|
|
7465
7658
|
directory,
|
|
7466
7659
|
`${validateWorkspace(expected.workspace, "workspace")}.json`
|
|
7467
7660
|
);
|
|
@@ -7480,7 +7673,7 @@ function withWorkspaceOwnershipTransaction(repoPath, operation, options = {}) {
|
|
|
7480
7673
|
const directory = ownershipDirectory(repoPath);
|
|
7481
7674
|
import_node_fs21.default.mkdirSync(directory, { recursive: true });
|
|
7482
7675
|
return withFileLockSync(
|
|
7483
|
-
|
|
7676
|
+
import_node_path21.default.join(directory, ".lock"),
|
|
7484
7677
|
{
|
|
7485
7678
|
activity: "workspace ownership transaction",
|
|
7486
7679
|
target: `'${repoPath}'`,
|
|
@@ -7700,20 +7893,20 @@ function listMissingWorkspaceOwnership(repoPath) {
|
|
|
7700
7893
|
(record) => inspectWorkspaceOwnership(record, worktrees, void 0).ownerStatus === "missing"
|
|
7701
7894
|
);
|
|
7702
7895
|
}
|
|
7703
|
-
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;
|
|
7704
7897
|
var init_workspace_ownership = __esm({
|
|
7705
7898
|
"src/core/workspace-ownership.ts"() {
|
|
7706
7899
|
"use strict";
|
|
7707
7900
|
import_node_child_process16 = require("child_process");
|
|
7708
7901
|
import_node_fs21 = __toESM(require("fs"));
|
|
7709
|
-
|
|
7902
|
+
import_node_path21 = __toESM(require("path"));
|
|
7710
7903
|
init_atomic_file();
|
|
7711
7904
|
init_devpod_workspaces();
|
|
7712
7905
|
init_file_lock();
|
|
7713
7906
|
init_workspace();
|
|
7714
7907
|
READ_ONLY_GIT_ENV = { ...process.env, GIT_OPTIONAL_LOCKS: "0" };
|
|
7715
7908
|
OWNERSHIP_VERSION = 1;
|
|
7716
|
-
OWNERSHIP_DIR =
|
|
7909
|
+
OWNERSHIP_DIR = import_node_path21.default.join("devrouter", "workspaces");
|
|
7717
7910
|
}
|
|
7718
7911
|
});
|
|
7719
7912
|
|
|
@@ -7727,10 +7920,10 @@ function inRepositoryWorkspaceScope(repoPath, worktreePath, livePaths) {
|
|
|
7727
7920
|
if (livePaths.some((candidate) => sameWorkspacePath(candidate, worktreePath))) return true;
|
|
7728
7921
|
const comparableRepo = comparableWorkspacePath(repoPath);
|
|
7729
7922
|
const comparableWorktree = comparableWorkspacePath(worktreePath);
|
|
7730
|
-
const localRoot =
|
|
7923
|
+
const localRoot = import_node_path22.default.join(comparableRepo, "trees") + import_node_path22.default.sep;
|
|
7731
7924
|
if (comparableWorktree.startsWith(localRoot)) return true;
|
|
7732
|
-
const legacyPrefix = `${
|
|
7733
|
-
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);
|
|
7734
7927
|
}
|
|
7735
7928
|
function previewActions(devpodStatus, routeCount, includeRecord) {
|
|
7736
7929
|
const actions = [
|
|
@@ -7982,11 +8175,11 @@ function applyWorkspaceGc(plan) {
|
|
|
7982
8175
|
candidates
|
|
7983
8176
|
};
|
|
7984
8177
|
}
|
|
7985
|
-
var
|
|
8178
|
+
var import_node_path22;
|
|
7986
8179
|
var init_workspace_gc = __esm({
|
|
7987
8180
|
"src/core/workspace-gc.ts"() {
|
|
7988
8181
|
"use strict";
|
|
7989
|
-
|
|
8182
|
+
import_node_path22 = __toESM(require("path"));
|
|
7990
8183
|
init_devpod_mutation();
|
|
7991
8184
|
init_devpod_workspaces();
|
|
7992
8185
|
init_host_routes();
|
|
@@ -8339,7 +8532,7 @@ async function buildDoctorReport(options = {}) {
|
|
|
8339
8532
|
const config = runtimeConfig.config;
|
|
8340
8533
|
loadedConfig = config;
|
|
8341
8534
|
loadedWorkspace = runtimeConfig.workspace;
|
|
8342
|
-
const cliVersion = true ? "0.0.
|
|
8535
|
+
const cliVersion = true ? "0.0.46" : "0.0.0-dev";
|
|
8343
8536
|
const configVersion = config.devrouter?.version;
|
|
8344
8537
|
if (configVersion && cliVersion !== "0.0.0-dev" && compareSemver(configVersion, cliVersion) > 0) {
|
|
8345
8538
|
addCheck(checks, {
|
|
@@ -8369,7 +8562,7 @@ async function buildDoctorReport(options = {}) {
|
|
|
8369
8562
|
(app) => app.docker.composeFiles.map((filePath) => ({
|
|
8370
8563
|
app: app.name,
|
|
8371
8564
|
filePath,
|
|
8372
|
-
absolutePath:
|
|
8565
|
+
absolutePath: import_node_path23.default.resolve(repo.path, filePath)
|
|
8373
8566
|
}))
|
|
8374
8567
|
).filter((entry) => !import_node_fs22.default.existsSync(entry.absolutePath));
|
|
8375
8568
|
addCheck(checks, {
|
|
@@ -8414,7 +8607,7 @@ async function buildDoctorReport(options = {}) {
|
|
|
8414
8607
|
const missingHostCwds = config.apps.filter((app) => app.runtime === "host").map((app) => ({
|
|
8415
8608
|
app: app.name,
|
|
8416
8609
|
cwd: app.hostRun.cwd,
|
|
8417
|
-
absolutePath:
|
|
8610
|
+
absolutePath: import_node_path23.default.resolve(repo.path, app.hostRun.cwd)
|
|
8418
8611
|
})).filter((entry) => !import_node_fs22.default.existsSync(entry.absolutePath));
|
|
8419
8612
|
addCheck(checks, {
|
|
8420
8613
|
id: "repo.host-cwd",
|
|
@@ -8559,12 +8752,12 @@ async function buildDoctorReport(options = {}) {
|
|
|
8559
8752
|
nextSteps
|
|
8560
8753
|
};
|
|
8561
8754
|
}
|
|
8562
|
-
var import_node_fs22,
|
|
8755
|
+
var import_node_fs22, import_node_path23, import_yaml5, POSTGRES_DEFAULTS;
|
|
8563
8756
|
var init_doctor = __esm({
|
|
8564
8757
|
"src/core/doctor.ts"() {
|
|
8565
8758
|
"use strict";
|
|
8566
8759
|
import_node_fs22 = __toESM(require("fs"));
|
|
8567
|
-
|
|
8760
|
+
import_node_path23 = __toESM(require("path"));
|
|
8568
8761
|
import_yaml5 = __toESM(require("yaml"));
|
|
8569
8762
|
init_docker();
|
|
8570
8763
|
init_host_routes();
|
|
@@ -9059,11 +9252,11 @@ var init_route_publication = __esm({
|
|
|
9059
9252
|
// src/core/workspace-ensure.ts
|
|
9060
9253
|
function assertOverlay(container, repoPath) {
|
|
9061
9254
|
const workingDir = container.labels["com.docker.compose.project.working_dir"];
|
|
9062
|
-
if (!workingDir || !sameWorkspacePath(workingDir,
|
|
9255
|
+
if (!workingDir || !sameWorkspacePath(workingDir, import_node_path24.default.join(repoPath, ".devcontainer"))) {
|
|
9063
9256
|
throw new Error(`Container '${container.id}' does not belong to the exact worktree.`);
|
|
9064
9257
|
}
|
|
9065
9258
|
const configFiles = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").filter(Boolean);
|
|
9066
|
-
const expectedOverlay =
|
|
9259
|
+
const expectedOverlay = import_node_path24.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
|
|
9067
9260
|
if (!configFiles.some((configFile) => sameWorkspacePath(configFile, expectedOverlay))) {
|
|
9068
9261
|
throw new Error(`Container '${container.id}' was not started with ${DEVCONTAINER_OVERLAY}.`);
|
|
9069
9262
|
}
|
|
@@ -9239,6 +9432,20 @@ function isWarmWorkspaceActive(repoPath) {
|
|
|
9239
9432
|
return true;
|
|
9240
9433
|
}
|
|
9241
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
|
+
}
|
|
9242
9449
|
function captureFirstTransitionBaseline(options) {
|
|
9243
9450
|
const containers = inspectWorkspaceContainers();
|
|
9244
9451
|
const runningPrimary = workspaceAppContainers(containers, options.repoPath).filter(
|
|
@@ -9453,7 +9660,7 @@ function resolvePrimaryTarget(repoPath) {
|
|
|
9453
9660
|
}
|
|
9454
9661
|
function isPrimaryCheckout(repoPath) {
|
|
9455
9662
|
try {
|
|
9456
|
-
return import_node_fs23.default.statSync(
|
|
9663
|
+
return import_node_fs23.default.statSync(import_node_path24.default.join(repoPath, ".git")).isDirectory();
|
|
9457
9664
|
} catch {
|
|
9458
9665
|
return false;
|
|
9459
9666
|
}
|
|
@@ -9503,7 +9710,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
9503
9710
|
const target = linked ? resolveLinkedTarget(repoPath) : resolvePrimaryTarget(repoPath);
|
|
9504
9711
|
let devpodId = target.devpodId;
|
|
9505
9712
|
if (target.kind === "linked") {
|
|
9506
|
-
const overlayPath =
|
|
9713
|
+
const overlayPath = import_node_path24.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
|
|
9507
9714
|
if (!import_node_fs23.default.existsSync(overlayPath)) {
|
|
9508
9715
|
throw new Error(`Missing required DevPod compose overlay: ${overlayPath}`);
|
|
9509
9716
|
}
|
|
@@ -9521,6 +9728,9 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
9521
9728
|
const desiredProcesses = managedRuntime ? desiredManagedProcesses(managedRuntime, runtime.resolvedProfile) : [];
|
|
9522
9729
|
if (managedRuntime) {
|
|
9523
9730
|
previousManagedState = readManagedRuntimeState(repoPath, target.workspace);
|
|
9731
|
+
if (previousManagedState && !hasExactManagedComposeProject(repoPath, previousManagedState)) {
|
|
9732
|
+
previousManagedState = void 0;
|
|
9733
|
+
}
|
|
9524
9734
|
if (previousManagedState?.status === "degraded") {
|
|
9525
9735
|
throw new Error(
|
|
9526
9736
|
"Managed runtime state is degraded; refusing a new profile transition until drift is repaired."
|
|
@@ -9571,7 +9781,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
9571
9781
|
}
|
|
9572
9782
|
const apps = proxyAppsFromConfig(runtime.config);
|
|
9573
9783
|
const parsedUpstreams = apps.map((app) => parseUpstream(app.upstream));
|
|
9574
|
-
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";
|
|
9575
9785
|
for (const [index, app] of apps.entries()) {
|
|
9576
9786
|
if (!parsedUpstreams[index].host.startsWith(`${aliasPrefix}-`)) {
|
|
9577
9787
|
const owner = target.kind === "linked" ? "workspace" : "checkout";
|
|
@@ -9586,10 +9796,10 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
9586
9796
|
}
|
|
9587
9797
|
const upstreamHosts = parsedUpstreams.map((upstream) => upstream.host);
|
|
9588
9798
|
const currentTarget = () => target.kind === "linked" ? target : { ...target, devpodId };
|
|
9589
|
-
const startAndProveAttachment = (recreate = false) => {
|
|
9799
|
+
const startAndProveAttachment = async (recreate = false) => {
|
|
9590
9800
|
const requestedTarget = currentTarget();
|
|
9591
9801
|
try {
|
|
9592
|
-
devpodId = startDevpodWorkspace({
|
|
9802
|
+
devpodId = await startDevpodWorkspace({
|
|
9593
9803
|
repoPath,
|
|
9594
9804
|
devpodId: requestedTarget.devpodId,
|
|
9595
9805
|
devcontainerPath: managedPlan?.generatedRelativePath,
|
|
@@ -9610,13 +9820,13 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
9610
9820
|
};
|
|
9611
9821
|
const preflight = (timeoutMs) => waitForContainerPreflight(repoPath, currentTarget(), upstreamHosts, timeoutMs);
|
|
9612
9822
|
const recreateAndPreflight = async () => {
|
|
9613
|
-
startAndProveAttachment(true);
|
|
9823
|
+
await startAndProveAttachment(true);
|
|
9614
9824
|
return preflight(options.containerTimeoutMs ?? DEFAULT_READINESS_TIMEOUT_MS);
|
|
9615
9825
|
};
|
|
9616
9826
|
let recreated = false;
|
|
9617
9827
|
let container;
|
|
9618
9828
|
try {
|
|
9619
|
-
startAndProveAttachment();
|
|
9829
|
+
await startAndProveAttachment();
|
|
9620
9830
|
} catch (error) {
|
|
9621
9831
|
if (managedPlan || !target.hadExactDevpod) {
|
|
9622
9832
|
throw error;
|
|
@@ -10003,13 +10213,13 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
10003
10213
|
}
|
|
10004
10214
|
});
|
|
10005
10215
|
}
|
|
10006
|
-
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;
|
|
10007
10217
|
var init_workspace_ensure = __esm({
|
|
10008
10218
|
"src/core/workspace-ensure.ts"() {
|
|
10009
10219
|
"use strict";
|
|
10010
10220
|
import_node_child_process19 = require("child_process");
|
|
10011
10221
|
import_node_fs23 = __toESM(require("fs"));
|
|
10012
|
-
|
|
10222
|
+
import_node_path24 = __toESM(require("path"));
|
|
10013
10223
|
init_devcontainer_profile();
|
|
10014
10224
|
init_devpod_environment();
|
|
10015
10225
|
init_devpod_mutation();
|
|
@@ -10095,7 +10305,7 @@ function warnMissingWorkspaceOwnership(repoPath) {
|
|
|
10095
10305
|
);
|
|
10096
10306
|
}
|
|
10097
10307
|
function defaultWorktreePath(mainRepo, ws) {
|
|
10098
|
-
return
|
|
10308
|
+
return import_node_path25.default.join(mainRepo, "trees", ws);
|
|
10099
10309
|
}
|
|
10100
10310
|
function assertDefaultWorktreeRootIgnored(mainRepo) {
|
|
10101
10311
|
const ignored = (0, import_node_child_process20.spawnSync)("git", ["-C", mainRepo, "check-ignore", "-q", "--no-index", "trees/"], {
|
|
@@ -10103,12 +10313,12 @@ function assertDefaultWorktreeRootIgnored(mainRepo) {
|
|
|
10103
10313
|
});
|
|
10104
10314
|
if (ignored.status !== 0) {
|
|
10105
10315
|
throw new Error(
|
|
10106
|
-
`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.`
|
|
10107
10317
|
);
|
|
10108
10318
|
}
|
|
10109
10319
|
}
|
|
10110
10320
|
function legacyDefaultWorktreePath(mainRepo, ws) {
|
|
10111
|
-
return
|
|
10321
|
+
return import_node_path25.default.join(import_node_path25.default.dirname(mainRepo), `${import_node_path25.default.basename(mainRepo)}-${ws}`);
|
|
10112
10322
|
}
|
|
10113
10323
|
function teardownFallbackPath(mainRepo, workspace) {
|
|
10114
10324
|
const candidates = [
|
|
@@ -10262,7 +10472,7 @@ async function workspaceUp(branch, opts = {}) {
|
|
|
10262
10472
|
const generatedPath = candidatePaths.find(
|
|
10263
10473
|
(candidate) => !import_node_fs24.default.existsSync(candidate) && !worktrees.some((worktree) => sameWorkspacePath(worktree.path, candidate))
|
|
10264
10474
|
);
|
|
10265
|
-
const selectedPath = opts.path ?
|
|
10475
|
+
const selectedPath = opts.path ? import_node_path25.default.resolve(opts.path) : existingBranch?.path ?? generatedPath;
|
|
10266
10476
|
if (!selectedPath) {
|
|
10267
10477
|
throw new Error(`Could not allocate a collision-safe worktree path for branch '${branch}'.`);
|
|
10268
10478
|
}
|
|
@@ -10450,13 +10660,13 @@ async function workspaceStop(target, opts = {}) {
|
|
|
10450
10660
|
async function workspaceDown(target, opts = {}) {
|
|
10451
10661
|
return runWorkspaceLifecycle("down", target, opts);
|
|
10452
10662
|
}
|
|
10453
|
-
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;
|
|
10454
10664
|
var init_workspace_lifecycle = __esm({
|
|
10455
10665
|
"src/core/workspace-lifecycle.ts"() {
|
|
10456
10666
|
"use strict";
|
|
10457
10667
|
import_node_child_process20 = require("child_process");
|
|
10458
10668
|
import_node_fs24 = __toESM(require("fs"));
|
|
10459
|
-
|
|
10669
|
+
import_node_path25 = __toESM(require("path"));
|
|
10460
10670
|
init_devpod_mutation();
|
|
10461
10671
|
init_devpod_workspaces();
|
|
10462
10672
|
init_repo_config();
|
|
@@ -11035,7 +11245,7 @@ function readJson(filePath) {
|
|
|
11035
11245
|
}
|
|
11036
11246
|
}
|
|
11037
11247
|
function relative(repoPath, filePath) {
|
|
11038
|
-
return
|
|
11248
|
+
return import_node_path26.default.relative(repoPath, filePath) || ".";
|
|
11039
11249
|
}
|
|
11040
11250
|
function redactEnvAssignments(value) {
|
|
11041
11251
|
return value.replace(
|
|
@@ -11076,7 +11286,7 @@ function inspectPackageManager(repoPath, pkg) {
|
|
|
11076
11286
|
["bun.lock", "bun"]
|
|
11077
11287
|
];
|
|
11078
11288
|
for (const [fileName, name] of lockfiles) {
|
|
11079
|
-
if (import_node_fs25.default.existsSync(
|
|
11289
|
+
if (import_node_fs25.default.existsSync(import_node_path26.default.join(repoPath, fileName))) {
|
|
11080
11290
|
return { name, source: fileName };
|
|
11081
11291
|
}
|
|
11082
11292
|
}
|
|
@@ -11091,7 +11301,7 @@ function inspectNode(repoPath, pkg) {
|
|
|
11091
11301
|
if (typeof engines?.node === "string") {
|
|
11092
11302
|
return { version: engines.node, source: "package.json:engines.node" };
|
|
11093
11303
|
}
|
|
11094
|
-
const nvmrc =
|
|
11304
|
+
const nvmrc = import_node_path26.default.join(repoPath, ".nvmrc");
|
|
11095
11305
|
if (import_node_fs25.default.existsSync(nvmrc)) {
|
|
11096
11306
|
const version = import_node_fs25.default.readFileSync(nvmrc, "utf-8").trim();
|
|
11097
11307
|
return { version, source: ".nvmrc" };
|
|
@@ -11154,7 +11364,7 @@ function configuredComposeFiles(repoPath) {
|
|
|
11154
11364
|
const files = config.apps.filter(
|
|
11155
11365
|
(app) => app.runtime === "docker"
|
|
11156
11366
|
).flatMap((app) => app.docker.composeFiles).filter(
|
|
11157
|
-
(fileName) => !
|
|
11367
|
+
(fileName) => !import_node_path26.default.isAbsolute(fileName) && !import_node_path26.default.normalize(fileName).startsWith("..")
|
|
11158
11368
|
);
|
|
11159
11369
|
return Array.from(new Set(files));
|
|
11160
11370
|
} catch {
|
|
@@ -11172,7 +11382,7 @@ function composeFiles(repoPath) {
|
|
|
11172
11382
|
...configuredComposeFiles(repoPath)
|
|
11173
11383
|
];
|
|
11174
11384
|
return Array.from(new Set(candidates)).filter(
|
|
11175
|
-
(fileName) => import_node_fs25.default.existsSync(
|
|
11385
|
+
(fileName) => import_node_fs25.default.existsSync(import_node_path26.default.join(repoPath, fileName))
|
|
11176
11386
|
);
|
|
11177
11387
|
}
|
|
11178
11388
|
function stringArray2(value) {
|
|
@@ -11210,7 +11420,7 @@ function inspectServices(repoPath) {
|
|
|
11210
11420
|
const services = [];
|
|
11211
11421
|
for (const fileName of composeFiles(repoPath)) {
|
|
11212
11422
|
try {
|
|
11213
|
-
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"));
|
|
11214
11424
|
const serviceMap = asRecord3(asRecord3(parsed)?.services);
|
|
11215
11425
|
for (const [name, value] of Object.entries(serviceMap ?? {})) {
|
|
11216
11426
|
const service = asRecord3(value);
|
|
@@ -11246,7 +11456,7 @@ function inspectServices(repoPath) {
|
|
|
11246
11456
|
}
|
|
11247
11457
|
function inspectEnvFiles(repoPath) {
|
|
11248
11458
|
const files = import_node_fs25.default.readdirSync(repoPath).filter((fileName) => /^\.env(\.|$)/.test(fileName)).sort().map((fileName) => {
|
|
11249
|
-
const content = import_node_fs25.default.readFileSync(
|
|
11459
|
+
const content = import_node_fs25.default.readFileSync(import_node_path26.default.join(repoPath, fileName), "utf-8");
|
|
11250
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();
|
|
11251
11461
|
return { path: fileName, names };
|
|
11252
11462
|
});
|
|
@@ -11260,13 +11470,13 @@ function inspectEnvFiles(repoPath) {
|
|
|
11260
11470
|
};
|
|
11261
11471
|
}
|
|
11262
11472
|
function inspectDevcontainer(repoPath) {
|
|
11263
|
-
const dir =
|
|
11473
|
+
const dir = import_node_path26.default.join(repoPath, ".devcontainer");
|
|
11264
11474
|
if (!import_node_fs25.default.existsSync(dir)) {
|
|
11265
11475
|
return { exists: false, files: [] };
|
|
11266
11476
|
}
|
|
11267
11477
|
return {
|
|
11268
11478
|
exists: true,
|
|
11269
|
-
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}`)
|
|
11270
11480
|
};
|
|
11271
11481
|
}
|
|
11272
11482
|
function inspectDevrouter(repoPath) {
|
|
@@ -11315,14 +11525,14 @@ function inspectAgentGuidance(repoPath) {
|
|
|
11315
11525
|
["AGENTS.md", "agents"],
|
|
11316
11526
|
["CLAUDE.md", "claude"]
|
|
11317
11527
|
]) {
|
|
11318
|
-
if (import_node_fs25.default.existsSync(
|
|
11528
|
+
if (import_node_fs25.default.existsSync(import_node_path26.default.join(repoPath, fileName))) {
|
|
11319
11529
|
results.push({ path: fileName, kind });
|
|
11320
11530
|
}
|
|
11321
11531
|
}
|
|
11322
|
-
const skillsDir =
|
|
11532
|
+
const skillsDir = import_node_path26.default.join(repoPath, ".agents", "skills");
|
|
11323
11533
|
if (import_node_fs25.default.existsSync(skillsDir)) {
|
|
11324
11534
|
for (const name of import_node_fs25.default.readdirSync(skillsDir).sort()) {
|
|
11325
|
-
const skillPath =
|
|
11535
|
+
const skillPath = import_node_path26.default.join(skillsDir, name, "SKILL.md");
|
|
11326
11536
|
if (import_node_fs25.default.existsSync(skillPath)) {
|
|
11327
11537
|
results.push({ path: relative(repoPath, skillPath), kind: "skill" });
|
|
11328
11538
|
}
|
|
@@ -11366,7 +11576,7 @@ function buildIssues(report) {
|
|
|
11366
11576
|
}
|
|
11367
11577
|
function inspectRepo(options = {}) {
|
|
11368
11578
|
const repoPath = resolveRepoPath(options.repo);
|
|
11369
|
-
const pkg = readJson(
|
|
11579
|
+
const pkg = readJson(import_node_path26.default.join(repoPath, "package.json"));
|
|
11370
11580
|
const scripts = inspectScripts(pkg);
|
|
11371
11581
|
const reportWithoutIssues = {
|
|
11372
11582
|
repoPath,
|
|
@@ -11385,12 +11595,12 @@ function inspectRepo(options = {}) {
|
|
|
11385
11595
|
issues: buildIssues(reportWithoutIssues)
|
|
11386
11596
|
};
|
|
11387
11597
|
}
|
|
11388
|
-
var import_node_fs25,
|
|
11598
|
+
var import_node_fs25, import_node_path26, import_yaml6;
|
|
11389
11599
|
var init_repo_inspect = __esm({
|
|
11390
11600
|
"src/core/repo-inspect.ts"() {
|
|
11391
11601
|
"use strict";
|
|
11392
11602
|
import_node_fs25 = __toESM(require("fs"));
|
|
11393
|
-
|
|
11603
|
+
import_node_path26 = __toESM(require("path"));
|
|
11394
11604
|
import_yaml6 = __toESM(require("yaml"));
|
|
11395
11605
|
init_repo_config();
|
|
11396
11606
|
}
|
|
@@ -11495,7 +11705,7 @@ function requiredFileChecks(repoPath) {
|
|
|
11495
11705
|
".devcontainer/docker-compose.yml",
|
|
11496
11706
|
".devrouter.yml"
|
|
11497
11707
|
];
|
|
11498
|
-
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)));
|
|
11499
11709
|
return {
|
|
11500
11710
|
id: "repo.devcontainer.verify-files",
|
|
11501
11711
|
level: missing.length === 0 ? "ok" : "error",
|
|
@@ -11714,12 +11924,12 @@ async function verifyDevcontainer(options = {}) {
|
|
|
11714
11924
|
nextSteps: collectNextSteps3(checks)
|
|
11715
11925
|
};
|
|
11716
11926
|
}
|
|
11717
|
-
var import_node_fs26,
|
|
11927
|
+
var import_node_fs26, import_node_path27;
|
|
11718
11928
|
var init_devcontainer_verify = __esm({
|
|
11719
11929
|
"src/core/devcontainer-verify.ts"() {
|
|
11720
11930
|
"use strict";
|
|
11721
11931
|
import_node_fs26 = __toESM(require("fs"));
|
|
11722
|
-
|
|
11932
|
+
import_node_path27 = __toESM(require("path"));
|
|
11723
11933
|
init_capabilities();
|
|
11724
11934
|
init_doctor();
|
|
11725
11935
|
init_host_routes();
|
|
@@ -12016,7 +12226,7 @@ function packageManagerIssues(repo) {
|
|
|
12016
12226
|
}
|
|
12017
12227
|
function plannedFiles(repoPath, version) {
|
|
12018
12228
|
const repo = inspectRepo({ repo: repoPath });
|
|
12019
|
-
const projectName = sanitizeProjectName(
|
|
12229
|
+
const projectName = sanitizeProjectName(import_node_path28.default.basename(repo.repoPath));
|
|
12020
12230
|
const nodeMajor = majorVersion(repo.node?.version, "24");
|
|
12021
12231
|
const pnpmVersion = repo.packageManager?.name === "pnpm" && repo.packageManager.version ? repo.packageManager.version : DEFAULT_PNPM_VERSION;
|
|
12022
12232
|
const port = inferPort2(repo);
|
|
@@ -12061,7 +12271,7 @@ function plannedFiles(repoPath, version) {
|
|
|
12061
12271
|
};
|
|
12062
12272
|
}
|
|
12063
12273
|
function classifyFile(repoPath, file) {
|
|
12064
|
-
const absolutePath =
|
|
12274
|
+
const absolutePath = import_node_path28.default.join(repoPath, file.relativePath);
|
|
12065
12275
|
if (!import_node_fs27.default.existsSync(absolutePath)) {
|
|
12066
12276
|
return {
|
|
12067
12277
|
path: file.relativePath,
|
|
@@ -12127,8 +12337,8 @@ function buildPlan(repoPath, dryRun, version) {
|
|
|
12127
12337
|
};
|
|
12128
12338
|
}
|
|
12129
12339
|
function writeFile(repoPath, file) {
|
|
12130
|
-
const absolutePath =
|
|
12131
|
-
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 });
|
|
12132
12342
|
import_node_fs27.default.writeFileSync(absolutePath, file.content, "utf-8");
|
|
12133
12343
|
if (file.executable) {
|
|
12134
12344
|
import_node_fs27.default.chmodSync(absolutePath, 493);
|
|
@@ -12169,12 +12379,12 @@ function writeDevcontainer(options = {}) {
|
|
|
12169
12379
|
nextSteps: postWriteNextSteps(repoPath)
|
|
12170
12380
|
};
|
|
12171
12381
|
}
|
|
12172
|
-
var import_node_fs27,
|
|
12382
|
+
var import_node_fs27, import_node_path28, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
|
|
12173
12383
|
var init_devcontainer_write = __esm({
|
|
12174
12384
|
"src/core/devcontainer-write.ts"() {
|
|
12175
12385
|
"use strict";
|
|
12176
12386
|
import_node_fs27 = __toESM(require("fs"));
|
|
12177
|
-
|
|
12387
|
+
import_node_path28 = __toESM(require("path"));
|
|
12178
12388
|
init_repo_config();
|
|
12179
12389
|
init_repo_inspect();
|
|
12180
12390
|
MANAGED_MARKER2 = "devrouter:managed devcontainer";
|
|
@@ -12565,7 +12775,7 @@ function sanitizeRouterId(value) {
|
|
|
12565
12775
|
return value.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
12566
12776
|
}
|
|
12567
12777
|
function repoHash(repoPath) {
|
|
12568
|
-
return (0, import_node_crypto9.createHash)("sha1").update(
|
|
12778
|
+
return (0, import_node_crypto9.createHash)("sha1").update(import_node_path29.default.resolve(repoPath)).digest("hex").slice(0, 12);
|
|
12569
12779
|
}
|
|
12570
12780
|
function asDockerApp(app) {
|
|
12571
12781
|
return app.runtime === "docker";
|
|
@@ -12644,9 +12854,9 @@ function prepareDockerOverlay(repoPath, appName, apps, publishTcpPorts = false)
|
|
|
12644
12854
|
if (dockerApps.length === 0) {
|
|
12645
12855
|
throw new Error("No docker apps selected to prepare compose overlay.");
|
|
12646
12856
|
}
|
|
12647
|
-
const cachePath =
|
|
12857
|
+
const cachePath = import_node_path29.default.join(CACHE_DIR, repoHash(repoPath), sanitizeRouterId(appName));
|
|
12648
12858
|
import_node_fs28.default.mkdirSync(cachePath, { recursive: true });
|
|
12649
|
-
const overlayPath =
|
|
12859
|
+
const overlayPath = import_node_path29.default.join(cachePath, "compose.devrouter.yml");
|
|
12650
12860
|
const overlayDocument = buildOverlayDocument(dockerApps, publishTcpPorts);
|
|
12651
12861
|
import_node_fs28.default.writeFileSync(overlayPath, import_yaml7.default.stringify(overlayDocument, { lineWidth: 0 }), "utf-8");
|
|
12652
12862
|
return {
|
|
@@ -12768,14 +12978,14 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
|
|
|
12768
12978
|
const port = Number(match[1]);
|
|
12769
12979
|
return Number.isInteger(port) && port > 0 ? port : void 0;
|
|
12770
12980
|
}
|
|
12771
|
-
var import_node_child_process25, import_node_crypto9, import_node_fs28,
|
|
12981
|
+
var import_node_child_process25, import_node_crypto9, import_node_fs28, import_node_path29, import_yaml7;
|
|
12772
12982
|
var init_docker_run = __esm({
|
|
12773
12983
|
"src/core/docker-run.ts"() {
|
|
12774
12984
|
"use strict";
|
|
12775
12985
|
import_node_child_process25 = require("child_process");
|
|
12776
12986
|
import_node_crypto9 = require("crypto");
|
|
12777
12987
|
import_node_fs28 = __toESM(require("fs"));
|
|
12778
|
-
|
|
12988
|
+
import_node_path29 = __toESM(require("path"));
|
|
12779
12989
|
import_yaml7 = __toESM(require("yaml"));
|
|
12780
12990
|
init_docker_error_guidance();
|
|
12781
12991
|
init_paths();
|
|
@@ -13629,7 +13839,7 @@ function measureWorktreeConsumption(worktreePath, options) {
|
|
|
13629
13839
|
timedOut = true;
|
|
13630
13840
|
break;
|
|
13631
13841
|
}
|
|
13632
|
-
const entryPath =
|
|
13842
|
+
const entryPath = import_node_path30.default.join(dirPath, entry.name);
|
|
13633
13843
|
let entryStat;
|
|
13634
13844
|
try {
|
|
13635
13845
|
entryStat = import_node_fs29.default.lstatSync(entryPath);
|
|
@@ -13706,12 +13916,12 @@ function describeError(error, worktreePath) {
|
|
|
13706
13916
|
}
|
|
13707
13917
|
return `could not stat worktree path '${worktreePath}': ${error?.message ?? String(error)}`;
|
|
13708
13918
|
}
|
|
13709
|
-
var import_node_fs29,
|
|
13919
|
+
var import_node_fs29, import_node_path30, DEFAULT_DEADLINE_MS, BLOCK_SIZE_BYTES;
|
|
13710
13920
|
var init_workspace_consumption = __esm({
|
|
13711
13921
|
"src/core/workspace-consumption.ts"() {
|
|
13712
13922
|
"use strict";
|
|
13713
13923
|
import_node_fs29 = __toESM(require("fs"));
|
|
13714
|
-
|
|
13924
|
+
import_node_path30 = __toESM(require("path"));
|
|
13715
13925
|
init_devpod_environment();
|
|
13716
13926
|
DEFAULT_DEADLINE_MS = 1e4;
|
|
13717
13927
|
BLOCK_SIZE_BYTES = 512;
|
|
@@ -14337,7 +14547,7 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
|
|
|
14337
14547
|
containers = measureContainersFn(worktreePaths);
|
|
14338
14548
|
} catch (error) {
|
|
14339
14549
|
const reason = `container measurement failed: ${describeCause(error)}`;
|
|
14340
|
-
containers = new Map(worktreePaths.map((
|
|
14550
|
+
containers = new Map(worktreePaths.map((path30) => [path30, unknownContainers(reason)]));
|
|
14341
14551
|
}
|
|
14342
14552
|
}
|
|
14343
14553
|
const rows = records.map((record) => {
|
|
@@ -14568,7 +14778,7 @@ var init_version = __esm({
|
|
|
14568
14778
|
|
|
14569
14779
|
// src/cli.ts
|
|
14570
14780
|
var import_commander = require("commander");
|
|
14571
|
-
var CLI_VERSION = true ? "0.0.
|
|
14781
|
+
var CLI_VERSION = true ? "0.0.46" : "0.0.0-dev";
|
|
14572
14782
|
var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
|
|
14573
14783
|
function withErrorHandling(action2) {
|
|
14574
14784
|
return async (...args) => {
|