@devrouter/cli 0.0.45 → 0.0.48
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/README.md +11 -0
- package/dist/devrouter.js +1339 -395
- package/package.json +1 -1
- package/upgrade-prompts/0.0.46.md +41 -0
- package/upgrade-prompts/0.0.48.md +44 -0
package/dist/devrouter.js
CHANGED
|
@@ -44,6 +44,7 @@ function buildDevrouterSection() {
|
|
|
44
44
|
"",
|
|
45
45
|
"Quick validation sequence:",
|
|
46
46
|
"- Managed devcontainer consumer images contain no devrouter package or helper; `devrouter ensure` delivers the matching helper at runtime.",
|
|
47
|
+
"- Devsy runtime: run `devrouter setup --yes --workspace-runtime devsy` once; `doctor` then reports verified agent readiness without network access.",
|
|
47
48
|
"- `devrouter up`",
|
|
48
49
|
"- `devrouter tls install` (required when repo defines tcp/postgres apps)",
|
|
49
50
|
"- `devrouter app ls --repo .`",
|
|
@@ -322,7 +323,7 @@ Run several worktrees of one repo in parallel without host/route collisions. A *
|
|
|
322
323
|
- **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
324
|
- **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
325
|
- **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.
|
|
326
|
+
- **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. For Devsy, run \`devrouter setup --yes --workspace-runtime devsy\` once so Devrouter acquires and verifies the pinned agent in its own machine cache. \`doctor\` reports \`ready\`, \`missing\`, \`stale\`, or \`invalid\` without network access, and \`ensure\` fails before the provider queue when readiness is not \`ready\`. An explicit \`DEVSY_AGENT_BINARY\` remains authoritative and must match a pinned official asset.
|
|
326
327
|
- **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
328
|
- **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
329
|
- **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.
|
|
@@ -376,8 +377,9 @@ Run several worktrees of one repo in parallel without host/route collisions. A *
|
|
|
376
377
|
- \`devrouter init [--write-agents] [--write-skill]\`: print AI onboarding prompt (non-mutating by default)
|
|
377
378
|
- \`devrouter -V [--repo .]\`: show installed CLI version, local repo version, and next upgrade target
|
|
378
379
|
- \`devrouter upgrade [version] [--repo .]\`: list upgrade targets or print target Agent Adaptation Prompt
|
|
379
|
-
- \`devrouter setup --yes [--repo .] [--json]\`: first-run machine setup plus structured diagnostics
|
|
380
|
+
- \`devrouter setup --yes [--repo .] [--json] [--workspace-runtime <devpod|devsy>]\`: first-run machine setup plus structured diagnostics; explicit Devsy selection acquires its verified agent
|
|
380
381
|
- \`devrouter ensure [path] [--profile <name>] [--open] [--json]\`: canonical startup/reconciliation for primary and linked checkouts; a managed profile selects its independent resource dimensions
|
|
382
|
+
- \`devrouter profile resolve --repo <path> [--profile <selection>] [--json]\`: resolve exact profile resources for automation without starting or inspecting a runtime
|
|
381
383
|
- \`devrouter stop [path] [--delete] [--json]\`: stop the exact workspace runtime and remove exact routes; \`--delete\` explicitly deletes its ownership-proven data without removing the checkout
|
|
382
384
|
- \`devrouter exec [path] -- <command...>\`: literal one-shot command inside the exact running workspace runtime
|
|
383
385
|
- \`devrouter up\` / \`devrouter down\`: start/stop shared Traefik router
|
|
@@ -411,6 +413,9 @@ Run several worktrees of one repo in parallel without host/route collisions. A *
|
|
|
411
413
|
|
|
412
414
|
For devcontainer onboarding:
|
|
413
415
|
|
|
416
|
+
When Devsy owns the workspace, first run
|
|
417
|
+
\`devrouter setup --repo . --yes --workspace-runtime devsy --json\`.
|
|
418
|
+
|
|
414
419
|
1. \`devrouter setup --repo . --yes --json\`
|
|
415
420
|
2. \`devrouter doctor --repo . --json\`
|
|
416
421
|
3. \`devrouter repo inspect --repo . --json\`
|
|
@@ -720,6 +725,9 @@ function runDockerCompose(args) {
|
|
|
720
725
|
function startRouterStack() {
|
|
721
726
|
runDockerCompose(["up", "-d"]);
|
|
722
727
|
}
|
|
728
|
+
function restartRouterStack() {
|
|
729
|
+
runDockerCompose(["restart", "traefik"]);
|
|
730
|
+
}
|
|
723
731
|
function stopRouterStack() {
|
|
724
732
|
runDockerCompose(["down"]);
|
|
725
733
|
}
|
|
@@ -898,16 +906,65 @@ function parseLockOwner(value) {
|
|
|
898
906
|
return { pid };
|
|
899
907
|
}
|
|
900
908
|
}
|
|
901
|
-
function
|
|
902
|
-
|
|
909
|
+
function parseCanonicalLockOwner(value) {
|
|
910
|
+
const trimmed = value.trim();
|
|
911
|
+
return CANONICAL_OWNER_RE.test(trimmed) ? parseLockOwner(trimmed) : void 0;
|
|
912
|
+
}
|
|
913
|
+
function parseLockRecord(value) {
|
|
914
|
+
const trimmed = value.trim();
|
|
915
|
+
const lastColon = trimmed.lastIndexOf(":");
|
|
916
|
+
if (lastColon >= 0 && CANONICAL_OWNER_RE.test(trimmed.slice(0, lastColon))) {
|
|
917
|
+
const tail = Number(trimmed.slice(lastColon + 1));
|
|
918
|
+
if (Number.isInteger(tail) && tail > 0) {
|
|
919
|
+
return { owner: parseLockOwner(trimmed.slice(0, lastColon)), acquiredAtMs: tail };
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
return { owner: parseLockOwner(trimmed) };
|
|
923
|
+
}
|
|
924
|
+
function isLockOwnerLive(owner, cache, now = Date.now()) {
|
|
925
|
+
const cacheKey = `${owner.pid}:${owner.processBirth ?? "legacy"}`;
|
|
926
|
+
if (!isProcessAlive(owner.pid)) {
|
|
927
|
+
cache.delete(cacheKey);
|
|
928
|
+
return false;
|
|
929
|
+
}
|
|
903
930
|
if (!owner.processBirth) return true;
|
|
931
|
+
const cached = cache.get(cacheKey);
|
|
932
|
+
if (cached && now - cached.checkedAtMs < PROCESS_BIRTH_RECHECK_INTERVAL_MS) {
|
|
933
|
+
return cached.live;
|
|
934
|
+
}
|
|
904
935
|
const currentBirth = processBirthIdentity(owner.pid);
|
|
905
|
-
|
|
936
|
+
const live = currentBirth === void 0 || currentBirth === owner.processBirth;
|
|
937
|
+
cache.set(cacheKey, { checkedAtMs: now, live });
|
|
938
|
+
return live;
|
|
906
939
|
}
|
|
907
940
|
function sameFile(left, right) {
|
|
908
941
|
return left.dev === right.dev && left.ino === right.ino;
|
|
909
942
|
}
|
|
910
|
-
function
|
|
943
|
+
function inspectFairQueue(lockPath, ownTicketPath, livenessCache) {
|
|
944
|
+
const directory = import_node_path4.default.dirname(lockPath);
|
|
945
|
+
const prefix = `${import_node_path4.default.basename(lockPath)}.queue.`;
|
|
946
|
+
for (; ; ) {
|
|
947
|
+
const names = import_node_fs4.default.readdirSync(directory).filter((name) => name.startsWith(prefix)).sort();
|
|
948
|
+
const ownName = import_node_path4.default.basename(ownTicketPath);
|
|
949
|
+
const position = names.indexOf(ownName);
|
|
950
|
+
if (position < 0) {
|
|
951
|
+
throw new Error("provider mutation queue ticket disappeared before acquisition");
|
|
952
|
+
}
|
|
953
|
+
const leaderPath = import_node_path4.default.join(directory, names[0]);
|
|
954
|
+
let leader;
|
|
955
|
+
try {
|
|
956
|
+
leader = parseCanonicalLockOwner(import_node_fs4.default.readFileSync(leaderPath, "utf-8"));
|
|
957
|
+
} catch (error) {
|
|
958
|
+
if (error.code === "ENOENT") continue;
|
|
959
|
+
throw error;
|
|
960
|
+
}
|
|
961
|
+
if (leader && isLockOwnerLive(leader, livenessCache)) {
|
|
962
|
+
return { leaderPid: leader.pid, position: position + 1 };
|
|
963
|
+
}
|
|
964
|
+
import_node_fs4.default.rmSync(leaderPath, { force: true });
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
function tryReclaimStaleLock(lockPath, staleLinkPath, livenessCache) {
|
|
911
968
|
let fd;
|
|
912
969
|
try {
|
|
913
970
|
fd = import_node_fs4.default.openSync(lockPath, "r");
|
|
@@ -918,9 +975,9 @@ function tryReclaimStaleLock(lockPath, staleLinkPath) {
|
|
|
918
975
|
throw error;
|
|
919
976
|
}
|
|
920
977
|
try {
|
|
921
|
-
const
|
|
922
|
-
if (owner && isLockOwnerLive(owner)) {
|
|
923
|
-
return { kind: "live", pid: owner.pid };
|
|
978
|
+
const record = parseLockRecord(import_node_fs4.default.readFileSync(fd, "utf-8"));
|
|
979
|
+
if (record.owner && isLockOwnerLive(record.owner, livenessCache)) {
|
|
980
|
+
return { kind: "live", pid: record.owner.pid, acquiredAtMs: record.acquiredAtMs };
|
|
924
981
|
}
|
|
925
982
|
const staleStat = import_node_fs4.default.fstatSync(fd);
|
|
926
983
|
if (staleStat.nlink !== 1) {
|
|
@@ -965,28 +1022,87 @@ function acquireFileLock(lockPath, options) {
|
|
|
965
1022
|
const owner = `${process.pid}:${Buffer.from(processBirth).toString("base64url")}:${ownerId}`;
|
|
966
1023
|
const candidatePath = `${lockPath}.${process.pid}.${ownerId}.candidate`;
|
|
967
1024
|
const staleLinkPath = `${candidatePath}.stale`;
|
|
1025
|
+
const enqueuedAtMs = Date.now();
|
|
1026
|
+
const queueTicketPath = `${lockPath}.queue.${String(enqueuedAtMs).padStart(13, "0")}.${String(process.pid).padStart(10, "0")}.${ownerId}`;
|
|
1027
|
+
const queueCandidatePath = `${queueTicketPath}.candidate`;
|
|
968
1028
|
const deadline = Date.now() + (options.waitMs ?? 0);
|
|
1029
|
+
const waitStartedAt = Date.now();
|
|
1030
|
+
const progressIntervalMs = options.progressIntervalMs ?? DEFAULT_WAIT_PROGRESS_INTERVAL_MS;
|
|
1031
|
+
let lastProgressAt = waitStartedAt;
|
|
969
1032
|
let reclaimAttempts = 0;
|
|
1033
|
+
const livenessCache = /* @__PURE__ */ new Map();
|
|
970
1034
|
import_node_fs4.default.writeFileSync(candidatePath, `${owner}
|
|
1035
|
+
`, {
|
|
1036
|
+
encoding: "utf-8",
|
|
1037
|
+
flag: "wx"
|
|
1038
|
+
});
|
|
1039
|
+
if (options.fair) {
|
|
1040
|
+
import_node_fs4.default.writeFileSync(queueCandidatePath, `${owner}
|
|
971
1041
|
`, { encoding: "utf-8", flag: "wx" });
|
|
1042
|
+
import_node_fs4.default.renameSync(queueCandidatePath, queueTicketPath);
|
|
1043
|
+
}
|
|
972
1044
|
try {
|
|
973
1045
|
for (; ; ) {
|
|
1046
|
+
let queueState;
|
|
1047
|
+
if (options.fair) {
|
|
1048
|
+
queueState = inspectFairQueue(lockPath, queueTicketPath, livenessCache);
|
|
1049
|
+
}
|
|
1050
|
+
if (queueState && queueState.position > 1) {
|
|
1051
|
+
const now = Date.now();
|
|
1052
|
+
if (now >= deadline) {
|
|
1053
|
+
const target = options.target ? ` for ${options.target}` : "";
|
|
1054
|
+
const waitedSeconds = Math.round((now - waitStartedAt) / 1e3);
|
|
1055
|
+
throw new Error(
|
|
1056
|
+
`${options.activity} is still queued${target} (position ${queueState.position}, ahead PID ${queueState.leaderPid}); gave up after waiting ${waitedSeconds}s`
|
|
1057
|
+
);
|
|
1058
|
+
}
|
|
1059
|
+
if (options.onWait && now - lastProgressAt >= progressIntervalMs) {
|
|
1060
|
+
lastProgressAt = now;
|
|
1061
|
+
options.onWait({
|
|
1062
|
+
waitingMs: now - waitStartedAt,
|
|
1063
|
+
holderPid: queueState.leaderPid,
|
|
1064
|
+
queuePosition: queueState.position,
|
|
1065
|
+
waitingOn: "queue"
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
sleepSync(20);
|
|
1069
|
+
continue;
|
|
1070
|
+
}
|
|
1071
|
+
const acquiredAtMs = Date.now();
|
|
1072
|
+
const record = `${owner}:${acquiredAtMs}`;
|
|
1073
|
+
import_node_fs4.default.writeFileSync(candidatePath, `${record}
|
|
1074
|
+
`, "utf-8");
|
|
974
1075
|
try {
|
|
975
1076
|
import_node_fs4.default.linkSync(candidatePath, lockPath);
|
|
976
|
-
return
|
|
1077
|
+
return record;
|
|
977
1078
|
} catch (error) {
|
|
978
1079
|
if (error.code !== "EEXIST") {
|
|
979
1080
|
throw error;
|
|
980
1081
|
}
|
|
981
1082
|
}
|
|
982
|
-
const state = tryReclaimStaleLock(lockPath, staleLinkPath);
|
|
1083
|
+
const state = tryReclaimStaleLock(lockPath, staleLinkPath, livenessCache);
|
|
983
1084
|
if (state.kind === "reclaimed") {
|
|
984
1085
|
continue;
|
|
985
1086
|
}
|
|
986
1087
|
if (state.kind === "live") {
|
|
987
|
-
|
|
1088
|
+
const now = Date.now();
|
|
1089
|
+
if (now >= deadline) {
|
|
988
1090
|
const target = options.target ? ` for ${options.target}` : "";
|
|
989
|
-
|
|
1091
|
+
const waitedSeconds = Math.round((now - waitStartedAt) / 1e3);
|
|
1092
|
+
const heldSeconds = state.acquiredAtMs !== void 0 ? `, held for ${Math.round((now - state.acquiredAtMs) / 1e3)}s` : "";
|
|
1093
|
+
throw new Error(
|
|
1094
|
+
`${options.activity} is already running${target} (PID ${state.pid}${heldSeconds}); gave up after waiting ${waitedSeconds}s`
|
|
1095
|
+
);
|
|
1096
|
+
}
|
|
1097
|
+
if (options.onWait && now - lastProgressAt >= progressIntervalMs) {
|
|
1098
|
+
lastProgressAt = now;
|
|
1099
|
+
options.onWait({
|
|
1100
|
+
waitingMs: now - waitStartedAt,
|
|
1101
|
+
holderPid: state.pid,
|
|
1102
|
+
holderHeldMs: state.acquiredAtMs !== void 0 ? Math.max(0, now - state.acquiredAtMs) : void 0,
|
|
1103
|
+
queuePosition: queueState?.position,
|
|
1104
|
+
waitingOn: "lock"
|
|
1105
|
+
});
|
|
990
1106
|
}
|
|
991
1107
|
sleepSync(20);
|
|
992
1108
|
continue;
|
|
@@ -1002,6 +1118,8 @@ function acquireFileLock(lockPath, options) {
|
|
|
1002
1118
|
} finally {
|
|
1003
1119
|
import_node_fs4.default.rmSync(candidatePath, { force: true });
|
|
1004
1120
|
import_node_fs4.default.rmSync(staleLinkPath, { force: true });
|
|
1121
|
+
import_node_fs4.default.rmSync(queueCandidatePath, { force: true });
|
|
1122
|
+
import_node_fs4.default.rmSync(queueTicketPath, { force: true });
|
|
1005
1123
|
}
|
|
1006
1124
|
}
|
|
1007
1125
|
function releaseFileLock(lockPath, owner) {
|
|
@@ -1031,19 +1149,33 @@ async function withFileLock(lockPath, options, operation) {
|
|
|
1031
1149
|
releaseFileLock(lockPath, owner);
|
|
1032
1150
|
}
|
|
1033
1151
|
}
|
|
1034
|
-
|
|
1152
|
+
function createStderrWaitReporter(activity, target) {
|
|
1153
|
+
return (progress) => {
|
|
1154
|
+
const heldSeconds = progress.holderHeldMs !== void 0 ? `, held for ${Math.round(progress.holderHeldMs / 1e3)}s` : "";
|
|
1155
|
+
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}`;
|
|
1156
|
+
process.stderr.write(
|
|
1157
|
+
`${activity} for ${target}: ${status}; waited ${Math.round(progress.waitingMs / 1e3)}s so far
|
|
1158
|
+
`
|
|
1159
|
+
);
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
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
1163
|
var init_file_lock = __esm({
|
|
1036
1164
|
"src/core/file-lock.ts"() {
|
|
1037
1165
|
"use strict";
|
|
1038
1166
|
import_node_child_process2 = require("child_process");
|
|
1039
1167
|
import_node_crypto2 = require("crypto");
|
|
1040
1168
|
import_node_fs4 = __toESM(require("fs"));
|
|
1169
|
+
import_node_path4 = __toESM(require("path"));
|
|
1170
|
+
DEFAULT_WAIT_PROGRESS_INTERVAL_MS = 1e4;
|
|
1171
|
+
PROCESS_BIRTH_RECHECK_INTERVAL_MS = 1e3;
|
|
1172
|
+
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
1173
|
}
|
|
1042
1174
|
});
|
|
1043
1175
|
|
|
1044
1176
|
// src/core/workspace.ts
|
|
1045
1177
|
function comparableWorkspacePath(filePath) {
|
|
1046
|
-
const resolved =
|
|
1178
|
+
const resolved = import_node_path5.default.resolve(filePath);
|
|
1047
1179
|
try {
|
|
1048
1180
|
return import_node_fs5.default.realpathSync.native(resolved);
|
|
1049
1181
|
} catch {
|
|
@@ -1073,7 +1205,7 @@ function workspaceIdentityCandidates(source) {
|
|
|
1073
1205
|
return candidates;
|
|
1074
1206
|
}
|
|
1075
1207
|
function isLinkedWorktree(repoPath) {
|
|
1076
|
-
const gitPath =
|
|
1208
|
+
const gitPath = import_node_path5.default.join(repoPath, ".git");
|
|
1077
1209
|
let stat;
|
|
1078
1210
|
try {
|
|
1079
1211
|
stat = import_node_fs5.default.statSync(gitPath);
|
|
@@ -1097,7 +1229,7 @@ function isLinkedWorktree(repoPath) {
|
|
|
1097
1229
|
return /(^|\/)worktrees\//.test(gitdir);
|
|
1098
1230
|
}
|
|
1099
1231
|
function gitMetadataDir(repoPath) {
|
|
1100
|
-
const gitPath =
|
|
1232
|
+
const gitPath = import_node_path5.default.join(repoPath, ".git");
|
|
1101
1233
|
let stat;
|
|
1102
1234
|
try {
|
|
1103
1235
|
stat = import_node_fs5.default.statSync(gitPath);
|
|
@@ -1114,7 +1246,7 @@ function gitMetadataDir(repoPath) {
|
|
|
1114
1246
|
if (!match) {
|
|
1115
1247
|
return void 0;
|
|
1116
1248
|
}
|
|
1117
|
-
return
|
|
1249
|
+
return import_node_path5.default.resolve(repoPath, match[1].trim());
|
|
1118
1250
|
}
|
|
1119
1251
|
function validatePersistedWorkspace(value) {
|
|
1120
1252
|
const workspace = value.trim();
|
|
@@ -1128,7 +1260,7 @@ function readPersistedWorkspace(repoPath) {
|
|
|
1128
1260
|
if (!gitDir) {
|
|
1129
1261
|
return void 0;
|
|
1130
1262
|
}
|
|
1131
|
-
const metadataPath =
|
|
1263
|
+
const metadataPath = import_node_path5.default.join(gitDir, WORKSPACE_METADATA_FILE);
|
|
1132
1264
|
try {
|
|
1133
1265
|
return validatePersistedWorkspace(import_node_fs5.default.readFileSync(metadataPath, "utf-8"));
|
|
1134
1266
|
} catch (error) {
|
|
@@ -1153,7 +1285,7 @@ function persistWorkspace(repoPath, value) {
|
|
|
1153
1285
|
if (!gitDir) {
|
|
1154
1286
|
throw new Error(`cannot persist workspace identity: '${repoPath}' is not a Git checkout`);
|
|
1155
1287
|
}
|
|
1156
|
-
writeFileAtomically(
|
|
1288
|
+
writeFileAtomically(import_node_path5.default.join(gitDir, WORKSPACE_METADATA_FILE), `${workspace}
|
|
1157
1289
|
`);
|
|
1158
1290
|
return workspace;
|
|
1159
1291
|
}
|
|
@@ -1162,7 +1294,7 @@ async function withWorkspaceLifecycleLock(repoPath, operation) {
|
|
|
1162
1294
|
if (!gitDir) {
|
|
1163
1295
|
throw new Error(`cannot lock workspace lifecycle: '${repoPath}' is not a Git checkout`);
|
|
1164
1296
|
}
|
|
1165
|
-
const lockPath =
|
|
1297
|
+
const lockPath = import_node_path5.default.join(gitDir, WORKSPACE_LOCK_FILE);
|
|
1166
1298
|
return withFileLock(
|
|
1167
1299
|
lockPath,
|
|
1168
1300
|
{ activity: "workspace lifecycle", target: `'${repoPath}'` },
|
|
@@ -1190,7 +1322,7 @@ function deriveLinkedWorktreeWorkspace(repoPath, linkedWorktreeBranch) {
|
|
|
1190
1322
|
return void 0;
|
|
1191
1323
|
}
|
|
1192
1324
|
const branch = currentBranch(repoPath);
|
|
1193
|
-
return wsFromBranch(branch ??
|
|
1325
|
+
return wsFromBranch(branch ?? import_node_path5.default.basename(import_node_path5.default.resolve(repoPath)));
|
|
1194
1326
|
}
|
|
1195
1327
|
function resolveWorktreeWorkspace(repoPath, linkedWorktreeBranch) {
|
|
1196
1328
|
return readPersistedWorkspace(repoPath) ?? deriveLinkedWorktreeWorkspace(repoPath, linkedWorktreeBranch);
|
|
@@ -1213,14 +1345,14 @@ function resolveWorkspace(repoPath, override) {
|
|
|
1213
1345
|
}
|
|
1214
1346
|
return deriveLinkedWorktreeWorkspace(repoPath);
|
|
1215
1347
|
}
|
|
1216
|
-
var import_node_child_process3, import_node_crypto3, import_node_fs5,
|
|
1348
|
+
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
1349
|
var init_workspace = __esm({
|
|
1218
1350
|
"src/core/workspace.ts"() {
|
|
1219
1351
|
"use strict";
|
|
1220
1352
|
import_node_child_process3 = require("child_process");
|
|
1221
1353
|
import_node_crypto3 = require("crypto");
|
|
1222
1354
|
import_node_fs5 = __toESM(require("fs"));
|
|
1223
|
-
|
|
1355
|
+
import_node_path5 = __toESM(require("path"));
|
|
1224
1356
|
init_atomic_file();
|
|
1225
1357
|
init_file_lock();
|
|
1226
1358
|
MAX_WORKSPACE_LENGTH = 32;
|
|
@@ -1282,6 +1414,12 @@ function hostRouteFromInput(input2, existing, now) {
|
|
|
1282
1414
|
function sanitizeKey(value) {
|
|
1283
1415
|
return value.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
1284
1416
|
}
|
|
1417
|
+
function buildHostRouteRouterKey(routeId) {
|
|
1418
|
+
return `host-${sanitizeKey(routeId)}`;
|
|
1419
|
+
}
|
|
1420
|
+
function buildHostRouteRouterName(repoPath, name) {
|
|
1421
|
+
return `${buildHostRouteRouterKey(buildHostRouteId(repoPath, name))}@file`;
|
|
1422
|
+
}
|
|
1285
1423
|
function tcpTlsOptionName(tcpProtocol) {
|
|
1286
1424
|
return `devrouter-tcp-${tcpProtocol}`;
|
|
1287
1425
|
}
|
|
@@ -1292,7 +1430,7 @@ function buildHostRoutesDocument(routes, tlsEnabled) {
|
|
|
1292
1430
|
const tcpServices = {};
|
|
1293
1431
|
const tlsOptions = {};
|
|
1294
1432
|
for (const route of routes) {
|
|
1295
|
-
const key =
|
|
1433
|
+
const key = buildHostRouteRouterKey(route.id);
|
|
1296
1434
|
if (route.protocol === "tcp") {
|
|
1297
1435
|
const alpn = route.tcpProtocol ? TCP_TLS_ALPN_PROTOCOLS[route.tcpProtocol] : void 0;
|
|
1298
1436
|
let tls = {};
|
|
@@ -1644,7 +1782,7 @@ function listHostRoutes(tlsEnabled) {
|
|
|
1644
1782
|
protocol: isTcp ? `tcp/${tcpProtocol}` : "http",
|
|
1645
1783
|
appName: route.name,
|
|
1646
1784
|
serviceName: route.name,
|
|
1647
|
-
projectName:
|
|
1785
|
+
projectName: import_node_path6.default.basename(route.repoPath),
|
|
1648
1786
|
hosts: [route.host],
|
|
1649
1787
|
// TCP routes are reached via an SNI-aware client on the shared protocol
|
|
1650
1788
|
// port; surface a protocol-scheme URL rather than http(s)://.
|
|
@@ -1659,12 +1797,12 @@ function listHostRoutes(tlsEnabled) {
|
|
|
1659
1797
|
};
|
|
1660
1798
|
});
|
|
1661
1799
|
}
|
|
1662
|
-
var import_node_fs6,
|
|
1800
|
+
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
1801
|
var init_host_routes = __esm({
|
|
1664
1802
|
"src/core/host-routes.ts"() {
|
|
1665
1803
|
"use strict";
|
|
1666
1804
|
import_node_fs6 = __toESM(require("fs"));
|
|
1667
|
-
|
|
1805
|
+
import_node_path6 = __toESM(require("path"));
|
|
1668
1806
|
import_node_util = require("util");
|
|
1669
1807
|
import_yaml = __toESM(require("yaml"));
|
|
1670
1808
|
init_atomic_file();
|
|
@@ -2512,10 +2650,10 @@ function appToNodeValue(app) {
|
|
|
2512
2650
|
return value;
|
|
2513
2651
|
}
|
|
2514
2652
|
function resolveRepoPath(repoPath) {
|
|
2515
|
-
return
|
|
2653
|
+
return import_node_path7.default.resolve(repoPath ?? process.cwd());
|
|
2516
2654
|
}
|
|
2517
2655
|
function getRepoConfigPath(repoPath) {
|
|
2518
|
-
return
|
|
2656
|
+
return import_node_path7.default.join(resolveRepoPath(repoPath), CONFIG_FILE_NAME);
|
|
2519
2657
|
}
|
|
2520
2658
|
function loadRepoConfig(repoPath) {
|
|
2521
2659
|
const resolvedRepoPath = resolveRepoPath(repoPath);
|
|
@@ -2530,7 +2668,7 @@ function loadRepoConfig(repoPath) {
|
|
|
2530
2668
|
const config = parseConfig(parsed ?? {}, configPath);
|
|
2531
2669
|
const requiredVersion = config.devrouter?.version;
|
|
2532
2670
|
if (requiredVersion && !hasWarnedVersionMismatch) {
|
|
2533
|
-
const cliVersion = true ? "0.0.
|
|
2671
|
+
const cliVersion = true ? "0.0.48" : "0.0.0-dev";
|
|
2534
2672
|
if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
|
|
2535
2673
|
hasWarnedVersionMismatch = true;
|
|
2536
2674
|
process.stderr.write(
|
|
@@ -2563,7 +2701,7 @@ function initRepoConfig(repoPath, options = {}) {
|
|
|
2563
2701
|
}
|
|
2564
2702
|
} : {},
|
|
2565
2703
|
project: {
|
|
2566
|
-
name:
|
|
2704
|
+
name: import_node_path7.default.basename(resolvedRepoPath)
|
|
2567
2705
|
},
|
|
2568
2706
|
apps: []
|
|
2569
2707
|
};
|
|
@@ -2792,7 +2930,7 @@ function namespaceHost(host, workspace) {
|
|
|
2792
2930
|
return `${base}.${workspace}${suffix}`;
|
|
2793
2931
|
}
|
|
2794
2932
|
function applyWorkspace(config, workspace, repoPath) {
|
|
2795
|
-
const defaultToken = wsFromBranch(config.project?.name ??
|
|
2933
|
+
const defaultToken = wsFromBranch(config.project?.name ?? import_node_path7.default.basename(import_node_path7.default.resolve(repoPath))) ?? "app";
|
|
2796
2934
|
const substitutionToken = workspace ?? defaultToken;
|
|
2797
2935
|
const next = structuredClone(config);
|
|
2798
2936
|
for (const app of next.apps) {
|
|
@@ -2874,12 +3012,12 @@ function resolveAppDependencies(config, app) {
|
|
|
2874
3012
|
}
|
|
2875
3013
|
return results;
|
|
2876
3014
|
}
|
|
2877
|
-
var import_node_fs7,
|
|
3015
|
+
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
3016
|
var init_repo_config = __esm({
|
|
2879
3017
|
"src/core/repo-config.ts"() {
|
|
2880
3018
|
"use strict";
|
|
2881
3019
|
import_node_fs7 = __toESM(require("fs"));
|
|
2882
|
-
|
|
3020
|
+
import_node_path7 = __toESM(require("path"));
|
|
2883
3021
|
import_yaml2 = __toESM(require("yaml"));
|
|
2884
3022
|
init_capabilities();
|
|
2885
3023
|
init_host_routes();
|
|
@@ -3060,7 +3198,8 @@ function buildOnboardingPrompt(options = {}) {
|
|
|
3060
3198
|
"- 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
3199
|
`- 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
3200
|
"- 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.",
|
|
3201
|
+
"- 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.",
|
|
3202
|
+
"- Devsy agent readiness: run `devrouter setup --yes --workspace-runtime devsy` once so Devrouter acquires and verifies the pinned agent in its own machine cache. `doctor` reports `ready`, `missing`, `stale`, or `invalid` without network access, and `ensure` fails before the provider queue when readiness is not `ready`. An explicit `DEVSY_AGENT_BINARY` remains authoritative and must match a pinned official asset.",
|
|
3064
3203
|
"- A managed adapter plus `postCreateCommand` requires `waitFor` exactly `postCreateCommand` or `postStartCommand`; managed selective config preserves lifecycle fields and changes only `runServices`.",
|
|
3065
3204
|
"- 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
3205
|
"- 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.",
|
|
@@ -3092,7 +3231,7 @@ function buildOnboardingPrompt(options = {}) {
|
|
|
3092
3231
|
"- Warning: Do not run migration/seed until env probe confirms expected DB variables and values.",
|
|
3093
3232
|
"",
|
|
3094
3233
|
"Required workflow:",
|
|
3095
|
-
"1) Run `devrouter setup --yes --json` for devrouter-owned machine state
|
|
3234
|
+
"1) Run `devrouter setup --yes --json` for devrouter-owned machine state. When selecting Devsy, use `devrouter setup --yes --workspace-runtime devsy --json` to acquire its verified agent. Use `devrouter doctor --repo <REPO_PATH> --json` to diagnose missing prerequisites without mutation.",
|
|
3096
3235
|
"2) Run `devrouter repo inspect --repo <REPO_PATH> --json` before editing files.",
|
|
3097
3236
|
"3) For the supported Node/pnpm/Postgres devcontainer shape, run `devrouter repo devcontainer write --repo <REPO_PATH> --dry-run --json`, review the plan, then run `devrouter repo devcontainer write --repo <REPO_PATH> --yes`.",
|
|
3098
3237
|
"4) For unsupported shapes or custom existing files, make minimal manual edits and explain the assumptions.",
|
|
@@ -3102,6 +3241,7 @@ function buildOnboardingPrompt(options = {}) {
|
|
|
3102
3241
|
"",
|
|
3103
3242
|
"Validation commands to run/report for the devcontainer path:",
|
|
3104
3243
|
"- devrouter setup --yes --json",
|
|
3244
|
+
"- Devsy runtime setup: devrouter setup --yes --workspace-runtime devsy --json",
|
|
3105
3245
|
"- devrouter doctor --repo <REPO_PATH> --json",
|
|
3106
3246
|
"- devrouter repo inspect --repo <REPO_PATH> --json",
|
|
3107
3247
|
"- devrouter repo devcontainer write --repo <REPO_PATH> --dry-run --json",
|
|
@@ -3229,6 +3369,10 @@ var init_ai_prompt = __esm({
|
|
|
3229
3369
|
command: "devrouter ensure [path] [--profile <name>] [--open] [--json]",
|
|
3230
3370
|
purpose: "Canonical startup and proof for an exact primary or linked checkout; atomically publishes routes after readiness."
|
|
3231
3371
|
},
|
|
3372
|
+
{
|
|
3373
|
+
command: "devrouter profile resolve --repo <path> [--profile <selection>] [--json]",
|
|
3374
|
+
purpose: "Resolve exact profile resources without starting or inspecting a runtime; fail closed on invalid selections or unknown report schemas."
|
|
3375
|
+
},
|
|
3232
3376
|
{
|
|
3233
3377
|
command: "devrouter stop [path] [--delete] [--json]",
|
|
3234
3378
|
purpose: "Stop the exact checkout workspace runtime and routes; --delete explicitly deletes the ownership-proven runtime without removing the checkout."
|
|
@@ -3787,7 +3931,7 @@ function readPromptDirectory(promptDirPath) {
|
|
|
3787
3931
|
continue;
|
|
3788
3932
|
}
|
|
3789
3933
|
const version = normalizeVersion(basename);
|
|
3790
|
-
const promptPath =
|
|
3934
|
+
const promptPath = import_node_path8.default.join(promptDirPath, file.name);
|
|
3791
3935
|
const prompt = import_node_fs8.default.readFileSync(promptPath, "utf-8").trim();
|
|
3792
3936
|
releases.push({ version, prompt, promptPath });
|
|
3793
3937
|
}
|
|
@@ -3809,17 +3953,17 @@ function listAvailableUpgradeTargets(currentVersion, releases) {
|
|
|
3809
3953
|
}
|
|
3810
3954
|
function resolvePromptDirectory(explicitPath) {
|
|
3811
3955
|
if (explicitPath) {
|
|
3812
|
-
return
|
|
3956
|
+
return import_node_path8.default.resolve(explicitPath);
|
|
3813
3957
|
}
|
|
3814
|
-
const entryFile = process.argv[1] ?
|
|
3815
|
-
const entryDir =
|
|
3816
|
-
const resolvedEntryDir = import_node_fs8.default.existsSync(entryFile) ?
|
|
3958
|
+
const entryFile = process.argv[1] ? import_node_path8.default.resolve(process.argv[1]) : __filename;
|
|
3959
|
+
const entryDir = import_node_path8.default.dirname(entryFile);
|
|
3960
|
+
const resolvedEntryDir = import_node_fs8.default.existsSync(entryFile) ? import_node_path8.default.dirname(import_node_fs8.default.realpathSync(entryFile)) : entryDir;
|
|
3817
3961
|
const candidates = [
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3962
|
+
import_node_path8.default.resolve(resolvedEntryDir, "..", UPGRADE_PROMPTS_DIR),
|
|
3963
|
+
import_node_path8.default.resolve(entryDir, "..", UPGRADE_PROMPTS_DIR),
|
|
3964
|
+
import_node_path8.default.resolve(__dirname, "..", UPGRADE_PROMPTS_DIR),
|
|
3965
|
+
import_node_path8.default.resolve(__dirname, "..", "..", UPGRADE_PROMPTS_DIR),
|
|
3966
|
+
import_node_path8.default.resolve(process.cwd(), UPGRADE_PROMPTS_DIR)
|
|
3823
3967
|
];
|
|
3824
3968
|
const existing = candidates.find((candidate) => import_node_fs8.default.existsSync(candidate));
|
|
3825
3969
|
return existing ?? candidates[0];
|
|
@@ -3837,12 +3981,12 @@ function loadUpgradeCatalog(options = {}) {
|
|
|
3837
3981
|
releases
|
|
3838
3982
|
};
|
|
3839
3983
|
}
|
|
3840
|
-
var import_node_fs8,
|
|
3984
|
+
var import_node_fs8, import_node_path8, SEMVER_RE, UPGRADE_PROMPTS_DIR;
|
|
3841
3985
|
var init_upgrade = __esm({
|
|
3842
3986
|
"src/core/upgrade.ts"() {
|
|
3843
3987
|
"use strict";
|
|
3844
3988
|
import_node_fs8 = __toESM(require("fs"));
|
|
3845
|
-
|
|
3989
|
+
import_node_path8 = __toESM(require("path"));
|
|
3846
3990
|
init_repo_config();
|
|
3847
3991
|
SEMVER_RE = /^v?(\d+)\.(\d+)\.(\d+)$/;
|
|
3848
3992
|
UPGRADE_PROMPTS_DIR = "upgrade-prompts";
|
|
@@ -3931,6 +4075,384 @@ var init_upgrade2 = __esm({
|
|
|
3931
4075
|
}
|
|
3932
4076
|
});
|
|
3933
4077
|
|
|
4078
|
+
// src/core/devsy-agent.ts
|
|
4079
|
+
function parseVersion(output2) {
|
|
4080
|
+
return output2?.match(
|
|
4081
|
+
/\bv?(\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?)\b/
|
|
4082
|
+
)?.[1];
|
|
4083
|
+
}
|
|
4084
|
+
function devsyAgentRepairSuggestion(inspection) {
|
|
4085
|
+
if (inspection.state === "stale") {
|
|
4086
|
+
return `Install Devsy ${SUPPORTED_DEVSY_VERSION} for a supported host, then run: ${DEVSY_AGENT_SETUP_COMMAND}`;
|
|
4087
|
+
}
|
|
4088
|
+
return inspection.source === "explicit" ? `Fix or unset DEVSY_AGENT_BINARY, then run: ${DEVSY_AGENT_SETUP_COMMAND}` : `Run: ${DEVSY_AGENT_SETUP_COMMAND}`;
|
|
4089
|
+
}
|
|
4090
|
+
function installedVersionOutput() {
|
|
4091
|
+
const result = (0, import_node_child_process4.spawnSync)("devsy", ["--version"], { encoding: "utf-8" });
|
|
4092
|
+
if (result.error || result.status !== 0) return void 0;
|
|
4093
|
+
return [result.stdout, result.stderr].filter(Boolean).join("\n").trim() || void 0;
|
|
4094
|
+
}
|
|
4095
|
+
function nativeAssetName(platform, arch) {
|
|
4096
|
+
if (platform !== "darwin") return void 0;
|
|
4097
|
+
if (arch === "arm64") return "devsy-linux-arm64";
|
|
4098
|
+
if (arch === "x64") return "devsy-linux-amd64";
|
|
4099
|
+
return void 0;
|
|
4100
|
+
}
|
|
4101
|
+
function managedBinaryPath(cacheRoot, asset) {
|
|
4102
|
+
return import_node_path9.default.join(cacheRoot, `v${SUPPORTED_DEVSY_VERSION}`, asset.name);
|
|
4103
|
+
}
|
|
4104
|
+
function hashOpenFile(file) {
|
|
4105
|
+
const digest = (0, import_node_crypto4.createHash)("sha256");
|
|
4106
|
+
const buffer = Buffer.allocUnsafe(1024 * 1024);
|
|
4107
|
+
let position = 0;
|
|
4108
|
+
for (; ; ) {
|
|
4109
|
+
const bytesRead = import_node_fs9.default.readSync(file, buffer, 0, buffer.length, position);
|
|
4110
|
+
if (bytesRead === 0) break;
|
|
4111
|
+
digest.update(buffer.subarray(0, bytesRead));
|
|
4112
|
+
position += bytesRead;
|
|
4113
|
+
}
|
|
4114
|
+
return digest.digest("hex");
|
|
4115
|
+
}
|
|
4116
|
+
function inspectBinary(binaryPath, assets, missingState) {
|
|
4117
|
+
let file;
|
|
4118
|
+
try {
|
|
4119
|
+
file = import_node_fs9.default.openSync(binaryPath, import_node_fs9.default.constants.O_RDONLY | import_node_fs9.default.constants.O_NOFOLLOW);
|
|
4120
|
+
const stat = import_node_fs9.default.fstatSync(file);
|
|
4121
|
+
if (!stat.isFile()) {
|
|
4122
|
+
return { state: "invalid", reason: "the selected source is not a regular file" };
|
|
4123
|
+
}
|
|
4124
|
+
const sizeMatches = assets.filter((asset2) => asset2.size === stat.size);
|
|
4125
|
+
if (sizeMatches.length === 0) {
|
|
4126
|
+
return { state: "invalid", reason: "the selected source has an unexpected size" };
|
|
4127
|
+
}
|
|
4128
|
+
const sha2562 = hashOpenFile(file);
|
|
4129
|
+
const asset = sizeMatches.find((candidate) => candidate.sha256 === sha2562);
|
|
4130
|
+
return asset ? { state: "ready", reason: "the selected source matches the pinned manifest", asset } : { state: "invalid", reason: "the selected source has an unexpected digest" };
|
|
4131
|
+
} catch (error) {
|
|
4132
|
+
const code = error.code;
|
|
4133
|
+
return code === "ENOENT" ? { state: missingState, reason: "the selected source is missing" } : { state: "invalid", reason: "the selected source is not a readable regular file" };
|
|
4134
|
+
} finally {
|
|
4135
|
+
if (file !== void 0) import_node_fs9.default.closeSync(file);
|
|
4136
|
+
}
|
|
4137
|
+
}
|
|
4138
|
+
function resolvedOptions(options) {
|
|
4139
|
+
return {
|
|
4140
|
+
env: options.env ?? process.env,
|
|
4141
|
+
platform: options.platform ?? process.platform,
|
|
4142
|
+
arch: options.arch ?? process.arch,
|
|
4143
|
+
versionOutput: options.versionOutput ?? installedVersionOutput(),
|
|
4144
|
+
cacheRoot: options.cacheRoot ?? import_node_path9.default.join(CACHE_DIR, "devsy", "agents"),
|
|
4145
|
+
lockPath: options.lockPath ?? DEVSY_AGENT_CACHE_LOCK,
|
|
4146
|
+
assets: options.assets ?? DEVSY_AGENT_ASSETS,
|
|
4147
|
+
nativeAssetName: options.nativeAssetName
|
|
4148
|
+
};
|
|
4149
|
+
}
|
|
4150
|
+
function inspectDevsyAgent(options = {}) {
|
|
4151
|
+
const resolved = resolvedOptions(options);
|
|
4152
|
+
const installedVersion = parseVersion(resolved.versionOutput);
|
|
4153
|
+
const explicitPath = resolved.env.DEVSY_AGENT_BINARY?.trim();
|
|
4154
|
+
const source = explicitPath ? "explicit" : "managed";
|
|
4155
|
+
if (installedVersion !== SUPPORTED_DEVSY_VERSION) {
|
|
4156
|
+
return {
|
|
4157
|
+
state: "stale",
|
|
4158
|
+
source,
|
|
4159
|
+
reason: installedVersion ? `installed Devsy ${installedVersion} is not supported by this Devrouter release` : "the supported Devsy version is not available",
|
|
4160
|
+
installedVersion
|
|
4161
|
+
};
|
|
4162
|
+
}
|
|
4163
|
+
if (explicitPath) {
|
|
4164
|
+
return {
|
|
4165
|
+
...inspectBinary(explicitPath, resolved.assets, "invalid"),
|
|
4166
|
+
source,
|
|
4167
|
+
binaryPath: explicitPath,
|
|
4168
|
+
installedVersion
|
|
4169
|
+
};
|
|
4170
|
+
}
|
|
4171
|
+
const expectedName = resolved.nativeAssetName ?? nativeAssetName(resolved.platform, resolved.arch);
|
|
4172
|
+
const asset = resolved.assets.find((candidate) => candidate.name === expectedName);
|
|
4173
|
+
if (!asset) {
|
|
4174
|
+
return {
|
|
4175
|
+
state: "stale",
|
|
4176
|
+
source,
|
|
4177
|
+
reason: `this Devrouter release has no pinned Devsy agent for ${resolved.platform}/${resolved.arch}`,
|
|
4178
|
+
installedVersion
|
|
4179
|
+
};
|
|
4180
|
+
}
|
|
4181
|
+
const binaryPath = managedBinaryPath(resolved.cacheRoot, asset);
|
|
4182
|
+
return {
|
|
4183
|
+
...inspectBinary(binaryPath, [asset], "missing"),
|
|
4184
|
+
source,
|
|
4185
|
+
binaryPath,
|
|
4186
|
+
asset,
|
|
4187
|
+
installedVersion
|
|
4188
|
+
};
|
|
4189
|
+
}
|
|
4190
|
+
async function writeAll(handle, chunk, position) {
|
|
4191
|
+
let offset = 0;
|
|
4192
|
+
while (offset < chunk.byteLength) {
|
|
4193
|
+
const { bytesWritten } = await handle.write(
|
|
4194
|
+
chunk,
|
|
4195
|
+
offset,
|
|
4196
|
+
chunk.byteLength - offset,
|
|
4197
|
+
position + offset
|
|
4198
|
+
);
|
|
4199
|
+
if (bytesWritten === 0) throw new Error("Devsy agent download stopped making progress");
|
|
4200
|
+
offset += bytesWritten;
|
|
4201
|
+
}
|
|
4202
|
+
}
|
|
4203
|
+
function appendBoundedTail(current, chunk) {
|
|
4204
|
+
if (chunk.length >= GITHUB_CLI_STDERR_TAIL_BYTES) {
|
|
4205
|
+
return Buffer.from(chunk.subarray(chunk.length - GITHUB_CLI_STDERR_TAIL_BYTES));
|
|
4206
|
+
}
|
|
4207
|
+
const combined = Buffer.concat([current, chunk]);
|
|
4208
|
+
return combined.length > GITHUB_CLI_STDERR_TAIL_BYTES ? combined.subarray(combined.length - GITHUB_CLI_STDERR_TAIL_BYTES) : combined;
|
|
4209
|
+
}
|
|
4210
|
+
function errorMessage(error) {
|
|
4211
|
+
return error instanceof Error ? error.message : String(error);
|
|
4212
|
+
}
|
|
4213
|
+
async function downloadWithGitHubCli(asset, writeChunk) {
|
|
4214
|
+
const child = (0, import_node_child_process4.spawn)(
|
|
4215
|
+
"gh",
|
|
4216
|
+
[
|
|
4217
|
+
"api",
|
|
4218
|
+
`repos/devsy-org/devsy/releases/assets/${asset.githubAssetId}`,
|
|
4219
|
+
"-H",
|
|
4220
|
+
"Accept: application/octet-stream"
|
|
4221
|
+
],
|
|
4222
|
+
{ stdio: ["ignore", "pipe", "pipe"] }
|
|
4223
|
+
);
|
|
4224
|
+
if (!child.stdout || !child.stderr) {
|
|
4225
|
+
child.kill();
|
|
4226
|
+
throw new Error("GitHub CLI fallback did not create output pipes");
|
|
4227
|
+
}
|
|
4228
|
+
let stderrTail = Buffer.alloc(0);
|
|
4229
|
+
child.stderr.on("data", (chunk) => {
|
|
4230
|
+
const value = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
4231
|
+
stderrTail = appendBoundedTail(stderrTail, value);
|
|
4232
|
+
});
|
|
4233
|
+
let spawnError;
|
|
4234
|
+
const completion = new Promise((resolve) => {
|
|
4235
|
+
child.once("error", (error) => {
|
|
4236
|
+
spawnError = error;
|
|
4237
|
+
});
|
|
4238
|
+
child.once("close", (status) => {
|
|
4239
|
+
resolve(status);
|
|
4240
|
+
});
|
|
4241
|
+
});
|
|
4242
|
+
try {
|
|
4243
|
+
for await (const chunk of child.stdout) {
|
|
4244
|
+
await writeChunk(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
4245
|
+
}
|
|
4246
|
+
const status = await completion;
|
|
4247
|
+
if (spawnError) throw spawnError;
|
|
4248
|
+
if (status !== 0) {
|
|
4249
|
+
const details = stderrTail.toString("utf-8").trim();
|
|
4250
|
+
throw new Error(
|
|
4251
|
+
`gh api exited with status ${status ?? "unknown"}${details ? `: ${details}` : ""}`
|
|
4252
|
+
);
|
|
4253
|
+
}
|
|
4254
|
+
} catch (error) {
|
|
4255
|
+
if (child.exitCode === null) child.kill();
|
|
4256
|
+
await completion.catch(() => void 0);
|
|
4257
|
+
throw error;
|
|
4258
|
+
}
|
|
4259
|
+
}
|
|
4260
|
+
function fsyncDirectory2(directory) {
|
|
4261
|
+
const handle = import_node_fs9.default.openSync(directory, "r");
|
|
4262
|
+
try {
|
|
4263
|
+
import_node_fs9.default.fsyncSync(handle);
|
|
4264
|
+
} finally {
|
|
4265
|
+
import_node_fs9.default.closeSync(handle);
|
|
4266
|
+
}
|
|
4267
|
+
}
|
|
4268
|
+
async function downloadAndPublish(asset, binaryPath, fetcher, githubCliDownloader) {
|
|
4269
|
+
const directory = import_node_path9.default.dirname(binaryPath);
|
|
4270
|
+
import_node_fs9.default.mkdirSync(directory, { recursive: true, mode: 448 });
|
|
4271
|
+
const temporaryPath = import_node_path9.default.join(
|
|
4272
|
+
directory,
|
|
4273
|
+
`.${import_node_path9.default.basename(binaryPath)}.${process.pid}.${(0, import_node_crypto4.randomUUID)()}.tmp`
|
|
4274
|
+
);
|
|
4275
|
+
let handle;
|
|
4276
|
+
try {
|
|
4277
|
+
let response;
|
|
4278
|
+
let directFailure;
|
|
4279
|
+
try {
|
|
4280
|
+
response = await fetcher(asset.url);
|
|
4281
|
+
} catch (error) {
|
|
4282
|
+
directFailure = error;
|
|
4283
|
+
}
|
|
4284
|
+
handle = await import_node_fs9.default.promises.open(temporaryPath, "wx", 384);
|
|
4285
|
+
const digest = (0, import_node_crypto4.createHash)("sha256");
|
|
4286
|
+
let size = 0;
|
|
4287
|
+
const writeChunk = async (chunk) => {
|
|
4288
|
+
size += chunk.byteLength;
|
|
4289
|
+
if (size > asset.size) {
|
|
4290
|
+
throw new Error("Devsy agent download exceeded the pinned size");
|
|
4291
|
+
}
|
|
4292
|
+
digest.update(chunk);
|
|
4293
|
+
await writeAll(handle, chunk, size - chunk.byteLength);
|
|
4294
|
+
};
|
|
4295
|
+
let transport;
|
|
4296
|
+
if (response) {
|
|
4297
|
+
if (!response.ok) {
|
|
4298
|
+
throw new Error(`Devsy agent download failed with HTTP ${response.status}`);
|
|
4299
|
+
}
|
|
4300
|
+
if (!response.body) throw new Error("Devsy agent download returned no response body");
|
|
4301
|
+
const reader = response.body.getReader();
|
|
4302
|
+
try {
|
|
4303
|
+
for (; ; ) {
|
|
4304
|
+
const { done, value } = await reader.read();
|
|
4305
|
+
if (done) break;
|
|
4306
|
+
await writeChunk(value);
|
|
4307
|
+
}
|
|
4308
|
+
} catch (error) {
|
|
4309
|
+
await reader.cancel().catch(() => void 0);
|
|
4310
|
+
throw error;
|
|
4311
|
+
}
|
|
4312
|
+
transport = "https";
|
|
4313
|
+
} else {
|
|
4314
|
+
try {
|
|
4315
|
+
await githubCliDownloader(asset, writeChunk);
|
|
4316
|
+
} catch (fallbackError) {
|
|
4317
|
+
throw new Error(
|
|
4318
|
+
`Devsy agent direct HTTPS download failed: ${errorMessage(directFailure)}; GitHub CLI fallback failed: ${errorMessage(fallbackError)}. Restore direct GitHub release access or install and authenticate gh.`
|
|
4319
|
+
);
|
|
4320
|
+
}
|
|
4321
|
+
transport = "github-cli";
|
|
4322
|
+
}
|
|
4323
|
+
if (size !== asset.size) throw new Error("Devsy agent download has an unexpected size");
|
|
4324
|
+
if (digest.digest("hex") !== asset.sha256) {
|
|
4325
|
+
throw new Error("Devsy agent download has an unexpected digest");
|
|
4326
|
+
}
|
|
4327
|
+
await handle.sync();
|
|
4328
|
+
await handle.chmod(448);
|
|
4329
|
+
await handle.sync();
|
|
4330
|
+
await handle.close();
|
|
4331
|
+
handle = void 0;
|
|
4332
|
+
await import_node_fs9.default.promises.rename(temporaryPath, binaryPath);
|
|
4333
|
+
fsyncDirectory2(directory);
|
|
4334
|
+
return transport;
|
|
4335
|
+
} finally {
|
|
4336
|
+
if (handle) await handle.close();
|
|
4337
|
+
await import_node_fs9.default.promises.rm(temporaryPath, { force: true });
|
|
4338
|
+
}
|
|
4339
|
+
}
|
|
4340
|
+
async function prepareDevsyAgent(options = {}) {
|
|
4341
|
+
const resolved = resolvedOptions(options);
|
|
4342
|
+
const inspectOptions = resolved;
|
|
4343
|
+
const before = inspectDevsyAgent(inspectOptions);
|
|
4344
|
+
if (before.state === "ready" && before.binaryPath && before.asset) {
|
|
4345
|
+
return {
|
|
4346
|
+
binaryPath: before.binaryPath,
|
|
4347
|
+
source: before.source,
|
|
4348
|
+
asset: before.asset,
|
|
4349
|
+
changed: false,
|
|
4350
|
+
transport: "existing"
|
|
4351
|
+
};
|
|
4352
|
+
}
|
|
4353
|
+
if (before.source === "explicit" || before.state === "stale" || !before.binaryPath || !before.asset) {
|
|
4354
|
+
throw new DevsyAgentReadinessError(before);
|
|
4355
|
+
}
|
|
4356
|
+
const lock = options.withLock ?? ((operation) => {
|
|
4357
|
+
import_node_fs9.default.mkdirSync(import_node_path9.default.dirname(resolved.lockPath), { recursive: true });
|
|
4358
|
+
return withFileLock(
|
|
4359
|
+
resolved.lockPath,
|
|
4360
|
+
{
|
|
4361
|
+
activity: "Devsy agent acquisition",
|
|
4362
|
+
waitMs: DEVSY_AGENT_LOCK_WAIT_MS,
|
|
4363
|
+
fair: true,
|
|
4364
|
+
onWait: createStderrWaitReporter("Devsy agent acquisition", "the managed cache")
|
|
4365
|
+
},
|
|
4366
|
+
operation
|
|
4367
|
+
);
|
|
4368
|
+
});
|
|
4369
|
+
return lock(async () => {
|
|
4370
|
+
const current = inspectDevsyAgent(inspectOptions);
|
|
4371
|
+
if (current.state === "ready" && current.binaryPath && current.asset) {
|
|
4372
|
+
return {
|
|
4373
|
+
binaryPath: current.binaryPath,
|
|
4374
|
+
source: current.source,
|
|
4375
|
+
asset: current.asset,
|
|
4376
|
+
changed: false,
|
|
4377
|
+
transport: "existing"
|
|
4378
|
+
};
|
|
4379
|
+
}
|
|
4380
|
+
if (current.source === "explicit" || current.state === "stale" || !current.binaryPath || !current.asset) {
|
|
4381
|
+
throw new DevsyAgentReadinessError(current);
|
|
4382
|
+
}
|
|
4383
|
+
const transport = await downloadAndPublish(
|
|
4384
|
+
current.asset,
|
|
4385
|
+
current.binaryPath,
|
|
4386
|
+
options.fetcher ?? fetch,
|
|
4387
|
+
options.githubCliDownloader ?? downloadWithGitHubCli
|
|
4388
|
+
);
|
|
4389
|
+
const published = inspectDevsyAgent(inspectOptions);
|
|
4390
|
+
if (published.state !== "ready" || !published.binaryPath || !published.asset) {
|
|
4391
|
+
throw new DevsyAgentReadinessError(published);
|
|
4392
|
+
}
|
|
4393
|
+
return {
|
|
4394
|
+
binaryPath: published.binaryPath,
|
|
4395
|
+
source: published.source,
|
|
4396
|
+
asset: published.asset,
|
|
4397
|
+
changed: true,
|
|
4398
|
+
transport
|
|
4399
|
+
};
|
|
4400
|
+
});
|
|
4401
|
+
}
|
|
4402
|
+
function requireReadyDevsyAgent(options = {}) {
|
|
4403
|
+
const inspection = inspectDevsyAgent(options);
|
|
4404
|
+
if (inspection.state !== "ready" || !inspection.binaryPath || !inspection.asset) {
|
|
4405
|
+
throw new DevsyAgentReadinessError(inspection);
|
|
4406
|
+
}
|
|
4407
|
+
return {
|
|
4408
|
+
binaryPath: inspection.binaryPath,
|
|
4409
|
+
source: inspection.source,
|
|
4410
|
+
asset: inspection.asset,
|
|
4411
|
+
changed: false,
|
|
4412
|
+
transport: "existing"
|
|
4413
|
+
};
|
|
4414
|
+
}
|
|
4415
|
+
var import_node_child_process4, import_node_crypto4, import_node_fs9, import_node_path9, SUPPORTED_DEVSY_VERSION, DEVSY_AGENT_SETUP_COMMAND, DEVSY_AGENT_ASSETS, DevsyAgentReadinessError, DEVSY_AGENT_CACHE_LOCK, DEVSY_AGENT_LOCK_WAIT_MS, GITHUB_CLI_STDERR_TAIL_BYTES;
|
|
4416
|
+
var init_devsy_agent = __esm({
|
|
4417
|
+
"src/core/devsy-agent.ts"() {
|
|
4418
|
+
"use strict";
|
|
4419
|
+
import_node_child_process4 = require("child_process");
|
|
4420
|
+
import_node_crypto4 = require("crypto");
|
|
4421
|
+
import_node_fs9 = __toESM(require("fs"));
|
|
4422
|
+
import_node_path9 = __toESM(require("path"));
|
|
4423
|
+
init_file_lock();
|
|
4424
|
+
init_router();
|
|
4425
|
+
SUPPORTED_DEVSY_VERSION = "1.16.2";
|
|
4426
|
+
DEVSY_AGENT_SETUP_COMMAND = "devrouter setup --yes --workspace-runtime devsy";
|
|
4427
|
+
DEVSY_AGENT_ASSETS = [
|
|
4428
|
+
{
|
|
4429
|
+
githubAssetId: 529830010,
|
|
4430
|
+
name: "devsy-linux-arm64",
|
|
4431
|
+
size: 124518562,
|
|
4432
|
+
sha256: "31060b96486b5398f2aa3ee0875b2555782a2db0954a799d387be38ed4b4990d",
|
|
4433
|
+
url: "https://github.com/devsy-org/devsy/releases/download/v1.16.2/devsy-linux-arm64"
|
|
4434
|
+
},
|
|
4435
|
+
{
|
|
4436
|
+
githubAssetId: 529830011,
|
|
4437
|
+
name: "devsy-linux-amd64",
|
|
4438
|
+
size: 133505186,
|
|
4439
|
+
sha256: "4983c52a3536c5a91d1b5f356a1c3428778ebf3f896d9897f60bce3978abc839",
|
|
4440
|
+
url: "https://github.com/devsy-org/devsy/releases/download/v1.16.2/devsy-linux-amd64"
|
|
4441
|
+
}
|
|
4442
|
+
];
|
|
4443
|
+
DevsyAgentReadinessError = class extends Error {
|
|
4444
|
+
constructor(inspection) {
|
|
4445
|
+
super(`Devsy agent is ${inspection.state}: ${inspection.reason}`);
|
|
4446
|
+
this.inspection = inspection;
|
|
4447
|
+
this.name = "DevsyAgentReadinessError";
|
|
4448
|
+
}
|
|
4449
|
+
};
|
|
4450
|
+
DEVSY_AGENT_CACHE_LOCK = import_node_path9.default.join(DEVROUTER_HOME, "devsy-agent-cache.lock");
|
|
4451
|
+
DEVSY_AGENT_LOCK_WAIT_MS = 18e5;
|
|
4452
|
+
GITHUB_CLI_STDERR_TAIL_BYTES = 4096;
|
|
4453
|
+
}
|
|
4454
|
+
});
|
|
4455
|
+
|
|
3934
4456
|
// src/core/docker.ts
|
|
3935
4457
|
async function getDockerodeConstructor() {
|
|
3936
4458
|
if (!dockerodeConstructorPromise) {
|
|
@@ -3942,7 +4464,7 @@ async function getDockerodeConstructor() {
|
|
|
3942
4464
|
return dockerodeConstructorPromise;
|
|
3943
4465
|
}
|
|
3944
4466
|
function runDockerContextCommand(args) {
|
|
3945
|
-
const result = (0,
|
|
4467
|
+
const result = (0, import_node_child_process5.spawnSync)("docker", ["context", ...args], {
|
|
3946
4468
|
encoding: "utf-8"
|
|
3947
4469
|
});
|
|
3948
4470
|
if (result.status !== 0) {
|
|
@@ -3955,7 +4477,7 @@ function getCurrentDockerContext() {
|
|
|
3955
4477
|
return runDockerContextCommand(["show"]);
|
|
3956
4478
|
}
|
|
3957
4479
|
function getDockerHostFromContext(context) {
|
|
3958
|
-
const result = (0,
|
|
4480
|
+
const result = (0, import_node_child_process5.spawnSync)(
|
|
3959
4481
|
"docker",
|
|
3960
4482
|
["context", "inspect", context, "--format", "{{ .Endpoints.docker.Host }}"],
|
|
3961
4483
|
{ encoding: "utf-8" }
|
|
@@ -4023,11 +4545,11 @@ async function networkExists(name) {
|
|
|
4023
4545
|
return false;
|
|
4024
4546
|
}
|
|
4025
4547
|
}
|
|
4026
|
-
var
|
|
4548
|
+
var import_node_child_process5, dockerodeConstructorPromise;
|
|
4027
4549
|
var init_docker = __esm({
|
|
4028
4550
|
"src/core/docker.ts"() {
|
|
4029
4551
|
"use strict";
|
|
4030
|
-
|
|
4552
|
+
import_node_child_process5 = require("child_process");
|
|
4031
4553
|
dockerodeConstructorPromise = null;
|
|
4032
4554
|
}
|
|
4033
4555
|
});
|
|
@@ -4054,11 +4576,11 @@ function parseDevcontainerConfig(contents, sourcePath) {
|
|
|
4054
4576
|
return parsed;
|
|
4055
4577
|
}
|
|
4056
4578
|
function readDevcontainerConfig(repoPath) {
|
|
4057
|
-
const sourcePath =
|
|
4058
|
-
if (!
|
|
4579
|
+
const sourcePath = import_node_path10.default.join(repoPath, ".devcontainer", "devcontainer.json");
|
|
4580
|
+
if (!import_node_fs10.default.existsSync(sourcePath) || !import_node_fs10.default.lstatSync(sourcePath).isFile()) {
|
|
4059
4581
|
throw new Error(`Managed Dev Container source config does not exist: ${sourcePath}`);
|
|
4060
4582
|
}
|
|
4061
|
-
const sourceContents =
|
|
4583
|
+
const sourceContents = import_node_fs10.default.readFileSync(sourcePath, "utf-8");
|
|
4062
4584
|
return {
|
|
4063
4585
|
sourcePath,
|
|
4064
4586
|
sourceContents,
|
|
@@ -4074,12 +4596,12 @@ function assertManagedDevcontainerLifecycle(repoPath) {
|
|
|
4074
4596
|
`Managed Dev Container source '${sourcePath}' defines postCreateCommand, but waitFor ${actual}. Set waitFor to 'postCreateCommand' or 'postStartCommand' before retrying devrouter ensure.`
|
|
4075
4597
|
);
|
|
4076
4598
|
}
|
|
4077
|
-
var
|
|
4599
|
+
var import_node_fs10, import_node_path10, import_jsonc_parser;
|
|
4078
4600
|
var init_devcontainer_config = __esm({
|
|
4079
4601
|
"src/core/devcontainer-config.ts"() {
|
|
4080
4602
|
"use strict";
|
|
4081
|
-
|
|
4082
|
-
|
|
4603
|
+
import_node_fs10 = __toESM(require("fs"));
|
|
4604
|
+
import_node_path10 = __toESM(require("path"));
|
|
4083
4605
|
import_jsonc_parser = require("jsonc-parser");
|
|
4084
4606
|
}
|
|
4085
4607
|
});
|
|
@@ -4089,7 +4611,7 @@ function renderDeliveryScript(targetPath) {
|
|
|
4089
4611
|
if (!/^\/tmp\/devrouter\/bin\/[a-zA-Z0-9._-]+$/.test(targetPath)) {
|
|
4090
4612
|
throw new Error(`Unsafe managed runtime delivery path: ${targetPath}`);
|
|
4091
4613
|
}
|
|
4092
|
-
const temporaryPrefix =
|
|
4614
|
+
const temporaryPrefix = import_node_path11.default.posix.basename(targetPath);
|
|
4093
4615
|
return `set -eu
|
|
4094
4616
|
umask 077
|
|
4095
4617
|
runtime_root=/tmp/devrouter
|
|
@@ -4113,7 +4635,7 @@ function commandFailure(result) {
|
|
|
4113
4635
|
return [result.error?.message, result.stderr, result.stdout].filter(Boolean).map((value) => String(value).trim()).filter(Boolean).join("\n");
|
|
4114
4636
|
}
|
|
4115
4637
|
function deliverRuntimeFile(containerId, targetPath, contents, label) {
|
|
4116
|
-
const result = (0,
|
|
4638
|
+
const result = (0, import_node_child_process6.spawnSync)(
|
|
4117
4639
|
"docker",
|
|
4118
4640
|
["exec", "-i", containerId, "sh", "-c", renderDeliveryScript(targetPath)],
|
|
4119
4641
|
{ input: contents, encoding: "utf-8" }
|
|
@@ -4124,32 +4646,32 @@ function deliverRuntimeFile(containerId, targetPath, contents, label) {
|
|
|
4124
4646
|
}
|
|
4125
4647
|
}
|
|
4126
4648
|
function readRegularFileBytes(filePath) {
|
|
4127
|
-
if (!
|
|
4128
|
-
return
|
|
4649
|
+
if (!import_node_fs11.default.existsSync(filePath) || !import_node_fs11.default.lstatSync(filePath).isFile()) return void 0;
|
|
4650
|
+
return import_node_fs11.default.readFileSync(filePath);
|
|
4129
4651
|
}
|
|
4130
4652
|
function readRegularFile(filePath) {
|
|
4131
4653
|
return readRegularFileBytes(filePath)?.toString("utf-8");
|
|
4132
4654
|
}
|
|
4133
4655
|
function adapterFingerprint(adapter) {
|
|
4134
|
-
return (0,
|
|
4656
|
+
return (0, import_node_crypto5.createHash)("sha256").update(adapter).digest("hex");
|
|
4135
4657
|
}
|
|
4136
4658
|
function resolveProcessHelperPath() {
|
|
4137
4659
|
const candidates = [
|
|
4138
|
-
|
|
4139
|
-
|
|
4660
|
+
import_node_path11.default.resolve(__dirname, "..", "bin", "devrouter-process"),
|
|
4661
|
+
import_node_path11.default.resolve(__dirname, "..", "..", "bin", "devrouter-process")
|
|
4140
4662
|
];
|
|
4141
|
-
const helperPath = candidates.find((candidate) =>
|
|
4663
|
+
const helperPath = candidates.find((candidate) => import_node_fs11.default.existsSync(candidate));
|
|
4142
4664
|
if (!helperPath) {
|
|
4143
4665
|
throw new Error("Could not locate the packaged devrouter-process helper.");
|
|
4144
4666
|
}
|
|
4145
4667
|
return helperPath;
|
|
4146
4668
|
}
|
|
4147
4669
|
function resolveManagedPostStartPlan(repoPath) {
|
|
4148
|
-
const adapterPath =
|
|
4670
|
+
const adapterPath = import_node_path11.default.join(repoPath, MANAGED_ADAPTER_PATH);
|
|
4149
4671
|
const adapterBytes = readRegularFileBytes(adapterPath);
|
|
4150
4672
|
const adapterText = adapterBytes?.toString("utf-8");
|
|
4151
|
-
const dockerfilePath =
|
|
4152
|
-
const devcontainerPath =
|
|
4673
|
+
const dockerfilePath = import_node_path11.default.join(repoPath, ".devcontainer", "Dockerfile");
|
|
4674
|
+
const devcontainerPath = import_node_path11.default.join(repoPath, ".devcontainer", "devcontainer.json");
|
|
4153
4675
|
const dockerfile = readRegularFile(dockerfilePath) ?? "";
|
|
4154
4676
|
const devcontainer = readRegularFile(devcontainerPath) ?? "";
|
|
4155
4677
|
const devrouterPattern = [adapterText, dockerfile, devcontainer].filter((value) => value !== void 0).some(
|
|
@@ -4190,7 +4712,7 @@ function resolveManagedPostStartPlan(repoPath) {
|
|
|
4190
4712
|
}
|
|
4191
4713
|
function runManagedPostStart(options) {
|
|
4192
4714
|
if (options.plan.kind !== "runtime") return;
|
|
4193
|
-
const helper =
|
|
4715
|
+
const helper = import_node_fs11.default.readFileSync(resolveProcessHelperPath());
|
|
4194
4716
|
deliverRuntimeFile(
|
|
4195
4717
|
options.container.id,
|
|
4196
4718
|
RUNTIME_HELPER_PATH,
|
|
@@ -4204,7 +4726,7 @@ function runManagedPostStart(options) {
|
|
|
4204
4726
|
options.plan.adapterContents,
|
|
4205
4727
|
"the managed post-start adapter"
|
|
4206
4728
|
);
|
|
4207
|
-
const started = (0,
|
|
4729
|
+
const started = (0, import_node_child_process6.spawnSync)(
|
|
4208
4730
|
"docker",
|
|
4209
4731
|
[
|
|
4210
4732
|
"exec",
|
|
@@ -4242,7 +4764,7 @@ function assertSafeProcessName(name) {
|
|
|
4242
4764
|
}
|
|
4243
4765
|
function runManagedProcessAction(options) {
|
|
4244
4766
|
assertSafeProcessName(options.name);
|
|
4245
|
-
const result = (0,
|
|
4767
|
+
const result = (0, import_node_child_process6.spawnSync)(
|
|
4246
4768
|
"docker",
|
|
4247
4769
|
[
|
|
4248
4770
|
"exec",
|
|
@@ -4278,14 +4800,14 @@ function runManagedProcessAction(options) {
|
|
|
4278
4800
|
}
|
|
4279
4801
|
return "drifted";
|
|
4280
4802
|
}
|
|
4281
|
-
var
|
|
4803
|
+
var import_node_child_process6, import_node_crypto5, import_node_fs11, import_node_path11, MANAGED_MARKER, MANAGED_ADAPTER_PATH, RUNTIME_HELPER_PATH, ADAPTER_WRAPPER;
|
|
4282
4804
|
var init_managed_post_start = __esm({
|
|
4283
4805
|
"src/core/managed-post-start.ts"() {
|
|
4284
4806
|
"use strict";
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4807
|
+
import_node_child_process6 = require("child_process");
|
|
4808
|
+
import_node_crypto5 = require("crypto");
|
|
4809
|
+
import_node_fs11 = __toESM(require("fs"));
|
|
4810
|
+
import_node_path11 = __toESM(require("path"));
|
|
4289
4811
|
init_devcontainer_config();
|
|
4290
4812
|
MANAGED_MARKER = "devrouter:managed devcontainer";
|
|
4291
4813
|
MANAGED_ADAPTER_PATH = ".devcontainer/post-start.sh";
|
|
@@ -4352,7 +4874,7 @@ function isLoopbackHost(host) {
|
|
|
4352
4874
|
return host === "127.0.0.1" || host === "localhost" || host === "host.docker.internal";
|
|
4353
4875
|
}
|
|
4354
4876
|
function inspectCompose(composeFile, _workspace) {
|
|
4355
|
-
if (!
|
|
4877
|
+
if (!import_node_fs12.default.existsSync(composeFile)) {
|
|
4356
4878
|
return {
|
|
4357
4879
|
aliases: [],
|
|
4358
4880
|
publishedPorts: [],
|
|
@@ -4361,7 +4883,7 @@ function inspectCompose(composeFile, _workspace) {
|
|
|
4361
4883
|
};
|
|
4362
4884
|
}
|
|
4363
4885
|
try {
|
|
4364
|
-
const raw =
|
|
4886
|
+
const raw = import_node_fs12.default.readFileSync(composeFile, "utf-8");
|
|
4365
4887
|
const parsed = import_yaml3.default.parse(raw);
|
|
4366
4888
|
const root = asRecord(parsed);
|
|
4367
4889
|
const services = asRecord(root?.services);
|
|
@@ -4418,11 +4940,11 @@ function routedProxyApps(config) {
|
|
|
4418
4940
|
);
|
|
4419
4941
|
}
|
|
4420
4942
|
function buildDevcontainerChecks(repoPath, config, workspace) {
|
|
4421
|
-
const devcontainerDir =
|
|
4422
|
-
if (!
|
|
4943
|
+
const devcontainerDir = import_node_path12.default.join(repoPath, ".devcontainer");
|
|
4944
|
+
if (!import_node_fs12.default.existsSync(devcontainerDir)) {
|
|
4423
4945
|
return [];
|
|
4424
4946
|
}
|
|
4425
|
-
const compose = inspectCompose(
|
|
4947
|
+
const compose = inspectCompose(import_node_path12.default.join(devcontainerDir, "docker-compose.yml"), workspace);
|
|
4426
4948
|
const checks = [];
|
|
4427
4949
|
if (compose.parseError) {
|
|
4428
4950
|
checks.push({
|
|
@@ -4449,9 +4971,9 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
|
|
|
4449
4971
|
details: compose.publishedPorts.length > 0 ? compose.publishedPorts.join(", ") : void 0,
|
|
4450
4972
|
suggestion: compose.parseError || compose.publishedPorts.length > 0 ? "Remove published ports and route services through devnet aliases with devrouter proxy apps." : void 0
|
|
4451
4973
|
});
|
|
4452
|
-
const dockerfilePath =
|
|
4453
|
-
const dockerfileExists =
|
|
4454
|
-
const dockerfile = dockerfileExists ?
|
|
4974
|
+
const dockerfilePath = import_node_path12.default.join(devcontainerDir, "Dockerfile");
|
|
4975
|
+
const dockerfileExists = import_node_fs12.default.existsSync(dockerfilePath);
|
|
4976
|
+
const dockerfile = dockerfileExists ? import_node_fs12.default.readFileSync(dockerfilePath, "utf-8") : "";
|
|
4455
4977
|
const devrouterArtifacts = ["@devrouter/cli", "devrouter-process"].filter(
|
|
4456
4978
|
(artifact) => dockerfile.includes(artifact)
|
|
4457
4979
|
);
|
|
@@ -4506,12 +5028,12 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
|
|
|
4506
5028
|
});
|
|
4507
5029
|
return checks;
|
|
4508
5030
|
}
|
|
4509
|
-
var
|
|
5031
|
+
var import_node_fs12, import_node_path12, import_yaml3;
|
|
4510
5032
|
var init_devcontainer_diagnostics = __esm({
|
|
4511
5033
|
"src/core/devcontainer-diagnostics.ts"() {
|
|
4512
5034
|
"use strict";
|
|
4513
|
-
|
|
4514
|
-
|
|
5035
|
+
import_node_fs12 = __toESM(require("fs"));
|
|
5036
|
+
import_node_path12 = __toESM(require("path"));
|
|
4515
5037
|
import_yaml3 = __toESM(require("yaml"));
|
|
4516
5038
|
init_managed_post_start();
|
|
4517
5039
|
}
|
|
@@ -4519,18 +5041,18 @@ var init_devcontainer_diagnostics = __esm({
|
|
|
4519
5041
|
|
|
4520
5042
|
// src/core/paths.ts
|
|
4521
5043
|
function assertPathWithinRepo(filePath, repoRoot, label) {
|
|
4522
|
-
const resolvedRoot =
|
|
4523
|
-
const resolved =
|
|
4524
|
-
if (resolved !== resolvedRoot && !resolved.startsWith(resolvedRoot +
|
|
5044
|
+
const resolvedRoot = import_node_path13.default.resolve(repoRoot);
|
|
5045
|
+
const resolved = import_node_path13.default.resolve(repoRoot, filePath);
|
|
5046
|
+
if (resolved !== resolvedRoot && !resolved.startsWith(resolvedRoot + import_node_path13.default.sep)) {
|
|
4525
5047
|
throw new Error(`${label} path '${filePath}' escapes the repository root.`);
|
|
4526
5048
|
}
|
|
4527
5049
|
return resolved;
|
|
4528
5050
|
}
|
|
4529
|
-
var
|
|
5051
|
+
var import_node_path13;
|
|
4530
5052
|
var init_paths = __esm({
|
|
4531
5053
|
"src/core/paths.ts"() {
|
|
4532
5054
|
"use strict";
|
|
4533
|
-
|
|
5055
|
+
import_node_path13 = __toESM(require("path"));
|
|
4534
5056
|
}
|
|
4535
5057
|
});
|
|
4536
5058
|
|
|
@@ -4615,11 +5137,11 @@ function reconcileRouteRunConflict(repoPath, app) {
|
|
|
4615
5137
|
}
|
|
4616
5138
|
}
|
|
4617
5139
|
}
|
|
4618
|
-
var
|
|
5140
|
+
var import_node_fs13;
|
|
4619
5141
|
var init_route_state = __esm({
|
|
4620
5142
|
"src/core/route-state.ts"() {
|
|
4621
5143
|
"use strict";
|
|
4622
|
-
|
|
5144
|
+
import_node_fs13 = __toESM(require("fs"));
|
|
4623
5145
|
init_host_routes();
|
|
4624
5146
|
init_workspace();
|
|
4625
5147
|
}
|
|
@@ -4785,10 +5307,10 @@ var init_routes = __esm({
|
|
|
4785
5307
|
|
|
4786
5308
|
// src/core/managed-runtime-state.ts
|
|
4787
5309
|
function stateKey(repoPath, workspace) {
|
|
4788
|
-
return (0,
|
|
5310
|
+
return (0, import_node_crypto6.createHash)("sha256").update(`${repoPath}\0${workspace ?? ""}`, "utf-8").digest("hex");
|
|
4789
5311
|
}
|
|
4790
5312
|
function managedRuntimeStatePath(repoPath, workspace) {
|
|
4791
|
-
return
|
|
5313
|
+
return import_node_path14.default.join(DEVROUTER_HOME, "managed-runtime", `${stateKey(repoPath, workspace)}.json`);
|
|
4792
5314
|
}
|
|
4793
5315
|
function isStringArray(value) {
|
|
4794
5316
|
return Array.isArray(value) && value.every((item) => typeof item === "string") && new Set(value).size === value.length;
|
|
@@ -4851,10 +5373,10 @@ function validateState(value, repoPath, workspace) {
|
|
|
4851
5373
|
}
|
|
4852
5374
|
function readManagedRuntimeState(repoPath, workspace) {
|
|
4853
5375
|
const statePath = managedRuntimeStatePath(repoPath, workspace);
|
|
4854
|
-
if (!
|
|
5376
|
+
if (!import_node_fs14.default.existsSync(statePath)) return void 0;
|
|
4855
5377
|
let parsed;
|
|
4856
5378
|
try {
|
|
4857
|
-
parsed = JSON.parse(
|
|
5379
|
+
parsed = JSON.parse(import_node_fs14.default.readFileSync(statePath, "utf-8"));
|
|
4858
5380
|
} catch (error) {
|
|
4859
5381
|
throw new Error(`Could not parse managed runtime state: ${String(error)}`);
|
|
4860
5382
|
}
|
|
@@ -4876,13 +5398,13 @@ function markManagedRuntimeDegraded(state, transitionPhase) {
|
|
|
4876
5398
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4877
5399
|
});
|
|
4878
5400
|
}
|
|
4879
|
-
var
|
|
5401
|
+
var import_node_crypto6, import_node_fs14, import_node_path14;
|
|
4880
5402
|
var init_managed_runtime_state = __esm({
|
|
4881
5403
|
"src/core/managed-runtime-state.ts"() {
|
|
4882
5404
|
"use strict";
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
5405
|
+
import_node_crypto6 = require("crypto");
|
|
5406
|
+
import_node_fs14 = __toESM(require("fs"));
|
|
5407
|
+
import_node_path14 = __toESM(require("path"));
|
|
4886
5408
|
init_atomic_file();
|
|
4887
5409
|
init_router();
|
|
4888
5410
|
}
|
|
@@ -4933,14 +5455,14 @@ function resolveComposeFiles(source, repoPath, linked) {
|
|
|
4933
5455
|
"managedRuntime requires .devcontainer/devcontainer.json to define dockerComposeFile."
|
|
4934
5456
|
);
|
|
4935
5457
|
}
|
|
4936
|
-
const directory =
|
|
5458
|
+
const directory = import_node_path15.default.join(repoPath, ".devcontainer");
|
|
4937
5459
|
const files = references.map((reference) => {
|
|
4938
5460
|
const resolved2 = assertPathWithinRepo(
|
|
4939
5461
|
resolveComposeReference(reference, linked),
|
|
4940
5462
|
directory,
|
|
4941
5463
|
"dockerComposeFile"
|
|
4942
5464
|
);
|
|
4943
|
-
if (!
|
|
5465
|
+
if (!import_node_fs15.default.existsSync(resolved2) || !import_node_fs15.default.lstatSync(resolved2).isFile()) {
|
|
4944
5466
|
throw new Error(`Managed Dev Container compose file does not exist: ${resolved2}`);
|
|
4945
5467
|
}
|
|
4946
5468
|
return resolved2;
|
|
@@ -4948,7 +5470,7 @@ function resolveComposeFiles(source, repoPath, linked) {
|
|
|
4948
5470
|
for (const file of files) {
|
|
4949
5471
|
let parsed;
|
|
4950
5472
|
try {
|
|
4951
|
-
parsed = import_yaml4.default.parse(
|
|
5473
|
+
parsed = import_yaml4.default.parse(import_node_fs15.default.readFileSync(file, "utf-8"));
|
|
4952
5474
|
} catch (error) {
|
|
4953
5475
|
throw new Error(
|
|
4954
5476
|
`Could not parse managed Dev Container compose file '${file}': ${String(error)}`
|
|
@@ -4959,7 +5481,7 @@ function resolveComposeFiles(source, repoPath, linked) {
|
|
|
4959
5481
|
}
|
|
4960
5482
|
}
|
|
4961
5483
|
const fileArgs = files.flatMap((file) => ["-f", file]);
|
|
4962
|
-
const resolved = (0,
|
|
5484
|
+
const resolved = (0, import_node_child_process7.spawnSync)(
|
|
4963
5485
|
"docker",
|
|
4964
5486
|
[
|
|
4965
5487
|
"compose",
|
|
@@ -4985,7 +5507,7 @@ function resolveComposeFiles(source, repoPath, linked) {
|
|
|
4985
5507
|
return { directory, files, services: services.sort() };
|
|
4986
5508
|
}
|
|
4987
5509
|
function assertIgnoredGeneratedPath(repoPath, generatedPath) {
|
|
4988
|
-
const ignored = (0,
|
|
5510
|
+
const ignored = (0, import_node_child_process7.spawnSync)(
|
|
4989
5511
|
"git",
|
|
4990
5512
|
["-C", repoPath, "check-ignore", "--quiet", "--no-index", "--", generatedPath],
|
|
4991
5513
|
{ encoding: "utf-8" }
|
|
@@ -4997,12 +5519,12 @@ function assertIgnoredGeneratedPath(repoPath, generatedPath) {
|
|
|
4997
5519
|
}
|
|
4998
5520
|
}
|
|
4999
5521
|
function assertGeneratedPathOwnership(generatedPath) {
|
|
5000
|
-
if (!
|
|
5001
|
-
const stat =
|
|
5522
|
+
if (!import_node_fs15.default.existsSync(generatedPath)) return;
|
|
5523
|
+
const stat = import_node_fs15.default.lstatSync(generatedPath);
|
|
5002
5524
|
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
5003
5525
|
throw new Error(`Managed Dev Container path '${generatedPath}' is not a regular file.`);
|
|
5004
5526
|
}
|
|
5005
|
-
const firstLine2 =
|
|
5527
|
+
const firstLine2 = import_node_fs15.default.readFileSync(generatedPath, "utf-8").split(/\r?\n/, 1)[0];
|
|
5006
5528
|
if (firstLine2 !== MANAGED_DEVCONTAINER_MARKER) {
|
|
5007
5529
|
throw new Error(
|
|
5008
5530
|
`Managed Dev Container path '${generatedPath}' exists without the devrouter ownership marker.`
|
|
@@ -5013,7 +5535,7 @@ function isWildcard(values) {
|
|
|
5013
5535
|
return values?.length === 1 && values[0] === "*";
|
|
5014
5536
|
}
|
|
5015
5537
|
function sha256(contents) {
|
|
5016
|
-
return (0,
|
|
5538
|
+
return (0, import_node_crypto7.createHash)("sha256").update(contents, "utf-8").digest("hex");
|
|
5017
5539
|
}
|
|
5018
5540
|
function safeComposeProject(value) {
|
|
5019
5541
|
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(value)) {
|
|
@@ -5078,7 +5600,7 @@ function inspectManagedDevcontainerConfig(options) {
|
|
|
5078
5600
|
const contents = `${MANAGED_DEVCONTAINER_MARKER}
|
|
5079
5601
|
${JSON.stringify(effective, null, 2)}
|
|
5080
5602
|
`;
|
|
5081
|
-
const generatedPath =
|
|
5603
|
+
const generatedPath = import_node_path15.default.join(options.repoPath, MANAGED_DEVCONTAINER_PATH);
|
|
5082
5604
|
assertIgnoredGeneratedPath(options.repoPath, generatedPath);
|
|
5083
5605
|
assertGeneratedPathOwnership(generatedPath);
|
|
5084
5606
|
return {
|
|
@@ -5105,13 +5627,13 @@ function writeManagedDevcontainerConfig(plan) {
|
|
|
5105
5627
|
function inspectManagedDevcontainerGeneratedConfig(plan) {
|
|
5106
5628
|
let stat;
|
|
5107
5629
|
try {
|
|
5108
|
-
stat =
|
|
5630
|
+
stat = import_node_fs15.default.lstatSync(plan.generatedPath);
|
|
5109
5631
|
} catch (error) {
|
|
5110
5632
|
if (error.code === "ENOENT") return { status: "missing" };
|
|
5111
5633
|
throw error;
|
|
5112
5634
|
}
|
|
5113
5635
|
if (!stat.isFile() || stat.isSymbolicLink()) return { status: "foreign" };
|
|
5114
|
-
const contents =
|
|
5636
|
+
const contents = import_node_fs15.default.readFileSync(plan.generatedPath, "utf-8");
|
|
5115
5637
|
if (!contents.startsWith(`${MANAGED_DEVCONTAINER_MARKER}
|
|
5116
5638
|
`)) {
|
|
5117
5639
|
return { status: "foreign" };
|
|
@@ -5123,17 +5645,17 @@ function inspectManagedDevcontainerGeneratedConfig(plan) {
|
|
|
5123
5645
|
};
|
|
5124
5646
|
}
|
|
5125
5647
|
function removeManagedDevcontainerConfig(plan) {
|
|
5126
|
-
if (!
|
|
5127
|
-
const stat =
|
|
5648
|
+
if (!import_node_fs15.default.existsSync(plan.generatedPath)) return;
|
|
5649
|
+
const stat = import_node_fs15.default.lstatSync(plan.generatedPath);
|
|
5128
5650
|
if (!stat.isFile()) {
|
|
5129
5651
|
throw new Error(`Managed Dev Container path '${plan.generatedPath}' is not a regular file.`);
|
|
5130
5652
|
}
|
|
5131
|
-
const contents =
|
|
5653
|
+
const contents = import_node_fs15.default.readFileSync(plan.generatedPath, "utf-8");
|
|
5132
5654
|
if (!contents.startsWith(`${MANAGED_DEVCONTAINER_MARKER}
|
|
5133
5655
|
`)) {
|
|
5134
5656
|
throw new Error(`Managed Dev Container path '${plan.generatedPath}' is not devrouter-owned.`);
|
|
5135
5657
|
}
|
|
5136
|
-
|
|
5658
|
+
import_node_fs15.default.unlinkSync(plan.generatedPath);
|
|
5137
5659
|
}
|
|
5138
5660
|
function assertSafeContainerId(containerId) {
|
|
5139
5661
|
if (!/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(containerId)) {
|
|
@@ -5160,7 +5682,7 @@ function startExactManagedServices(options) {
|
|
|
5160
5682
|
delete env.DEVROUTER_GIT_COMMON_DIR;
|
|
5161
5683
|
delete env.DEVCONTAINER_COMPOSE_OVERLAY;
|
|
5162
5684
|
}
|
|
5163
|
-
const result = (0,
|
|
5685
|
+
const result = (0, import_node_child_process7.spawnSync)(
|
|
5164
5686
|
"docker",
|
|
5165
5687
|
[
|
|
5166
5688
|
"compose",
|
|
@@ -5190,7 +5712,7 @@ function startExactManagedServices(options) {
|
|
|
5190
5712
|
}
|
|
5191
5713
|
function stopExactManagedService(containerId, service) {
|
|
5192
5714
|
assertSafeContainerId(containerId);
|
|
5193
|
-
const result = (0,
|
|
5715
|
+
const result = (0, import_node_child_process7.spawnSync)("docker", ["stop", containerId], {
|
|
5194
5716
|
encoding: "utf-8",
|
|
5195
5717
|
stdio: "inherit"
|
|
5196
5718
|
});
|
|
@@ -5198,14 +5720,14 @@ function stopExactManagedService(containerId, service) {
|
|
|
5198
5720
|
throw new Error(`Could not stop exact managed service '${service}' (${containerId}).`);
|
|
5199
5721
|
}
|
|
5200
5722
|
}
|
|
5201
|
-
var
|
|
5723
|
+
var import_node_child_process7, import_node_crypto7, import_node_fs15, import_node_path15, import_yaml4, MANAGED_DEVCONTAINER_PATH, MANAGED_DEVCONTAINER_MARKER;
|
|
5202
5724
|
var init_devcontainer_profile = __esm({
|
|
5203
5725
|
"src/core/devcontainer-profile.ts"() {
|
|
5204
5726
|
"use strict";
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5727
|
+
import_node_child_process7 = require("child_process");
|
|
5728
|
+
import_node_crypto7 = require("crypto");
|
|
5729
|
+
import_node_fs15 = __toESM(require("fs"));
|
|
5730
|
+
import_node_path15 = __toESM(require("path"));
|
|
5209
5731
|
import_yaml4 = __toESM(require("yaml"));
|
|
5210
5732
|
init_atomic_file();
|
|
5211
5733
|
init_devcontainer_config();
|
|
@@ -5219,9 +5741,17 @@ var init_devcontainer_profile = __esm({
|
|
|
5219
5741
|
function inspectWorkspaceContainers(options) {
|
|
5220
5742
|
let ids = options?.ids;
|
|
5221
5743
|
if (!ids) {
|
|
5222
|
-
const listed = (0,
|
|
5223
|
-
|
|
5224
|
-
|
|
5744
|
+
const listed = (0, import_node_child_process8.spawnSync)(
|
|
5745
|
+
"docker",
|
|
5746
|
+
[
|
|
5747
|
+
"ps",
|
|
5748
|
+
"-a",
|
|
5749
|
+
...options?.composeProject ? ["--filter", `label=com.docker.compose.project=${options.composeProject}`] : [],
|
|
5750
|
+
"--format",
|
|
5751
|
+
"{{.ID}}"
|
|
5752
|
+
],
|
|
5753
|
+
{ encoding: "utf-8" }
|
|
5754
|
+
);
|
|
5225
5755
|
if (listed.status !== 0) {
|
|
5226
5756
|
throw new Error(
|
|
5227
5757
|
`docker ps failed: ${(listed.stderr || listed.stdout || listed.error?.message || "unknown error").trim()}`
|
|
@@ -5231,7 +5761,7 @@ function inspectWorkspaceContainers(options) {
|
|
|
5231
5761
|
}
|
|
5232
5762
|
if (ids.length === 0) return [];
|
|
5233
5763
|
const template = options?.withSize ? SIZE_INSPECT_TEMPLATE : SAFE_INSPECT_TEMPLATE;
|
|
5234
|
-
const inspected = (0,
|
|
5764
|
+
const inspected = (0, import_node_child_process8.spawnSync)(
|
|
5235
5765
|
"docker",
|
|
5236
5766
|
["inspect", ...options?.withSize ? ["--size"] : [], "--format", template, ...ids],
|
|
5237
5767
|
{ encoding: "utf-8" }
|
|
@@ -5246,7 +5776,7 @@ function inspectWorkspaceContainers(options) {
|
|
|
5246
5776
|
function workspaceAppContainers(containers, repoPath) {
|
|
5247
5777
|
return containers.filter((container) => {
|
|
5248
5778
|
const workingDir = container.labels["com.docker.compose.project.working_dir"];
|
|
5249
|
-
return Boolean(workingDir && sameWorkspacePath(workingDir,
|
|
5779
|
+
return Boolean(workingDir && sameWorkspacePath(workingDir, import_node_path16.default.join(repoPath, ".devcontainer"))) && container.mounts.some(
|
|
5250
5780
|
(mount) => mount.Type === "bind" && sameWorkspacePath(mount.Source, repoPath)
|
|
5251
5781
|
);
|
|
5252
5782
|
});
|
|
@@ -5254,7 +5784,7 @@ function workspaceAppContainers(containers, repoPath) {
|
|
|
5254
5784
|
function hasExactComposeIdentity(container, options) {
|
|
5255
5785
|
if (options.composeProject !== void 0 && container.labels["com.docker.compose.project"] !== options.composeProject || container.labels["com.docker.compose.service"] !== options.service || !sameWorkspacePath(
|
|
5256
5786
|
container.labels["com.docker.compose.project.working_dir"] ?? "",
|
|
5257
|
-
|
|
5787
|
+
import_node_path16.default.join(options.repoPath, ".devcontainer")
|
|
5258
5788
|
)) {
|
|
5259
5789
|
return false;
|
|
5260
5790
|
}
|
|
@@ -5291,12 +5821,12 @@ function resolveRunningWorkspaceContainer(repoPath) {
|
|
|
5291
5821
|
}
|
|
5292
5822
|
return { id: container.id, workspacePath: repoMount.Destination };
|
|
5293
5823
|
}
|
|
5294
|
-
var
|
|
5824
|
+
var import_node_child_process8, import_node_path16, SAFE_INSPECT_TEMPLATE, SIZE_INSPECT_TEMPLATE;
|
|
5295
5825
|
var init_devpod_environment = __esm({
|
|
5296
5826
|
"src/core/devpod-environment.ts"() {
|
|
5297
5827
|
"use strict";
|
|
5298
|
-
|
|
5299
|
-
|
|
5828
|
+
import_node_child_process8 = require("child_process");
|
|
5829
|
+
import_node_path16 = __toESM(require("path"));
|
|
5300
5830
|
init_workspace();
|
|
5301
5831
|
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
5832
|
SIZE_INSPECT_TEMPLATE = SAFE_INSPECT_TEMPLATE.replace(
|
|
@@ -5750,7 +6280,7 @@ function toRepoStatus(repoPath) {
|
|
|
5750
6280
|
const resolvedRepoPath = resolveRepoPath(repoPath);
|
|
5751
6281
|
const configPath = getRepoConfigPath(resolvedRepoPath);
|
|
5752
6282
|
const explicitRepo = typeof repoPath === "string" && repoPath.trim().length > 0;
|
|
5753
|
-
const configExists =
|
|
6283
|
+
const configExists = import_node_fs16.default.existsSync(configPath);
|
|
5754
6284
|
if (!explicitRepo && !configExists) {
|
|
5755
6285
|
return void 0;
|
|
5756
6286
|
}
|
|
@@ -5898,11 +6428,11 @@ async function collectRouterStatus(repoPath) {
|
|
|
5898
6428
|
}
|
|
5899
6429
|
};
|
|
5900
6430
|
}
|
|
5901
|
-
var
|
|
6431
|
+
var import_node_fs16;
|
|
5902
6432
|
var init_status = __esm({
|
|
5903
6433
|
"src/core/status.ts"() {
|
|
5904
6434
|
"use strict";
|
|
5905
|
-
|
|
6435
|
+
import_node_fs16 = __toESM(require("fs"));
|
|
5906
6436
|
init_docker();
|
|
5907
6437
|
init_managed_runtime_state();
|
|
5908
6438
|
init_managed_runtime_status();
|
|
@@ -5913,7 +6443,7 @@ var init_status = __esm({
|
|
|
5913
6443
|
|
|
5914
6444
|
// src/core/tls.ts
|
|
5915
6445
|
function runOrThrow(command, args) {
|
|
5916
|
-
const result = (0,
|
|
6446
|
+
const result = (0, import_node_child_process9.spawnSync)(command, args, {
|
|
5917
6447
|
encoding: "utf-8"
|
|
5918
6448
|
});
|
|
5919
6449
|
if (result.status !== 0) {
|
|
@@ -5923,7 +6453,7 @@ function runOrThrow(command, args) {
|
|
|
5923
6453
|
return result.stdout.trim();
|
|
5924
6454
|
}
|
|
5925
6455
|
function commandExists(command) {
|
|
5926
|
-
const result = (0,
|
|
6456
|
+
const result = (0, import_node_child_process9.spawnSync)("sh", ["-c", `command -v ${command}`], { encoding: "utf-8" });
|
|
5927
6457
|
return result.status === 0;
|
|
5928
6458
|
}
|
|
5929
6459
|
function ensureMkcert() {
|
|
@@ -5941,8 +6471,8 @@ function tlsSetupCommand(repoPath) {
|
|
|
5941
6471
|
}
|
|
5942
6472
|
function getMkcertRootCAPath(options = {}) {
|
|
5943
6473
|
ensureMkcert();
|
|
5944
|
-
const rootCAPath =
|
|
5945
|
-
if (!
|
|
6474
|
+
const rootCAPath = import_node_path17.default.join(runOrThrow("mkcert", ["-CAROOT"]), "rootCA.pem");
|
|
6475
|
+
if (!import_node_fs17.default.existsSync(rootCAPath)) {
|
|
5946
6476
|
throw new Error(
|
|
5947
6477
|
`mkcert root CA was not found at '${rootCAPath}'. Run: ${tlsSetupCommand(options.repoPath)}`
|
|
5948
6478
|
);
|
|
@@ -5975,7 +6505,7 @@ function parseDnsHostsFromSubjectAltName(subjectAltName) {
|
|
|
5975
6505
|
return normalizeUniqueHosts(names);
|
|
5976
6506
|
}
|
|
5977
6507
|
function parseCertificateDnsHosts(pem) {
|
|
5978
|
-
const certificate = new
|
|
6508
|
+
const certificate = new import_node_crypto8.X509Certificate(pem);
|
|
5979
6509
|
const subjectAltName = certificate.subjectAltName ?? "";
|
|
5980
6510
|
if (subjectAltName.length === 0) {
|
|
5981
6511
|
return [];
|
|
@@ -6043,11 +6573,11 @@ function compactTLSCertificateHosts(hosts) {
|
|
|
6043
6573
|
);
|
|
6044
6574
|
}
|
|
6045
6575
|
function readCurrentCertificateHosts(options = {}) {
|
|
6046
|
-
if (!
|
|
6576
|
+
if (!import_node_fs17.default.existsSync(CERT_FILE)) {
|
|
6047
6577
|
return [];
|
|
6048
6578
|
}
|
|
6049
6579
|
try {
|
|
6050
|
-
const pem =
|
|
6580
|
+
const pem = import_node_fs17.default.readFileSync(CERT_FILE, "utf-8");
|
|
6051
6581
|
return parseCertificateDnsHosts(pem);
|
|
6052
6582
|
} catch (error) {
|
|
6053
6583
|
if (options.replaceMalformed) {
|
|
@@ -6175,20 +6705,20 @@ Run: ${tlsSetupCommand(options.repoPath)}`
|
|
|
6175
6705
|
);
|
|
6176
6706
|
}
|
|
6177
6707
|
}
|
|
6178
|
-
var
|
|
6708
|
+
var import_node_child_process9, import_node_crypto8, import_node_fs17, import_node_path17, DEFAULT_TLS_CERT_HOSTS, TLS_CERTIFICATE_LOCK_FILE, TLS_CERTIFICATE_LOCK_WAIT_MS, TLS_CERTIFICATE_WRITE_ATTEMPTS;
|
|
6179
6709
|
var init_tls = __esm({
|
|
6180
6710
|
"src/core/tls.ts"() {
|
|
6181
6711
|
"use strict";
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6712
|
+
import_node_child_process9 = require("child_process");
|
|
6713
|
+
import_node_crypto8 = require("crypto");
|
|
6714
|
+
import_node_fs17 = __toESM(require("fs"));
|
|
6715
|
+
import_node_path17 = __toESM(require("path"));
|
|
6186
6716
|
init_docker();
|
|
6187
6717
|
init_file_lock();
|
|
6188
6718
|
init_host_routes();
|
|
6189
6719
|
init_router();
|
|
6190
6720
|
DEFAULT_TLS_CERT_HOSTS = ["localhost", "*.localhost"];
|
|
6191
|
-
TLS_CERTIFICATE_LOCK_FILE =
|
|
6721
|
+
TLS_CERTIFICATE_LOCK_FILE = import_node_path17.default.join(DEVROUTER_HOME, "tls-certificate.lock");
|
|
6192
6722
|
TLS_CERTIFICATE_LOCK_WAIT_MS = 6e4;
|
|
6193
6723
|
TLS_CERTIFICATE_WRITE_ATTEMPTS = 2;
|
|
6194
6724
|
}
|
|
@@ -6196,7 +6726,7 @@ var init_tls = __esm({
|
|
|
6196
6726
|
|
|
6197
6727
|
// src/core/devpod-registry.ts
|
|
6198
6728
|
function listDevpodWorkspacesRaw() {
|
|
6199
|
-
const result = (0,
|
|
6729
|
+
const result = (0, import_node_child_process10.spawnSync)("devpod", ["list", "--output", "json", "--skip-pro"], {
|
|
6200
6730
|
encoding: "utf-8"
|
|
6201
6731
|
});
|
|
6202
6732
|
if (result.status !== 0) {
|
|
@@ -6231,17 +6761,17 @@ function listDevpodWorkspacesRaw() {
|
|
|
6231
6761
|
return workspace;
|
|
6232
6762
|
});
|
|
6233
6763
|
}
|
|
6234
|
-
var
|
|
6764
|
+
var import_node_child_process10;
|
|
6235
6765
|
var init_devpod_registry = __esm({
|
|
6236
6766
|
"src/core/devpod-registry.ts"() {
|
|
6237
6767
|
"use strict";
|
|
6238
|
-
|
|
6768
|
+
import_node_child_process10 = require("child_process");
|
|
6239
6769
|
}
|
|
6240
6770
|
});
|
|
6241
6771
|
|
|
6242
6772
|
// src/core/devsy-workspaces.ts
|
|
6243
6773
|
function listDevsyWorkspaces() {
|
|
6244
|
-
const result = (0,
|
|
6774
|
+
const result = (0, import_node_child_process11.spawnSync)(
|
|
6245
6775
|
"devsy",
|
|
6246
6776
|
["workspace", "list", "--result-format", "json", "--skip-pro"],
|
|
6247
6777
|
{
|
|
@@ -6307,7 +6837,7 @@ function parseDevsyRuntimeStatus(output2, expectedId) {
|
|
|
6307
6837
|
}
|
|
6308
6838
|
}
|
|
6309
6839
|
function inspectDevsyRuntimeStatus(devsyId) {
|
|
6310
|
-
const result = (0,
|
|
6840
|
+
const result = (0, import_node_child_process11.spawnSync)("devsy", ["workspace", "status", devsyId, "--result-format", "json"], {
|
|
6311
6841
|
encoding: "utf-8",
|
|
6312
6842
|
timeout: 1e4
|
|
6313
6843
|
});
|
|
@@ -6340,11 +6870,11 @@ function selectDevsyWorkspace(workspaces, repoPath) {
|
|
|
6340
6870
|
}
|
|
6341
6871
|
return matches[0];
|
|
6342
6872
|
}
|
|
6343
|
-
var
|
|
6873
|
+
var import_node_child_process11;
|
|
6344
6874
|
var init_devsy_workspaces = __esm({
|
|
6345
6875
|
"src/core/devsy-workspaces.ts"() {
|
|
6346
6876
|
"use strict";
|
|
6347
|
-
|
|
6877
|
+
import_node_child_process11 = require("child_process");
|
|
6348
6878
|
init_workspace();
|
|
6349
6879
|
}
|
|
6350
6880
|
});
|
|
@@ -6361,13 +6891,13 @@ function parseWorkspaceRuntime(value) {
|
|
|
6361
6891
|
}
|
|
6362
6892
|
function isRuntimeInstalled(runtime) {
|
|
6363
6893
|
const probeArgs = runtime === "devsy" ? ["--version"] : ["version"];
|
|
6364
|
-
const probe = (0,
|
|
6894
|
+
const probe = (0, import_node_child_process12.spawnSync)(runtime, probeArgs, { encoding: "utf-8" });
|
|
6365
6895
|
return probe.status === 0 && !probe.error;
|
|
6366
6896
|
}
|
|
6367
6897
|
function readWorkspaceRuntimeConfig() {
|
|
6368
6898
|
let raw;
|
|
6369
6899
|
try {
|
|
6370
|
-
raw = JSON.parse(
|
|
6900
|
+
raw = JSON.parse(import_node_fs18.default.readFileSync(RUNTIME_CONFIG_FILE, "utf-8"));
|
|
6371
6901
|
} catch {
|
|
6372
6902
|
return {};
|
|
6373
6903
|
}
|
|
@@ -6388,7 +6918,7 @@ function readWorkspaceRuntimeConfig() {
|
|
|
6388
6918
|
function inspectWorkspaceRuntimeConfig() {
|
|
6389
6919
|
let rawText;
|
|
6390
6920
|
try {
|
|
6391
|
-
rawText =
|
|
6921
|
+
rawText = import_node_fs18.default.readFileSync(RUNTIME_CONFIG_FILE, "utf-8");
|
|
6392
6922
|
} catch {
|
|
6393
6923
|
return { exists: false, config: {}, problems: [] };
|
|
6394
6924
|
}
|
|
@@ -6439,7 +6969,7 @@ function writeWorkspaceRuntimeConfig(config) {
|
|
|
6439
6969
|
}
|
|
6440
6970
|
next.devsyInactivityTimeout = timeout;
|
|
6441
6971
|
}
|
|
6442
|
-
|
|
6972
|
+
import_node_fs18.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
6443
6973
|
writeFileAtomically(RUNTIME_CONFIG_FILE, `${JSON.stringify(next, null, 2)}
|
|
6444
6974
|
`);
|
|
6445
6975
|
}
|
|
@@ -6547,20 +7077,20 @@ function resetWorkspaceRuntimeCaches() {
|
|
|
6547
7077
|
cachedRuntime = void 0;
|
|
6548
7078
|
cachedSnapshots = void 0;
|
|
6549
7079
|
}
|
|
6550
|
-
var
|
|
7080
|
+
var import_node_child_process12, import_node_fs18, import_node_path18, SUPPORTED_RUNTIMES2, RUNTIME_CONFIG_FILE, INACTIVITY_TIMEOUT_PATTERN, cachedRuntime, cachedSnapshots, UnsupportedWorkspaceRuntimeError, WorkspaceRuntimeOwnershipError;
|
|
6551
7081
|
var init_workspace_runtime = __esm({
|
|
6552
7082
|
"src/core/workspace-runtime.ts"() {
|
|
6553
7083
|
"use strict";
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
7084
|
+
import_node_child_process12 = require("child_process");
|
|
7085
|
+
import_node_fs18 = __toESM(require("fs"));
|
|
7086
|
+
import_node_path18 = __toESM(require("path"));
|
|
6557
7087
|
init_atomic_file();
|
|
6558
7088
|
init_devpod_registry();
|
|
6559
7089
|
init_devsy_workspaces();
|
|
6560
7090
|
init_router();
|
|
6561
7091
|
init_workspace();
|
|
6562
7092
|
SUPPORTED_RUNTIMES2 = ["devpod", "devsy"];
|
|
6563
|
-
RUNTIME_CONFIG_FILE =
|
|
7093
|
+
RUNTIME_CONFIG_FILE = import_node_path18.default.join(DEVROUTER_HOME, "workspace-runtime.json");
|
|
6564
7094
|
INACTIVITY_TIMEOUT_PATTERN = /^(?:\d+(?:ms|s|m|h))+$/;
|
|
6565
7095
|
UnsupportedWorkspaceRuntimeError = class extends Error {
|
|
6566
7096
|
};
|
|
@@ -6581,7 +7111,7 @@ function outputFromResult(result) {
|
|
|
6581
7111
|
return output2.length > 0 ? output2 : void 0;
|
|
6582
7112
|
}
|
|
6583
7113
|
function runTool(command, args = []) {
|
|
6584
|
-
const result = (0,
|
|
7114
|
+
const result = (0, import_node_child_process13.spawnSync)(command, args, {
|
|
6585
7115
|
encoding: "utf-8"
|
|
6586
7116
|
});
|
|
6587
7117
|
if (result.error) {
|
|
@@ -6638,12 +7168,12 @@ function parseMinimumNodeMajor(value) {
|
|
|
6638
7168
|
return Number(match[1]);
|
|
6639
7169
|
}
|
|
6640
7170
|
function readPackageJson(repoPath) {
|
|
6641
|
-
const packagePath =
|
|
6642
|
-
if (!
|
|
7171
|
+
const packagePath = import_node_path19.default.join(repoPath, "package.json");
|
|
7172
|
+
if (!import_node_fs19.default.existsSync(packagePath)) {
|
|
6643
7173
|
return void 0;
|
|
6644
7174
|
}
|
|
6645
7175
|
try {
|
|
6646
|
-
return JSON.parse(
|
|
7176
|
+
return JSON.parse(import_node_fs19.default.readFileSync(packagePath, "utf-8"));
|
|
6647
7177
|
} catch {
|
|
6648
7178
|
return void 0;
|
|
6649
7179
|
}
|
|
@@ -6708,7 +7238,44 @@ function nodeToolchainCheck(repoPath) {
|
|
|
6708
7238
|
details: details.join(", ")
|
|
6709
7239
|
};
|
|
6710
7240
|
}
|
|
7241
|
+
function devsyAgentCheck(inspection) {
|
|
7242
|
+
const details = [
|
|
7243
|
+
`state=${inspection.state}`,
|
|
7244
|
+
`source=${inspection.source}`,
|
|
7245
|
+
...inspection.installedVersion ? [`version=${inspection.installedVersion}`] : [],
|
|
7246
|
+
...inspection.asset ? [`asset=${inspection.asset.name}`] : []
|
|
7247
|
+
].join(", ");
|
|
7248
|
+
if (inspection.state === "ready") {
|
|
7249
|
+
return {
|
|
7250
|
+
id: "global.devsy-agent",
|
|
7251
|
+
level: "ok",
|
|
7252
|
+
summary: "Devsy agent source is ready.",
|
|
7253
|
+
details
|
|
7254
|
+
};
|
|
7255
|
+
}
|
|
7256
|
+
const summary = inspection.state === "missing" ? "Managed Devsy agent source is missing." : inspection.state === "stale" ? "Devsy agent source is stale for this Devrouter release." : "Devsy agent source is invalid.";
|
|
7257
|
+
return {
|
|
7258
|
+
id: "global.devsy-agent",
|
|
7259
|
+
level: "error",
|
|
7260
|
+
summary,
|
|
7261
|
+
details: `${details}, ${inspection.reason}`,
|
|
7262
|
+
suggestion: devsyAgentRepairSuggestion(inspection)
|
|
7263
|
+
};
|
|
7264
|
+
}
|
|
6711
7265
|
function buildGlobalToolChecks(repoPath) {
|
|
7266
|
+
const previous = process.env[DEVSY_DISABLE_TELEMETRY];
|
|
7267
|
+
process.env[DEVSY_DISABLE_TELEMETRY] = "true";
|
|
7268
|
+
try {
|
|
7269
|
+
return buildGlobalToolChecksWithoutTelemetry(repoPath);
|
|
7270
|
+
} finally {
|
|
7271
|
+
if (previous === void 0) {
|
|
7272
|
+
delete process.env[DEVSY_DISABLE_TELEMETRY];
|
|
7273
|
+
} else {
|
|
7274
|
+
process.env[DEVSY_DISABLE_TELEMETRY] = previous;
|
|
7275
|
+
}
|
|
7276
|
+
}
|
|
7277
|
+
}
|
|
7278
|
+
function buildGlobalToolChecksWithoutTelemetry(repoPath) {
|
|
6712
7279
|
const checks = [];
|
|
6713
7280
|
const compose = runTool("docker", ["compose", "version"]);
|
|
6714
7281
|
checks.push({
|
|
@@ -6762,6 +7329,13 @@ function buildGlobalToolChecks(repoPath) {
|
|
|
6762
7329
|
].filter(Boolean).join(", "),
|
|
6763
7330
|
suggestion: runtimeTool.ok ? void 0 : workspaceRuntime === "devsy" ? "Install Devsy for devcontainer workspace flows: brew install devsy-org/homebrew-tap/devsy" : "Install DevPod for devcontainer workspace flows: brew install devpod"
|
|
6764
7331
|
});
|
|
7332
|
+
if (workspaceRuntime === "devsy") {
|
|
7333
|
+
checks.push(
|
|
7334
|
+
devsyAgentCheck(
|
|
7335
|
+
inspectDevsyAgent({ versionOutput: runtimeTool.ok ? runtimeTool.output : "" })
|
|
7336
|
+
)
|
|
7337
|
+
);
|
|
7338
|
+
}
|
|
6765
7339
|
const configProblems = [...configInspection.problems];
|
|
6766
7340
|
const machineResolvedDevpod = workspaceRuntime === "devpod" && (runtimeResolution.source === "auto-detect" || runtimeResolution.source === "default");
|
|
6767
7341
|
if (machineConfig.devsyInactivityTimeout && machineResolvedDevpod) {
|
|
@@ -6783,14 +7357,16 @@ function buildGlobalToolChecks(repoPath) {
|
|
|
6783
7357
|
checks.push(nodeToolchainCheck(repoPath));
|
|
6784
7358
|
return checks;
|
|
6785
7359
|
}
|
|
6786
|
-
var
|
|
7360
|
+
var import_node_child_process13, import_node_fs19, import_node_path19, DEVSY_DISABLE_TELEMETRY;
|
|
6787
7361
|
var init_tool_diagnostics = __esm({
|
|
6788
7362
|
"src/core/tool-diagnostics.ts"() {
|
|
6789
7363
|
"use strict";
|
|
6790
|
-
|
|
6791
|
-
|
|
6792
|
-
|
|
7364
|
+
import_node_child_process13 = require("child_process");
|
|
7365
|
+
import_node_fs19 = __toESM(require("fs"));
|
|
7366
|
+
import_node_path19 = __toESM(require("path"));
|
|
7367
|
+
init_devsy_agent();
|
|
6793
7368
|
init_workspace_runtime();
|
|
7369
|
+
DEVSY_DISABLE_TELEMETRY = "DEVSY_DISABLE_TELEMETRY";
|
|
6794
7370
|
}
|
|
6795
7371
|
});
|
|
6796
7372
|
|
|
@@ -6859,7 +7435,7 @@ function inspectDevpodRuntimeStatus(devpodId, repoPath) {
|
|
|
6859
7435
|
if (resolveWorkspaceRuntimeOrDefault(repoPath) === "devsy") {
|
|
6860
7436
|
return inspectDevsyRuntimeStatus(devpodId);
|
|
6861
7437
|
}
|
|
6862
|
-
const result = (0,
|
|
7438
|
+
const result = (0, import_node_child_process14.spawnSync)("devpod", ["status", devpodId, "--output", "json", "--timeout", "5s"], {
|
|
6863
7439
|
encoding: "utf-8"
|
|
6864
7440
|
});
|
|
6865
7441
|
if (result.status !== 0 || result.error) return "unknown";
|
|
@@ -6892,11 +7468,11 @@ function selectDevpodWorkspace(workspaces, repoPath) {
|
|
|
6892
7468
|
}
|
|
6893
7469
|
return matches[0];
|
|
6894
7470
|
}
|
|
6895
|
-
var
|
|
7471
|
+
var import_node_child_process14;
|
|
6896
7472
|
var init_devpod_workspaces = __esm({
|
|
6897
7473
|
"src/core/devpod-workspaces.ts"() {
|
|
6898
7474
|
"use strict";
|
|
6899
|
-
|
|
7475
|
+
import_node_child_process14 = require("child_process");
|
|
6900
7476
|
init_devpod_registry();
|
|
6901
7477
|
init_devsy_workspaces();
|
|
6902
7478
|
init_workspace();
|
|
@@ -6916,19 +7492,71 @@ function failedStartMayHaveAttached(devsyId, repoPath) {
|
|
|
6916
7492
|
}
|
|
6917
7493
|
}
|
|
6918
7494
|
function withMutationLock(activity, target, operation) {
|
|
6919
|
-
|
|
7495
|
+
import_node_fs20.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
6920
7496
|
return withFileLockSync(
|
|
6921
7497
|
DEVSY_MUTATION_LOCK_FILE,
|
|
6922
|
-
{
|
|
7498
|
+
{
|
|
7499
|
+
activity,
|
|
7500
|
+
target: `'${target}'`,
|
|
7501
|
+
waitMs: DEVSY_MUTATION_WAIT_MS,
|
|
7502
|
+
fair: true,
|
|
7503
|
+
onWait: createStderrWaitReporter(activity, `'${target}'`)
|
|
7504
|
+
},
|
|
7505
|
+
operation
|
|
7506
|
+
);
|
|
7507
|
+
}
|
|
7508
|
+
function withMutationLockAsync(activity, target, operation) {
|
|
7509
|
+
import_node_fs20.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
7510
|
+
return withFileLock(
|
|
7511
|
+
DEVSY_MUTATION_LOCK_FILE,
|
|
7512
|
+
{
|
|
7513
|
+
activity,
|
|
7514
|
+
target: `'${target}'`,
|
|
7515
|
+
waitMs: DEVSY_MUTATION_WAIT_MS,
|
|
7516
|
+
fair: true,
|
|
7517
|
+
onWait: createStderrWaitReporter(activity, `'${target}'`)
|
|
7518
|
+
},
|
|
6923
7519
|
operation
|
|
6924
7520
|
);
|
|
6925
7521
|
}
|
|
6926
7522
|
function commandFailure2(result) {
|
|
6927
7523
|
return [result.error?.message, result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
6928
7524
|
}
|
|
7525
|
+
function runDevsyUp(args, env, quiet) {
|
|
7526
|
+
return new Promise((resolve) => {
|
|
7527
|
+
const child = (0, import_node_child_process15.spawn)("devsy", args, {
|
|
7528
|
+
stdio: ["inherit", quiet ? 2 : "inherit", "pipe"],
|
|
7529
|
+
env
|
|
7530
|
+
});
|
|
7531
|
+
const stderr = child.stderr;
|
|
7532
|
+
if (!stderr) throw new Error("Devsy startup stderr pipe was not created.");
|
|
7533
|
+
let stderrTail = Buffer.alloc(0);
|
|
7534
|
+
let spawnError;
|
|
7535
|
+
stderr.on("data", (chunk) => {
|
|
7536
|
+
const value = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
7537
|
+
const writable = process.stderr.write(value);
|
|
7538
|
+
if (!writable) {
|
|
7539
|
+
stderr.pause();
|
|
7540
|
+
process.stderr.once("drain", () => stderr.resume());
|
|
7541
|
+
}
|
|
7542
|
+
if (value.length >= DEVSY_STDERR_TAIL_BYTES) {
|
|
7543
|
+
stderrTail = Buffer.from(value.subarray(value.length - DEVSY_STDERR_TAIL_BYTES));
|
|
7544
|
+
} else {
|
|
7545
|
+
const combined = Buffer.concat([stderrTail, value]);
|
|
7546
|
+
stderrTail = combined.length > DEVSY_STDERR_TAIL_BYTES ? combined.subarray(combined.length - DEVSY_STDERR_TAIL_BYTES) : combined;
|
|
7547
|
+
}
|
|
7548
|
+
});
|
|
7549
|
+
child.once("error", (error) => {
|
|
7550
|
+
spawnError = error;
|
|
7551
|
+
});
|
|
7552
|
+
child.once("close", (status) => {
|
|
7553
|
+
resolve({ status, error: spawnError, stderrTail: stderrTail.toString("utf-8") });
|
|
7554
|
+
});
|
|
7555
|
+
});
|
|
7556
|
+
}
|
|
6929
7557
|
function runDevsyAction(action2, devsyId, force = false) {
|
|
6930
7558
|
const args = action2 === "delete" ? ["delete", devsyId, ...force ? ["--force"] : [], "--ignore-not-found"] : ["stop", devsyId];
|
|
6931
|
-
const result = (0,
|
|
7559
|
+
const result = (0, import_node_child_process15.spawnSync)("devsy", ["workspace", ...args], { encoding: "utf-8" });
|
|
6932
7560
|
if (result.status !== 0) {
|
|
6933
7561
|
throw new Error(
|
|
6934
7562
|
`devsy workspace ${action2}${force ? " --force" : ""} failed for '${devsyId}': ${commandFailure2(result) || "unknown error"}`
|
|
@@ -6988,9 +7616,17 @@ function assertDevsyTarget(devsyId, repoPath) {
|
|
|
6988
7616
|
}
|
|
6989
7617
|
return id;
|
|
6990
7618
|
}
|
|
6991
|
-
function startDevsyWorkspace(options) {
|
|
7619
|
+
async function startDevsyWorkspace(options) {
|
|
6992
7620
|
const activity = options.recreate ? "Devsy recreate" : "Devsy start";
|
|
6993
|
-
|
|
7621
|
+
let agent;
|
|
7622
|
+
try {
|
|
7623
|
+
agent = requireReadyDevsyAgent();
|
|
7624
|
+
} catch (error) {
|
|
7625
|
+
if (!(error instanceof DevsyAgentReadinessError)) throw error;
|
|
7626
|
+
const repair = devsyAgentRepairSuggestion(error.inspection);
|
|
7627
|
+
throw new Error(`${error.message}. ${repair}`);
|
|
7628
|
+
}
|
|
7629
|
+
return withMutationLockAsync(activity, options.repoPath, async () => {
|
|
6994
7630
|
let devsyId = assertDevsyTarget(options.devsyId, options.repoPath);
|
|
6995
7631
|
if (devsyId && options.recreate) {
|
|
6996
7632
|
const attached = listDevsyWorkspaces();
|
|
@@ -7019,6 +7655,7 @@ function startDevsyWorkspace(options) {
|
|
|
7019
7655
|
}
|
|
7020
7656
|
if (options.recreate) args.push("--recreate");
|
|
7021
7657
|
const env = { ...process.env };
|
|
7658
|
+
env.DEVSY_AGENT_BINARY = agent.binaryPath;
|
|
7022
7659
|
if (options.workspace) {
|
|
7023
7660
|
env.WORKSPACE = options.workspace.token;
|
|
7024
7661
|
env.DEVROUTER_WORKSPACE = options.workspace.token;
|
|
@@ -7030,12 +7667,13 @@ function startDevsyWorkspace(options) {
|
|
|
7030
7667
|
delete env.DEVROUTER_GIT_COMMON_DIR;
|
|
7031
7668
|
delete env.DEVCONTAINER_COMPOSE_OVERLAY;
|
|
7032
7669
|
}
|
|
7033
|
-
const result =
|
|
7034
|
-
stdio: options.quiet ? ["inherit", 2, "inherit"] : "inherit",
|
|
7035
|
-
env
|
|
7036
|
-
});
|
|
7670
|
+
const result = await runDevsyUp(args, env, options.quiet ?? false);
|
|
7037
7671
|
if (result.status !== 0) {
|
|
7038
|
-
|
|
7672
|
+
let message = `devsy workspace up failed for '${devsyId ?? options.repoPath}'.`;
|
|
7673
|
+
if (result.error?.message) message += ` ${result.error.message}`;
|
|
7674
|
+
if (AGENT_ACQUISITION_RE.test(result.stderrTail)) {
|
|
7675
|
+
message += ` Devsy rejected the verified agent source. Run: ${DEVSY_AGENT_SETUP_COMMAND}`;
|
|
7676
|
+
}
|
|
7039
7677
|
if (failedStartMayHaveAttached(devsyId, options.repoPath)) {
|
|
7040
7678
|
throw new DevsyStartPostconditionError(message);
|
|
7041
7679
|
}
|
|
@@ -7061,29 +7699,38 @@ function startDevsyWorkspace(options) {
|
|
|
7061
7699
|
}
|
|
7062
7700
|
});
|
|
7063
7701
|
}
|
|
7064
|
-
var
|
|
7702
|
+
var import_node_child_process15, import_node_fs20, import_node_path20, DEVSY_MUTATION_LOCK_FILE, DEVSY_MUTATION_WAIT_MS, DevsyStartPostconditionError, AGENT_ACQUISITION_RE, DEVSY_STDERR_TAIL_BYTES;
|
|
7065
7703
|
var init_devsy_mutation = __esm({
|
|
7066
7704
|
"src/core/devsy-mutation.ts"() {
|
|
7067
7705
|
"use strict";
|
|
7068
|
-
|
|
7069
|
-
|
|
7070
|
-
|
|
7706
|
+
import_node_child_process15 = require("child_process");
|
|
7707
|
+
import_node_fs20 = __toESM(require("fs"));
|
|
7708
|
+
import_node_path20 = __toESM(require("path"));
|
|
7709
|
+
init_devsy_agent();
|
|
7071
7710
|
init_devsy_workspaces();
|
|
7072
7711
|
init_file_lock();
|
|
7073
7712
|
init_router();
|
|
7074
|
-
DEVSY_MUTATION_LOCK_FILE =
|
|
7075
|
-
DEVSY_MUTATION_WAIT_MS =
|
|
7713
|
+
DEVSY_MUTATION_LOCK_FILE = import_node_path20.default.join(DEVROUTER_HOME, "devsy-mutation.lock");
|
|
7714
|
+
DEVSY_MUTATION_WAIT_MS = 18e5;
|
|
7076
7715
|
DevsyStartPostconditionError = class extends Error {
|
|
7077
7716
|
};
|
|
7717
|
+
AGENT_ACQUISITION_RE = /inject agent.*agent binary not found/i;
|
|
7718
|
+
DEVSY_STDERR_TAIL_BYTES = 8192;
|
|
7078
7719
|
}
|
|
7079
7720
|
});
|
|
7080
7721
|
|
|
7081
7722
|
// src/core/devpod-mutation.ts
|
|
7082
7723
|
function withMutationLock2(activity, target, operation) {
|
|
7083
|
-
|
|
7724
|
+
import_node_fs21.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
7084
7725
|
return withFileLockSync(
|
|
7085
7726
|
DEVPOD_MUTATION_LOCK_FILE,
|
|
7086
|
-
{
|
|
7727
|
+
{
|
|
7728
|
+
activity,
|
|
7729
|
+
target: `'${target}'`,
|
|
7730
|
+
waitMs: DEVPOD_MUTATION_WAIT_MS,
|
|
7731
|
+
fair: true,
|
|
7732
|
+
onWait: createStderrWaitReporter(activity, `'${target}'`)
|
|
7733
|
+
},
|
|
7087
7734
|
operation
|
|
7088
7735
|
);
|
|
7089
7736
|
}
|
|
@@ -7092,7 +7739,7 @@ function commandFailure3(result) {
|
|
|
7092
7739
|
}
|
|
7093
7740
|
function runDevpodAction(action2, devpodId, force = false) {
|
|
7094
7741
|
const args = action2 === "delete" ? [action2, devpodId, ...force ? ["--force"] : [], "--ignore-not-found"] : [action2, devpodId];
|
|
7095
|
-
const result = (0,
|
|
7742
|
+
const result = (0, import_node_child_process16.spawnSync)("devpod", args, { encoding: "utf-8" });
|
|
7096
7743
|
if (result.status !== 0) {
|
|
7097
7744
|
throw new Error(
|
|
7098
7745
|
`devpod ${action2}${force ? " --force" : ""} failed for '${devpodId}': ${commandFailure3(result) || "unknown error"}`
|
|
@@ -7158,10 +7805,10 @@ function deleteOwnedDevpodWorkspace(devpodId, worktreePath) {
|
|
|
7158
7805
|
resetWorkspaceRuntimeCaches();
|
|
7159
7806
|
return result;
|
|
7160
7807
|
}
|
|
7161
|
-
function startDevpodWorkspace(options) {
|
|
7808
|
+
async function startDevpodWorkspace(options) {
|
|
7162
7809
|
if (resolveWorkspaceRuntimeOrDefault(options.repoPath) === "devsy") {
|
|
7163
7810
|
try {
|
|
7164
|
-
const result = startDevsyWorkspace({
|
|
7811
|
+
const result = await startDevsyWorkspace({
|
|
7165
7812
|
repoPath: options.repoPath,
|
|
7166
7813
|
devsyId: options.devpodId,
|
|
7167
7814
|
devcontainerPath: options.devcontainerPath,
|
|
@@ -7220,7 +7867,7 @@ function startDevpodWorkspace(options) {
|
|
|
7220
7867
|
delete env.DEVROUTER_GIT_COMMON_DIR;
|
|
7221
7868
|
delete env.DEVCONTAINER_COMPOSE_OVERLAY;
|
|
7222
7869
|
}
|
|
7223
|
-
const result = (0,
|
|
7870
|
+
const result = (0, import_node_child_process16.spawnSync)("devpod", args, {
|
|
7224
7871
|
stdio: options.quiet ? ["inherit", 2, "inherit"] : "inherit",
|
|
7225
7872
|
env
|
|
7226
7873
|
});
|
|
@@ -7248,20 +7895,20 @@ function startDevpodWorkspace(options) {
|
|
|
7248
7895
|
}
|
|
7249
7896
|
});
|
|
7250
7897
|
}
|
|
7251
|
-
var
|
|
7898
|
+
var import_node_child_process16, import_node_fs21, import_node_path21, DEVPOD_MUTATION_LOCK_FILE, DEVPOD_MUTATION_WAIT_MS, DevpodStartPostconditionError;
|
|
7252
7899
|
var init_devpod_mutation = __esm({
|
|
7253
7900
|
"src/core/devpod-mutation.ts"() {
|
|
7254
7901
|
"use strict";
|
|
7255
|
-
|
|
7256
|
-
|
|
7257
|
-
|
|
7902
|
+
import_node_child_process16 = require("child_process");
|
|
7903
|
+
import_node_fs21 = __toESM(require("fs"));
|
|
7904
|
+
import_node_path21 = __toESM(require("path"));
|
|
7258
7905
|
init_devpod_workspaces();
|
|
7259
7906
|
init_devsy_mutation();
|
|
7260
7907
|
init_file_lock();
|
|
7261
7908
|
init_router();
|
|
7262
7909
|
init_workspace_runtime();
|
|
7263
|
-
DEVPOD_MUTATION_LOCK_FILE =
|
|
7264
|
-
DEVPOD_MUTATION_WAIT_MS =
|
|
7910
|
+
DEVPOD_MUTATION_LOCK_FILE = import_node_path21.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
|
|
7911
|
+
DEVPOD_MUTATION_WAIT_MS = 18e5;
|
|
7265
7912
|
DevpodStartPostconditionError = class extends Error {
|
|
7266
7913
|
};
|
|
7267
7914
|
}
|
|
@@ -7274,7 +7921,7 @@ function commandError(command, repoPath, stderr) {
|
|
|
7274
7921
|
);
|
|
7275
7922
|
}
|
|
7276
7923
|
function resolveGitCommonDir(repoPath) {
|
|
7277
|
-
const result = (0,
|
|
7924
|
+
const result = (0, import_node_child_process17.spawnSync)("git", ["-C", repoPath, "rev-parse", "--git-common-dir"], {
|
|
7278
7925
|
encoding: "utf-8",
|
|
7279
7926
|
env: READ_ONLY_GIT_ENV
|
|
7280
7927
|
});
|
|
@@ -7282,10 +7929,10 @@ function resolveGitCommonDir(repoPath) {
|
|
|
7282
7929
|
if (result.status !== 0 || !output2) {
|
|
7283
7930
|
throw commandError("Could not resolve the Git common directory", repoPath, result.stderr);
|
|
7284
7931
|
}
|
|
7285
|
-
return comparableWorkspacePath(
|
|
7932
|
+
return comparableWorkspacePath(import_node_path22.default.isAbsolute(output2) ? output2 : import_node_path22.default.resolve(repoPath, output2));
|
|
7286
7933
|
}
|
|
7287
7934
|
function resolveGitTopLevel(repoPath) {
|
|
7288
|
-
const result = (0,
|
|
7935
|
+
const result = (0, import_node_child_process17.spawnSync)("git", ["-C", repoPath, "rev-parse", "--show-toplevel"], {
|
|
7289
7936
|
encoding: "utf-8",
|
|
7290
7937
|
env: READ_ONLY_GIT_ENV
|
|
7291
7938
|
});
|
|
@@ -7296,7 +7943,7 @@ function resolveGitTopLevel(repoPath) {
|
|
|
7296
7943
|
return comparableWorkspacePath(output2);
|
|
7297
7944
|
}
|
|
7298
7945
|
function listGitWorktrees(repoPath) {
|
|
7299
|
-
const result = (0,
|
|
7946
|
+
const result = (0, import_node_child_process17.spawnSync)("git", ["-C", repoPath, "worktree", "list", "--porcelain"], {
|
|
7300
7947
|
encoding: "utf-8",
|
|
7301
7948
|
env: READ_ONLY_GIT_ENV
|
|
7302
7949
|
});
|
|
@@ -7333,7 +7980,7 @@ function listGitWorktrees(repoPath) {
|
|
|
7333
7980
|
return worktrees;
|
|
7334
7981
|
}
|
|
7335
7982
|
function ownershipDirectory(repoPath) {
|
|
7336
|
-
return
|
|
7983
|
+
return import_node_path22.default.join(resolveGitCommonDir(repoPath), OWNERSHIP_DIR);
|
|
7337
7984
|
}
|
|
7338
7985
|
function validateWorkspace(value, label) {
|
|
7339
7986
|
if (typeof value !== "string" || wsFromBranch(value) !== value) {
|
|
@@ -7361,7 +8008,7 @@ function validateRecord(value, expectedWorkspace) {
|
|
|
7361
8008
|
`workspace ownership file '${expectedWorkspace}' contains identity '${workspace}'`
|
|
7362
8009
|
);
|
|
7363
8010
|
}
|
|
7364
|
-
if (typeof candidate.worktreePath !== "string" || !
|
|
8011
|
+
if (typeof candidate.worktreePath !== "string" || !import_node_path22.default.isAbsolute(candidate.worktreePath)) {
|
|
7365
8012
|
throw new Error("invalid workspace ownership worktreePath");
|
|
7366
8013
|
}
|
|
7367
8014
|
if (candidate.branch !== null && typeof candidate.branch !== "string") {
|
|
@@ -7381,7 +8028,7 @@ function validateRecord(value, expectedWorkspace) {
|
|
|
7381
8028
|
function readRecordFile(filePath, expectedWorkspace) {
|
|
7382
8029
|
let parsed;
|
|
7383
8030
|
try {
|
|
7384
|
-
parsed = JSON.parse(
|
|
8031
|
+
parsed = JSON.parse(import_node_fs22.default.readFileSync(filePath, "utf-8"));
|
|
7385
8032
|
} catch (error) {
|
|
7386
8033
|
if (error instanceof SyntaxError) {
|
|
7387
8034
|
throw new Error(`invalid workspace ownership JSON at '${filePath}'`);
|
|
@@ -7397,7 +8044,7 @@ function listWorkspaceOwnership(repoPath) {
|
|
|
7397
8044
|
function listWorkspaceOwnershipInDirectory(directory) {
|
|
7398
8045
|
let entries;
|
|
7399
8046
|
try {
|
|
7400
|
-
entries =
|
|
8047
|
+
entries = import_node_fs22.default.readdirSync(directory, { withFileTypes: true });
|
|
7401
8048
|
} catch (error) {
|
|
7402
8049
|
if (error.code === "ENOENT") return [];
|
|
7403
8050
|
throw error;
|
|
@@ -7405,14 +8052,14 @@ function listWorkspaceOwnershipInDirectory(directory) {
|
|
|
7405
8052
|
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).sort((left, right) => left.name.localeCompare(right.name)).map((entry) => {
|
|
7406
8053
|
const workspace = entry.name.slice(0, -".json".length);
|
|
7407
8054
|
validateWorkspace(workspace, "filename");
|
|
7408
|
-
return readRecordFile(
|
|
8055
|
+
return readRecordFile(import_node_path22.default.join(directory, entry.name), workspace);
|
|
7409
8056
|
});
|
|
7410
8057
|
}
|
|
7411
8058
|
function writeWorkspaceOwnershipInDirectory(directory, input2) {
|
|
7412
8059
|
const workspace = validateWorkspace(input2.workspace, "workspace");
|
|
7413
8060
|
const devpodId = validateWorkspace(input2.devpodId, "devpodId");
|
|
7414
8061
|
const worktreePath = comparableWorkspacePath(input2.worktreePath);
|
|
7415
|
-
const filePath =
|
|
8062
|
+
const filePath = import_node_path22.default.join(directory, `${workspace}.json`);
|
|
7416
8063
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7417
8064
|
const records = listWorkspaceOwnershipInDirectory(directory);
|
|
7418
8065
|
const existing = records.find((record2) => record2.workspace === workspace);
|
|
@@ -7448,9 +8095,9 @@ function writeWorkspaceOwnershipInDirectory(directory, input2) {
|
|
|
7448
8095
|
return record;
|
|
7449
8096
|
}
|
|
7450
8097
|
function removeWorkspaceOwnershipInDirectory(directory, workspace) {
|
|
7451
|
-
const filePath =
|
|
8098
|
+
const filePath = import_node_path22.default.join(directory, `${validateWorkspace(workspace, "workspace")}.json`);
|
|
7452
8099
|
try {
|
|
7453
|
-
|
|
8100
|
+
import_node_fs22.default.rmSync(filePath);
|
|
7454
8101
|
return true;
|
|
7455
8102
|
} catch (error) {
|
|
7456
8103
|
if (error.code === "ENOENT") return false;
|
|
@@ -7461,7 +8108,7 @@ function sameOwnershipRecord(left, right) {
|
|
|
7461
8108
|
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
8109
|
}
|
|
7463
8110
|
function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
|
|
7464
|
-
const filePath =
|
|
8111
|
+
const filePath = import_node_path22.default.join(
|
|
7465
8112
|
directory,
|
|
7466
8113
|
`${validateWorkspace(expected.workspace, "workspace")}.json`
|
|
7467
8114
|
);
|
|
@@ -7473,14 +8120,14 @@ function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
|
|
|
7473
8120
|
throw error;
|
|
7474
8121
|
}
|
|
7475
8122
|
if (!sameOwnershipRecord(current, expected)) return "changed";
|
|
7476
|
-
|
|
8123
|
+
import_node_fs22.default.rmSync(filePath);
|
|
7477
8124
|
return "removed";
|
|
7478
8125
|
}
|
|
7479
8126
|
function withWorkspaceOwnershipTransaction(repoPath, operation, options = {}) {
|
|
7480
8127
|
const directory = ownershipDirectory(repoPath);
|
|
7481
|
-
|
|
8128
|
+
import_node_fs22.default.mkdirSync(directory, { recursive: true });
|
|
7482
8129
|
return withFileLockSync(
|
|
7483
|
-
|
|
8130
|
+
import_node_path22.default.join(directory, ".lock"),
|
|
7484
8131
|
{
|
|
7485
8132
|
activity: "workspace ownership transaction",
|
|
7486
8133
|
target: `'${repoPath}'`,
|
|
@@ -7513,7 +8160,7 @@ function persistedWorkspaceOwners(repoPath, worktreePath) {
|
|
|
7513
8160
|
try {
|
|
7514
8161
|
workspace = readPersistedWorkspace(worktree.path);
|
|
7515
8162
|
} catch (error) {
|
|
7516
|
-
if (!
|
|
8163
|
+
if (!import_node_fs22.default.existsSync(worktree.path)) continue;
|
|
7517
8164
|
throw error;
|
|
7518
8165
|
}
|
|
7519
8166
|
if (!workspace) continue;
|
|
@@ -7676,7 +8323,7 @@ function inspectWorkspaceOwnership(record, worktrees, devpods) {
|
|
|
7676
8323
|
if (worktree?.locked) {
|
|
7677
8324
|
return { ownerStatus: "locked", devpodStatus, worktree };
|
|
7678
8325
|
}
|
|
7679
|
-
if ((!worktree || worktree.prunable) &&
|
|
8326
|
+
if ((!worktree || worktree.prunable) && import_node_fs22.default.existsSync(record.worktreePath)) {
|
|
7680
8327
|
return { ownerStatus: "conflict", devpodStatus, worktree };
|
|
7681
8328
|
}
|
|
7682
8329
|
if (!worktree || worktree.prunable) {
|
|
@@ -7700,20 +8347,20 @@ function listMissingWorkspaceOwnership(repoPath) {
|
|
|
7700
8347
|
(record) => inspectWorkspaceOwnership(record, worktrees, void 0).ownerStatus === "missing"
|
|
7701
8348
|
);
|
|
7702
8349
|
}
|
|
7703
|
-
var
|
|
8350
|
+
var import_node_child_process17, import_node_fs22, import_node_path22, READ_ONLY_GIT_ENV, OWNERSHIP_VERSION, OWNERSHIP_DIR;
|
|
7704
8351
|
var init_workspace_ownership = __esm({
|
|
7705
8352
|
"src/core/workspace-ownership.ts"() {
|
|
7706
8353
|
"use strict";
|
|
7707
|
-
|
|
7708
|
-
|
|
7709
|
-
|
|
8354
|
+
import_node_child_process17 = require("child_process");
|
|
8355
|
+
import_node_fs22 = __toESM(require("fs"));
|
|
8356
|
+
import_node_path22 = __toESM(require("path"));
|
|
7710
8357
|
init_atomic_file();
|
|
7711
8358
|
init_devpod_workspaces();
|
|
7712
8359
|
init_file_lock();
|
|
7713
8360
|
init_workspace();
|
|
7714
8361
|
READ_ONLY_GIT_ENV = { ...process.env, GIT_OPTIONAL_LOCKS: "0" };
|
|
7715
8362
|
OWNERSHIP_VERSION = 1;
|
|
7716
|
-
OWNERSHIP_DIR =
|
|
8363
|
+
OWNERSHIP_DIR = import_node_path22.default.join("devrouter", "workspaces");
|
|
7717
8364
|
}
|
|
7718
8365
|
});
|
|
7719
8366
|
|
|
@@ -7727,10 +8374,10 @@ function inRepositoryWorkspaceScope(repoPath, worktreePath, livePaths) {
|
|
|
7727
8374
|
if (livePaths.some((candidate) => sameWorkspacePath(candidate, worktreePath))) return true;
|
|
7728
8375
|
const comparableRepo = comparableWorkspacePath(repoPath);
|
|
7729
8376
|
const comparableWorktree = comparableWorkspacePath(worktreePath);
|
|
7730
|
-
const localRoot =
|
|
8377
|
+
const localRoot = import_node_path23.default.join(comparableRepo, "trees") + import_node_path23.default.sep;
|
|
7731
8378
|
if (comparableWorktree.startsWith(localRoot)) return true;
|
|
7732
|
-
const legacyPrefix = `${
|
|
7733
|
-
return
|
|
8379
|
+
const legacyPrefix = `${import_node_path23.default.basename(comparableRepo)}-`;
|
|
8380
|
+
return import_node_path23.default.dirname(comparableWorktree) === import_node_path23.default.dirname(comparableRepo) && import_node_path23.default.basename(comparableWorktree).startsWith(legacyPrefix);
|
|
7734
8381
|
}
|
|
7735
8382
|
function previewActions(devpodStatus, routeCount, includeRecord) {
|
|
7736
8383
|
const actions = [
|
|
@@ -7982,11 +8629,11 @@ function applyWorkspaceGc(plan) {
|
|
|
7982
8629
|
candidates
|
|
7983
8630
|
};
|
|
7984
8631
|
}
|
|
7985
|
-
var
|
|
8632
|
+
var import_node_path23;
|
|
7986
8633
|
var init_workspace_gc = __esm({
|
|
7987
8634
|
"src/core/workspace-gc.ts"() {
|
|
7988
8635
|
"use strict";
|
|
7989
|
-
|
|
8636
|
+
import_node_path23 = __toESM(require("path"));
|
|
7990
8637
|
init_devpod_mutation();
|
|
7991
8638
|
init_devpod_workspaces();
|
|
7992
8639
|
init_host_routes();
|
|
@@ -8056,11 +8703,11 @@ function inspectPostgresCredentials(repoPath, config) {
|
|
|
8056
8703
|
parseErrors.push(`${app.name}: ${composeFile} (${message})`);
|
|
8057
8704
|
continue;
|
|
8058
8705
|
}
|
|
8059
|
-
if (!
|
|
8706
|
+
if (!import_node_fs23.default.existsSync(absolutePath)) {
|
|
8060
8707
|
continue;
|
|
8061
8708
|
}
|
|
8062
8709
|
try {
|
|
8063
|
-
const raw =
|
|
8710
|
+
const raw = import_node_fs23.default.readFileSync(absolutePath, "utf-8");
|
|
8064
8711
|
const parsed = import_yaml5.default.parse(raw);
|
|
8065
8712
|
const root = asRecord2(parsed);
|
|
8066
8713
|
const services = asRecord2(root?.services);
|
|
@@ -8339,7 +8986,7 @@ async function buildDoctorReport(options = {}) {
|
|
|
8339
8986
|
const config = runtimeConfig.config;
|
|
8340
8987
|
loadedConfig = config;
|
|
8341
8988
|
loadedWorkspace = runtimeConfig.workspace;
|
|
8342
|
-
const cliVersion = true ? "0.0.
|
|
8989
|
+
const cliVersion = true ? "0.0.48" : "0.0.0-dev";
|
|
8343
8990
|
const configVersion = config.devrouter?.version;
|
|
8344
8991
|
if (configVersion && cliVersion !== "0.0.0-dev" && compareSemver(configVersion, cliVersion) > 0) {
|
|
8345
8992
|
addCheck(checks, {
|
|
@@ -8369,9 +9016,9 @@ async function buildDoctorReport(options = {}) {
|
|
|
8369
9016
|
(app) => app.docker.composeFiles.map((filePath) => ({
|
|
8370
9017
|
app: app.name,
|
|
8371
9018
|
filePath,
|
|
8372
|
-
absolutePath:
|
|
9019
|
+
absolutePath: import_node_path24.default.resolve(repo.path, filePath)
|
|
8373
9020
|
}))
|
|
8374
|
-
).filter((entry) => !
|
|
9021
|
+
).filter((entry) => !import_node_fs23.default.existsSync(entry.absolutePath));
|
|
8375
9022
|
addCheck(checks, {
|
|
8376
9023
|
id: "repo.compose-files",
|
|
8377
9024
|
level: missingComposeFiles.length === 0 ? "ok" : "error",
|
|
@@ -8414,8 +9061,8 @@ async function buildDoctorReport(options = {}) {
|
|
|
8414
9061
|
const missingHostCwds = config.apps.filter((app) => app.runtime === "host").map((app) => ({
|
|
8415
9062
|
app: app.name,
|
|
8416
9063
|
cwd: app.hostRun.cwd,
|
|
8417
|
-
absolutePath:
|
|
8418
|
-
})).filter((entry) => !
|
|
9064
|
+
absolutePath: import_node_path24.default.resolve(repo.path, app.hostRun.cwd)
|
|
9065
|
+
})).filter((entry) => !import_node_fs23.default.existsSync(entry.absolutePath));
|
|
8419
9066
|
addCheck(checks, {
|
|
8420
9067
|
id: "repo.host-cwd",
|
|
8421
9068
|
level: missingHostCwds.length === 0 ? "ok" : "error",
|
|
@@ -8559,12 +9206,12 @@ async function buildDoctorReport(options = {}) {
|
|
|
8559
9206
|
nextSteps
|
|
8560
9207
|
};
|
|
8561
9208
|
}
|
|
8562
|
-
var
|
|
9209
|
+
var import_node_fs23, import_node_path24, import_yaml5, POSTGRES_DEFAULTS;
|
|
8563
9210
|
var init_doctor = __esm({
|
|
8564
9211
|
"src/core/doctor.ts"() {
|
|
8565
9212
|
"use strict";
|
|
8566
|
-
|
|
8567
|
-
|
|
9213
|
+
import_node_fs23 = __toESM(require("fs"));
|
|
9214
|
+
import_node_path24 = __toESM(require("path"));
|
|
8568
9215
|
import_yaml5 = __toESM(require("yaml"));
|
|
8569
9216
|
init_docker();
|
|
8570
9217
|
init_host_routes();
|
|
@@ -8671,11 +9318,13 @@ async function runSetup(options = {}) {
|
|
|
8671
9318
|
})
|
|
8672
9319
|
);
|
|
8673
9320
|
}
|
|
9321
|
+
let requestedWorkspaceRuntime;
|
|
8674
9322
|
if (options.workspaceRuntime !== void 0 || options.devsyInactivityTimeout !== void 0) {
|
|
8675
9323
|
try {
|
|
8676
9324
|
const existing = readWorkspaceRuntimeConfig();
|
|
9325
|
+
requestedWorkspaceRuntime = options.workspaceRuntime ? parseWorkspaceRuntime(options.workspaceRuntime) : void 0;
|
|
8677
9326
|
const next = {
|
|
8678
|
-
runtime:
|
|
9327
|
+
runtime: requestedWorkspaceRuntime ?? existing.runtime,
|
|
8679
9328
|
devsyInactivityTimeout: options.devsyInactivityTimeout ?? existing.devsyInactivityTimeout
|
|
8680
9329
|
};
|
|
8681
9330
|
writeWorkspaceRuntimeConfig(next);
|
|
@@ -8697,6 +9346,28 @@ async function runSetup(options = {}) {
|
|
|
8697
9346
|
);
|
|
8698
9347
|
}
|
|
8699
9348
|
}
|
|
9349
|
+
if (requestedWorkspaceRuntime === "devsy") {
|
|
9350
|
+
try {
|
|
9351
|
+
const prepared = await prepareDevsyAgent();
|
|
9352
|
+
actions.push(
|
|
9353
|
+
action(prepared.changed ? "performed" : "skipped", {
|
|
9354
|
+
id: "global.devsy-agent",
|
|
9355
|
+
summary: prepared.changed ? `Downloaded and verified ${prepared.asset.name} for Devsy.` : `Verified the ${prepared.source} Devsy agent source.`,
|
|
9356
|
+
details: `version=${SUPPORTED_DEVSY_VERSION}, asset=${prepared.asset.name}, source=${prepared.source}, transport=${prepared.transport}`
|
|
9357
|
+
})
|
|
9358
|
+
);
|
|
9359
|
+
} catch (error) {
|
|
9360
|
+
const details = error instanceof DevsyAgentReadinessError ? `state=${error.inspection.state}, source=${error.inspection.source}, ${error.inspection.reason}` : error instanceof Error ? error.message : String(error);
|
|
9361
|
+
actions.push(
|
|
9362
|
+
action("failed", {
|
|
9363
|
+
id: "global.devsy-agent",
|
|
9364
|
+
summary: "Failed to prepare a verified Devsy agent source.",
|
|
9365
|
+
details,
|
|
9366
|
+
suggestion: error instanceof DevsyAgentReadinessError ? devsyAgentRepairSuggestion(error.inspection) : `Run: ${DEVSY_AGENT_SETUP_COMMAND}`
|
|
9367
|
+
})
|
|
9368
|
+
);
|
|
9369
|
+
}
|
|
9370
|
+
}
|
|
8700
9371
|
try {
|
|
8701
9372
|
const existed = await networkExists(DEVNET_NAME);
|
|
8702
9373
|
await ensureNetwork(DEVNET_NAME);
|
|
@@ -8786,6 +9457,7 @@ async function runSetup(options = {}) {
|
|
|
8786
9457
|
var init_setup = __esm({
|
|
8787
9458
|
"src/core/setup.ts"() {
|
|
8788
9459
|
"use strict";
|
|
9460
|
+
init_devsy_agent();
|
|
8789
9461
|
init_docker();
|
|
8790
9462
|
init_doctor();
|
|
8791
9463
|
init_repo_config();
|
|
@@ -8854,12 +9526,12 @@ function parseSsPortListeners(stdout, port) {
|
|
|
8854
9526
|
return listeners;
|
|
8855
9527
|
}
|
|
8856
9528
|
function findPortListeners(port) {
|
|
8857
|
-
const result = (0,
|
|
9529
|
+
const result = (0, import_node_child_process18.spawnSync)("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN"], {
|
|
8858
9530
|
encoding: "utf-8"
|
|
8859
9531
|
});
|
|
8860
9532
|
if (result.error && result.error.code === "ENOENT") {
|
|
8861
9533
|
if (process.platform === "linux") {
|
|
8862
|
-
const ssResult = (0,
|
|
9534
|
+
const ssResult = (0, import_node_child_process18.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
|
|
8863
9535
|
if (ssResult.status === 0 && ssResult.stdout) {
|
|
8864
9536
|
return parseSsPortListeners(ssResult.stdout, port);
|
|
8865
9537
|
}
|
|
@@ -8888,11 +9560,11 @@ function findPortListeners(port) {
|
|
|
8888
9560
|
};
|
|
8889
9561
|
});
|
|
8890
9562
|
}
|
|
8891
|
-
var
|
|
9563
|
+
var import_node_child_process18;
|
|
8892
9564
|
var init_ports = __esm({
|
|
8893
9565
|
"src/util/ports.ts"() {
|
|
8894
9566
|
"use strict";
|
|
8895
|
-
|
|
9567
|
+
import_node_child_process18 = require("child_process");
|
|
8896
9568
|
}
|
|
8897
9569
|
});
|
|
8898
9570
|
|
|
@@ -8969,18 +9641,18 @@ function probeHttpRoute(host, options = {}) {
|
|
|
8969
9641
|
args.push("--cacert", CERT_FILE);
|
|
8970
9642
|
}
|
|
8971
9643
|
args.push(url);
|
|
8972
|
-
const result = (0,
|
|
9644
|
+
const result = (0, import_node_child_process19.spawnSync)("curl", args, { encoding: "utf-8" });
|
|
8973
9645
|
const stdout = result.stdout?.trim() ?? "";
|
|
8974
9646
|
const status = /^\d{3}$/.test(stdout) ? Number(stdout) : void 0;
|
|
8975
9647
|
const ok = result.status === 0 && status !== void 0 && status >= 100 && status < 500;
|
|
8976
9648
|
const details = ok ? `HTTP ${status}` : [status === void 0 ? void 0 : `HTTP ${status}`, result.stderr?.trim()].filter(Boolean).join(": ") || `curl exited with status ${result.status ?? "unknown"}`;
|
|
8977
9649
|
return { ok, status, details };
|
|
8978
9650
|
}
|
|
8979
|
-
var
|
|
9651
|
+
var import_node_child_process19;
|
|
8980
9652
|
var init_http_route_probe = __esm({
|
|
8981
9653
|
"src/core/http-route-probe.ts"() {
|
|
8982
9654
|
"use strict";
|
|
8983
|
-
|
|
9655
|
+
import_node_child_process19 = require("child_process");
|
|
8984
9656
|
init_router();
|
|
8985
9657
|
init_tls();
|
|
8986
9658
|
}
|
|
@@ -9056,14 +9728,135 @@ var init_route_publication = __esm({
|
|
|
9056
9728
|
}
|
|
9057
9729
|
});
|
|
9058
9730
|
|
|
9731
|
+
// src/core/traefik-route-health.ts
|
|
9732
|
+
function sleep(ms) {
|
|
9733
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9734
|
+
}
|
|
9735
|
+
function inspectRouterApi(protocol) {
|
|
9736
|
+
const result = (0, import_node_child_process20.spawnSync)(
|
|
9737
|
+
"curl",
|
|
9738
|
+
[
|
|
9739
|
+
"--silent",
|
|
9740
|
+
"--show-error",
|
|
9741
|
+
"--fail",
|
|
9742
|
+
"--max-time",
|
|
9743
|
+
"2",
|
|
9744
|
+
`${ROUTER_API_BASE}/${protocol}/routers`
|
|
9745
|
+
],
|
|
9746
|
+
{ encoding: "utf-8" }
|
|
9747
|
+
);
|
|
9748
|
+
if (result.status !== 0) {
|
|
9749
|
+
return {
|
|
9750
|
+
ok: false,
|
|
9751
|
+
details: result.stderr?.trim() || `curl exited with status ${result.status ?? "unknown"}`
|
|
9752
|
+
};
|
|
9753
|
+
}
|
|
9754
|
+
let value;
|
|
9755
|
+
try {
|
|
9756
|
+
value = JSON.parse(result.stdout);
|
|
9757
|
+
} catch {
|
|
9758
|
+
return { ok: false, details: `${protocol.toUpperCase()} router API returned invalid JSON` };
|
|
9759
|
+
}
|
|
9760
|
+
if (!Array.isArray(value)) {
|
|
9761
|
+
return { ok: false, details: `${protocol.toUpperCase()} router API returned a non-array` };
|
|
9762
|
+
}
|
|
9763
|
+
return {
|
|
9764
|
+
ok: true,
|
|
9765
|
+
names: new Set(
|
|
9766
|
+
value.flatMap((item) => {
|
|
9767
|
+
if (!item || typeof item !== "object") return [];
|
|
9768
|
+
const name = item.name;
|
|
9769
|
+
return typeof name === "string" ? [name] : [];
|
|
9770
|
+
})
|
|
9771
|
+
)
|
|
9772
|
+
};
|
|
9773
|
+
}
|
|
9774
|
+
function inspectExpectedRoutes(routes) {
|
|
9775
|
+
const missing = [];
|
|
9776
|
+
const failures = [];
|
|
9777
|
+
for (const protocol of ["http", "tcp"]) {
|
|
9778
|
+
const expected = routes.filter((route) => (route.protocol ?? "http") === protocol).map((route) => buildHostRouteRouterName(route.repoPath, route.name));
|
|
9779
|
+
if (expected.length === 0) continue;
|
|
9780
|
+
const result = inspectRouterApi(protocol);
|
|
9781
|
+
if (!result.ok) {
|
|
9782
|
+
failures.push(result.details);
|
|
9783
|
+
missing.push(...expected);
|
|
9784
|
+
continue;
|
|
9785
|
+
}
|
|
9786
|
+
missing.push(...expected.filter((name) => !result.names.has(name)));
|
|
9787
|
+
}
|
|
9788
|
+
return missing.length === 0 ? { ok: true } : {
|
|
9789
|
+
ok: false,
|
|
9790
|
+
missing,
|
|
9791
|
+
details: failures.length > 0 ? failures.join("; ") : "router names are absent"
|
|
9792
|
+
};
|
|
9793
|
+
}
|
|
9794
|
+
async function waitForExpectedRoutes(routes, timeoutMs, pollIntervalMs) {
|
|
9795
|
+
const deadline = Date.now() + timeoutMs;
|
|
9796
|
+
let result;
|
|
9797
|
+
do {
|
|
9798
|
+
result = inspectExpectedRoutes(routes);
|
|
9799
|
+
if (result.ok) return result;
|
|
9800
|
+
if (Date.now() < deadline) {
|
|
9801
|
+
await sleep(Math.min(pollIntervalMs, Math.max(1, deadline - Date.now())));
|
|
9802
|
+
}
|
|
9803
|
+
} while (Date.now() < deadline);
|
|
9804
|
+
return result;
|
|
9805
|
+
}
|
|
9806
|
+
async function ensureTraefikRoutesLoaded(routes, options = {}) {
|
|
9807
|
+
if (routes.length === 0) return { restarted: false };
|
|
9808
|
+
const initialTimeoutMs = options.initialTimeoutMs ?? DEFAULT_INITIAL_TIMEOUT_MS;
|
|
9809
|
+
const recoveryTimeoutMs = options.recoveryTimeoutMs ?? DEFAULT_RECOVERY_TIMEOUT_MS;
|
|
9810
|
+
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
9811
|
+
const initial = await waitForExpectedRoutes(routes, initialTimeoutMs, pollIntervalMs);
|
|
9812
|
+
if (initial.ok) return { restarted: false };
|
|
9813
|
+
import_node_fs24.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
9814
|
+
return withFileLock(
|
|
9815
|
+
ROUTER_RELOAD_LOCK_FILE,
|
|
9816
|
+
{
|
|
9817
|
+
activity: "Traefik route reload recovery",
|
|
9818
|
+
waitMs: initialTimeoutMs + recoveryTimeoutMs + 1e4,
|
|
9819
|
+
fair: true,
|
|
9820
|
+
onWait: createStderrWaitReporter("Traefik route reload recovery", "shared router")
|
|
9821
|
+
},
|
|
9822
|
+
async () => {
|
|
9823
|
+
const recheck = await waitForExpectedRoutes(routes, 0, pollIntervalMs);
|
|
9824
|
+
if (recheck.ok) return { restarted: false };
|
|
9825
|
+
restartRouterStack();
|
|
9826
|
+
const recovered = await waitForExpectedRoutes(routes, recoveryTimeoutMs, pollIntervalMs);
|
|
9827
|
+
if (recovered.ok) return { restarted: true };
|
|
9828
|
+
throw new Error(
|
|
9829
|
+
`Traefik did not load file-provider routes after one restart: ${recovered.missing.join(", ")} (${recovered.details}). Inspect: devrouter logs --tail 100`
|
|
9830
|
+
);
|
|
9831
|
+
}
|
|
9832
|
+
);
|
|
9833
|
+
}
|
|
9834
|
+
var import_node_child_process20, import_node_fs24, import_node_path25, ROUTER_API_BASE, ROUTER_RELOAD_LOCK_FILE, DEFAULT_INITIAL_TIMEOUT_MS, DEFAULT_RECOVERY_TIMEOUT_MS, DEFAULT_POLL_INTERVAL_MS;
|
|
9835
|
+
var init_traefik_route_health = __esm({
|
|
9836
|
+
"src/core/traefik-route-health.ts"() {
|
|
9837
|
+
"use strict";
|
|
9838
|
+
import_node_child_process20 = require("child_process");
|
|
9839
|
+
import_node_fs24 = __toESM(require("fs"));
|
|
9840
|
+
import_node_path25 = __toESM(require("path"));
|
|
9841
|
+
init_file_lock();
|
|
9842
|
+
init_host_routes();
|
|
9843
|
+
init_router();
|
|
9844
|
+
ROUTER_API_BASE = "http://127.0.0.1:8080/api";
|
|
9845
|
+
ROUTER_RELOAD_LOCK_FILE = import_node_path25.default.join(DEVROUTER_HOME, "router-reload.lock");
|
|
9846
|
+
DEFAULT_INITIAL_TIMEOUT_MS = 3e3;
|
|
9847
|
+
DEFAULT_RECOVERY_TIMEOUT_MS = 1e4;
|
|
9848
|
+
DEFAULT_POLL_INTERVAL_MS = 250;
|
|
9849
|
+
}
|
|
9850
|
+
});
|
|
9851
|
+
|
|
9059
9852
|
// src/core/workspace-ensure.ts
|
|
9060
9853
|
function assertOverlay(container, repoPath) {
|
|
9061
9854
|
const workingDir = container.labels["com.docker.compose.project.working_dir"];
|
|
9062
|
-
if (!workingDir || !sameWorkspacePath(workingDir,
|
|
9855
|
+
if (!workingDir || !sameWorkspacePath(workingDir, import_node_path26.default.join(repoPath, ".devcontainer"))) {
|
|
9063
9856
|
throw new Error(`Container '${container.id}' does not belong to the exact worktree.`);
|
|
9064
9857
|
}
|
|
9065
9858
|
const configFiles = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").filter(Boolean);
|
|
9066
|
-
const expectedOverlay =
|
|
9859
|
+
const expectedOverlay = import_node_path26.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
|
|
9067
9860
|
if (!configFiles.some((configFile) => sameWorkspacePath(configFile, expectedOverlay))) {
|
|
9068
9861
|
throw new Error(`Container '${container.id}' was not started with ${DEVCONTAINER_OVERLAY}.`);
|
|
9069
9862
|
}
|
|
@@ -9137,7 +9930,7 @@ async function waitForManagedServices(plan, repoPath, composeProject, expectedSe
|
|
|
9137
9930
|
} catch (error) {
|
|
9138
9931
|
lastError = error;
|
|
9139
9932
|
}
|
|
9140
|
-
if (Date.now() < deadline) await
|
|
9933
|
+
if (Date.now() < deadline) await sleep2(POLL_INTERVAL_MS);
|
|
9141
9934
|
} while (Date.now() < deadline);
|
|
9142
9935
|
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
9143
9936
|
}
|
|
@@ -9239,6 +10032,20 @@ function isWarmWorkspaceActive(repoPath) {
|
|
|
9239
10032
|
return true;
|
|
9240
10033
|
}
|
|
9241
10034
|
}
|
|
10035
|
+
function hasExactManagedComposeProject(repoPath, state) {
|
|
10036
|
+
try {
|
|
10037
|
+
return inspectWorkspaceContainers({
|
|
10038
|
+
composeProject: state.composeProject
|
|
10039
|
+
}).some((container) => {
|
|
10040
|
+
const workingDir = container.labels["com.docker.compose.project.working_dir"];
|
|
10041
|
+
return Boolean(
|
|
10042
|
+
workingDir && sameWorkspacePath(workingDir, import_node_path26.default.join(repoPath, ".devcontainer"))
|
|
10043
|
+
);
|
|
10044
|
+
});
|
|
10045
|
+
} catch {
|
|
10046
|
+
return true;
|
|
10047
|
+
}
|
|
10048
|
+
}
|
|
9242
10049
|
function captureFirstTransitionBaseline(options) {
|
|
9243
10050
|
const containers = inspectWorkspaceContainers();
|
|
9244
10051
|
const runningPrimary = workspaceAppContainers(containers, options.repoPath).filter(
|
|
@@ -9326,7 +10133,7 @@ function validateWorkspaceContainers(containers, options) {
|
|
|
9326
10133
|
}
|
|
9327
10134
|
return { id: appContainer.id, workspacePath: repoMount.Destination };
|
|
9328
10135
|
}
|
|
9329
|
-
function
|
|
10136
|
+
function sleep2(ms) {
|
|
9330
10137
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9331
10138
|
}
|
|
9332
10139
|
async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeoutMs) {
|
|
@@ -9340,7 +10147,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
9340
10147
|
target
|
|
9341
10148
|
});
|
|
9342
10149
|
if (target.kind === "linked") {
|
|
9343
|
-
const workspaceEnv = (0,
|
|
10150
|
+
const workspaceEnv = (0, import_node_child_process21.spawnSync)(
|
|
9344
10151
|
"docker",
|
|
9345
10152
|
["exec", appContainer.id, "printenv", "WORKSPACE"],
|
|
9346
10153
|
{ encoding: "utf-8" }
|
|
@@ -9350,7 +10157,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
9350
10157
|
`Workspace app container must expose WORKSPACE='${target.workspace}' (got '${workspaceEnv.stdout.trim() || "(empty)"}').`
|
|
9351
10158
|
);
|
|
9352
10159
|
}
|
|
9353
|
-
const devrouterWorkspaceEnv = (0,
|
|
10160
|
+
const devrouterWorkspaceEnv = (0, import_node_child_process21.spawnSync)(
|
|
9354
10161
|
"docker",
|
|
9355
10162
|
["exec", appContainer.id, "printenv", "DEVROUTER_WORKSPACE"],
|
|
9356
10163
|
{ encoding: "utf-8" }
|
|
@@ -9361,7 +10168,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
9361
10168
|
);
|
|
9362
10169
|
}
|
|
9363
10170
|
}
|
|
9364
|
-
const gitCheck = (0,
|
|
10171
|
+
const gitCheck = (0, import_node_child_process21.spawnSync)(
|
|
9365
10172
|
"docker",
|
|
9366
10173
|
[
|
|
9367
10174
|
"exec",
|
|
@@ -9382,7 +10189,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
9382
10189
|
lastError = error;
|
|
9383
10190
|
}
|
|
9384
10191
|
if (Date.now() < deadline) {
|
|
9385
|
-
await
|
|
10192
|
+
await sleep2(POLL_INTERVAL_MS);
|
|
9386
10193
|
}
|
|
9387
10194
|
} while (Date.now() < deadline);
|
|
9388
10195
|
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
@@ -9407,7 +10214,7 @@ async function waitForHttpRoutes(repoPath, apps, timeoutMs) {
|
|
|
9407
10214
|
return;
|
|
9408
10215
|
}
|
|
9409
10216
|
if (Date.now() < deadline) {
|
|
9410
|
-
await
|
|
10217
|
+
await sleep2(POLL_INTERVAL_MS);
|
|
9411
10218
|
}
|
|
9412
10219
|
} while (Date.now() < deadline);
|
|
9413
10220
|
throw new Error(
|
|
@@ -9453,14 +10260,14 @@ function resolvePrimaryTarget(repoPath) {
|
|
|
9453
10260
|
}
|
|
9454
10261
|
function isPrimaryCheckout(repoPath) {
|
|
9455
10262
|
try {
|
|
9456
|
-
return
|
|
10263
|
+
return import_node_fs25.default.statSync(import_node_path26.default.join(repoPath, ".git")).isDirectory();
|
|
9457
10264
|
} catch {
|
|
9458
10265
|
return false;
|
|
9459
10266
|
}
|
|
9460
10267
|
}
|
|
9461
10268
|
function openUrls(urls) {
|
|
9462
10269
|
for (const url of urls) {
|
|
9463
|
-
const opened = (0,
|
|
10270
|
+
const opened = (0, import_node_child_process21.spawnSync)("open", [url], { encoding: "utf-8" });
|
|
9464
10271
|
if (opened.status !== 0) {
|
|
9465
10272
|
process.stderr.write(`Warning: could not open '${url}'.
|
|
9466
10273
|
`);
|
|
@@ -9499,12 +10306,16 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
9499
10306
|
let managedConfigWritten = false;
|
|
9500
10307
|
let managedWorkspaceEnv;
|
|
9501
10308
|
let firstTransitionBaseline;
|
|
10309
|
+
const routeLoadOptions = {
|
|
10310
|
+
initialTimeoutMs: Math.min(options.httpTimeoutMs ?? DEFAULT_READINESS_TIMEOUT_MS, 3e3),
|
|
10311
|
+
recoveryTimeoutMs: Math.min(options.httpTimeoutMs ?? DEFAULT_READINESS_TIMEOUT_MS, 1e4)
|
|
10312
|
+
};
|
|
9502
10313
|
try {
|
|
9503
10314
|
const target = linked ? resolveLinkedTarget(repoPath) : resolvePrimaryTarget(repoPath);
|
|
9504
10315
|
let devpodId = target.devpodId;
|
|
9505
10316
|
if (target.kind === "linked") {
|
|
9506
|
-
const overlayPath =
|
|
9507
|
-
if (!
|
|
10317
|
+
const overlayPath = import_node_path26.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
|
|
10318
|
+
if (!import_node_fs25.default.existsSync(overlayPath)) {
|
|
9508
10319
|
throw new Error(`Missing required DevPod compose overlay: ${overlayPath}`);
|
|
9509
10320
|
}
|
|
9510
10321
|
}
|
|
@@ -9521,6 +10332,9 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
9521
10332
|
const desiredProcesses = managedRuntime ? desiredManagedProcesses(managedRuntime, runtime.resolvedProfile) : [];
|
|
9522
10333
|
if (managedRuntime) {
|
|
9523
10334
|
previousManagedState = readManagedRuntimeState(repoPath, target.workspace);
|
|
10335
|
+
if (previousManagedState && !hasExactManagedComposeProject(repoPath, previousManagedState)) {
|
|
10336
|
+
previousManagedState = void 0;
|
|
10337
|
+
}
|
|
9524
10338
|
if (previousManagedState?.status === "degraded") {
|
|
9525
10339
|
throw new Error(
|
|
9526
10340
|
"Managed runtime state is degraded; refusing a new profile transition until drift is repaired."
|
|
@@ -9571,7 +10385,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
9571
10385
|
}
|
|
9572
10386
|
const apps = proxyAppsFromConfig(runtime.config);
|
|
9573
10387
|
const parsedUpstreams = apps.map((app) => parseUpstream(app.upstream));
|
|
9574
|
-
const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ??
|
|
10388
|
+
const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ?? import_node_path26.default.basename(repoPath)) ?? "app";
|
|
9575
10389
|
for (const [index, app] of apps.entries()) {
|
|
9576
10390
|
if (!parsedUpstreams[index].host.startsWith(`${aliasPrefix}-`)) {
|
|
9577
10391
|
const owner = target.kind === "linked" ? "workspace" : "checkout";
|
|
@@ -9586,10 +10400,10 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
9586
10400
|
}
|
|
9587
10401
|
const upstreamHosts = parsedUpstreams.map((upstream) => upstream.host);
|
|
9588
10402
|
const currentTarget = () => target.kind === "linked" ? target : { ...target, devpodId };
|
|
9589
|
-
const startAndProveAttachment = (recreate = false) => {
|
|
10403
|
+
const startAndProveAttachment = async (recreate = false) => {
|
|
9590
10404
|
const requestedTarget = currentTarget();
|
|
9591
10405
|
try {
|
|
9592
|
-
devpodId = startDevpodWorkspace({
|
|
10406
|
+
devpodId = await startDevpodWorkspace({
|
|
9593
10407
|
repoPath,
|
|
9594
10408
|
devpodId: requestedTarget.devpodId,
|
|
9595
10409
|
devcontainerPath: managedPlan?.generatedRelativePath,
|
|
@@ -9610,13 +10424,13 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
9610
10424
|
};
|
|
9611
10425
|
const preflight = (timeoutMs) => waitForContainerPreflight(repoPath, currentTarget(), upstreamHosts, timeoutMs);
|
|
9612
10426
|
const recreateAndPreflight = async () => {
|
|
9613
|
-
startAndProveAttachment(true);
|
|
10427
|
+
await startAndProveAttachment(true);
|
|
9614
10428
|
return preflight(options.containerTimeoutMs ?? DEFAULT_READINESS_TIMEOUT_MS);
|
|
9615
10429
|
};
|
|
9616
10430
|
let recreated = false;
|
|
9617
10431
|
let container;
|
|
9618
10432
|
try {
|
|
9619
|
-
startAndProveAttachment();
|
|
10433
|
+
await startAndProveAttachment();
|
|
9620
10434
|
} catch (error) {
|
|
9621
10435
|
if (managedPlan || !target.hadExactDevpod) {
|
|
9622
10436
|
throw error;
|
|
@@ -9735,6 +10549,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
9735
10549
|
target.workspace
|
|
9736
10550
|
);
|
|
9737
10551
|
candidateRoutesPublished = true;
|
|
10552
|
+
await ensureTraefikRoutesLoaded(publication.routes, routeLoadOptions);
|
|
9738
10553
|
try {
|
|
9739
10554
|
await waitForHttpRoutes(
|
|
9740
10555
|
repoPath,
|
|
@@ -9758,6 +10573,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
9758
10573
|
});
|
|
9759
10574
|
recreated = true;
|
|
9760
10575
|
replaceHostRoutesForRepo(repoPath, publication.routes);
|
|
10576
|
+
await ensureTraefikRoutesLoaded(publication.routes, routeLoadOptions);
|
|
9761
10577
|
await waitForHttpRoutes(
|
|
9762
10578
|
repoPath,
|
|
9763
10579
|
apps,
|
|
@@ -9931,7 +10747,9 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
9931
10747
|
}
|
|
9932
10748
|
if (candidateRoutesPublished) {
|
|
9933
10749
|
try {
|
|
9934
|
-
|
|
10750
|
+
const rollbackRoutes = previousRoutes.map(routeInputFromState);
|
|
10751
|
+
replaceHostRoutesForRepo(repoPath, rollbackRoutes);
|
|
10752
|
+
await ensureTraefikRoutesLoaded(rollbackRoutes, routeLoadOptions);
|
|
9935
10753
|
} catch (rollbackError) {
|
|
9936
10754
|
rollbackErrors.push(
|
|
9937
10755
|
`routes: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`
|
|
@@ -9974,7 +10792,9 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
9974
10792
|
}
|
|
9975
10793
|
if (candidateRoutesPublished) {
|
|
9976
10794
|
try {
|
|
9977
|
-
|
|
10795
|
+
const rollbackRoutes = previousRoutes.map(routeInputFromState);
|
|
10796
|
+
replaceHostRoutesForRepo(repoPath, rollbackRoutes);
|
|
10797
|
+
await ensureTraefikRoutesLoaded(rollbackRoutes, routeLoadOptions);
|
|
9978
10798
|
} catch (rollbackError) {
|
|
9979
10799
|
rollbackErrors.push(
|
|
9980
10800
|
`routes: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`
|
|
@@ -10003,13 +10823,13 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
10003
10823
|
}
|
|
10004
10824
|
});
|
|
10005
10825
|
}
|
|
10006
|
-
var
|
|
10826
|
+
var import_node_child_process21, import_node_fs25, import_node_path26, DEVCONTAINER_OVERLAY, DEFAULT_READINESS_TIMEOUT_MS, POLL_INTERVAL_MS;
|
|
10007
10827
|
var init_workspace_ensure = __esm({
|
|
10008
10828
|
"src/core/workspace-ensure.ts"() {
|
|
10009
10829
|
"use strict";
|
|
10010
|
-
|
|
10011
|
-
|
|
10012
|
-
|
|
10830
|
+
import_node_child_process21 = require("child_process");
|
|
10831
|
+
import_node_fs25 = __toESM(require("fs"));
|
|
10832
|
+
import_node_path26 = __toESM(require("path"));
|
|
10013
10833
|
init_devcontainer_profile();
|
|
10014
10834
|
init_devpod_environment();
|
|
10015
10835
|
init_devpod_mutation();
|
|
@@ -10022,6 +10842,7 @@ var init_workspace_ensure = __esm({
|
|
|
10022
10842
|
init_repo_config();
|
|
10023
10843
|
init_route_publication();
|
|
10024
10844
|
init_router();
|
|
10845
|
+
init_traefik_route_health();
|
|
10025
10846
|
init_workspace();
|
|
10026
10847
|
init_workspace_ownership();
|
|
10027
10848
|
init_workspace_runtime();
|
|
@@ -10095,20 +10916,20 @@ function warnMissingWorkspaceOwnership(repoPath) {
|
|
|
10095
10916
|
);
|
|
10096
10917
|
}
|
|
10097
10918
|
function defaultWorktreePath(mainRepo, ws) {
|
|
10098
|
-
return
|
|
10919
|
+
return import_node_path27.default.join(mainRepo, "trees", ws);
|
|
10099
10920
|
}
|
|
10100
10921
|
function assertDefaultWorktreeRootIgnored(mainRepo) {
|
|
10101
|
-
const ignored = (0,
|
|
10922
|
+
const ignored = (0, import_node_child_process22.spawnSync)("git", ["-C", mainRepo, "check-ignore", "-q", "--no-index", "trees/"], {
|
|
10102
10923
|
encoding: "utf-8"
|
|
10103
10924
|
});
|
|
10104
10925
|
if (ignored.status !== 0) {
|
|
10105
10926
|
throw new Error(
|
|
10106
|
-
`Default worktree root '${
|
|
10927
|
+
`Default worktree root '${import_node_path27.default.join(mainRepo, "trees")}' is not ignored. Add 'trees/' to '${import_node_path27.default.join(mainRepo, ".gitignore")}' or use --path.`
|
|
10107
10928
|
);
|
|
10108
10929
|
}
|
|
10109
10930
|
}
|
|
10110
10931
|
function legacyDefaultWorktreePath(mainRepo, ws) {
|
|
10111
|
-
return
|
|
10932
|
+
return import_node_path27.default.join(import_node_path27.default.dirname(mainRepo), `${import_node_path27.default.basename(mainRepo)}-${ws}`);
|
|
10112
10933
|
}
|
|
10113
10934
|
function teardownFallbackPath(mainRepo, workspace) {
|
|
10114
10935
|
const candidates = [
|
|
@@ -10220,13 +11041,13 @@ function assertFullDownPreflight(mainRepo, target) {
|
|
|
10220
11041
|
if (sameWorkspacePath(target.worktreePath, mainRepo)) {
|
|
10221
11042
|
throw new Error("Refusing to remove the primary Git checkout.");
|
|
10222
11043
|
}
|
|
10223
|
-
if (!target.worktree || target.worktree.prunable || !
|
|
11044
|
+
if (!target.worktree || target.worktree.prunable || !import_node_fs26.default.existsSync(target.worktreePath)) return;
|
|
10224
11045
|
if (target.worktree.locked) {
|
|
10225
11046
|
throw new Error(
|
|
10226
11047
|
`Worktree '${target.worktreePath}' is locked; unlock it before workspace down.`
|
|
10227
11048
|
);
|
|
10228
11049
|
}
|
|
10229
|
-
const status = (0,
|
|
11050
|
+
const status = (0, import_node_child_process22.spawnSync)(
|
|
10230
11051
|
"git",
|
|
10231
11052
|
["-C", target.worktreePath, "status", "--porcelain", "--untracked-files=normal"],
|
|
10232
11053
|
{ encoding: "utf-8" }
|
|
@@ -10260,9 +11081,9 @@ async function workspaceUp(branch, opts = {}) {
|
|
|
10260
11081
|
(candidate) => defaultWorktreePath(mainRepo, candidate)
|
|
10261
11082
|
);
|
|
10262
11083
|
const generatedPath = candidatePaths.find(
|
|
10263
|
-
(candidate) => !
|
|
11084
|
+
(candidate) => !import_node_fs26.default.existsSync(candidate) && !worktrees.some((worktree) => sameWorkspacePath(worktree.path, candidate))
|
|
10264
11085
|
);
|
|
10265
|
-
const selectedPath = opts.path ?
|
|
11086
|
+
const selectedPath = opts.path ? import_node_path27.default.resolve(opts.path) : existingBranch?.path ?? generatedPath;
|
|
10266
11087
|
if (!selectedPath) {
|
|
10267
11088
|
throw new Error(`Could not allocate a collision-safe worktree path for branch '${branch}'.`);
|
|
10268
11089
|
}
|
|
@@ -10271,7 +11092,7 @@ async function workspaceUp(branch, opts = {}) {
|
|
|
10271
11092
|
`Branch '${branch}' already uses worktree '${existingBranch.path}', not '${selectedPath}'.`
|
|
10272
11093
|
);
|
|
10273
11094
|
}
|
|
10274
|
-
if (
|
|
11095
|
+
if (import_node_fs26.default.existsSync(selectedPath)) {
|
|
10275
11096
|
const registered = worktrees.find(
|
|
10276
11097
|
(worktree) => sameWorkspacePath(worktree.path, selectedPath)
|
|
10277
11098
|
);
|
|
@@ -10290,11 +11111,11 @@ async function workspaceUp(branch, opts = {}) {
|
|
|
10290
11111
|
return selectedPath;
|
|
10291
11112
|
}
|
|
10292
11113
|
if (!opts.path) assertDefaultWorktreeRootIgnored(mainRepo);
|
|
10293
|
-
const add = (0,
|
|
11114
|
+
const add = (0, import_node_child_process22.spawnSync)("git", ["-C", mainRepo, "worktree", "add", selectedPath, branch], {
|
|
10294
11115
|
encoding: "utf-8"
|
|
10295
11116
|
});
|
|
10296
11117
|
if (add.status !== 0) {
|
|
10297
|
-
const addNew = (0,
|
|
11118
|
+
const addNew = (0, import_node_child_process22.spawnSync)(
|
|
10298
11119
|
"git",
|
|
10299
11120
|
["-C", mainRepo, "worktree", "add", "-b", branch, selectedPath],
|
|
10300
11121
|
{ encoding: "utf-8" }
|
|
@@ -10396,8 +11217,8 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
|
|
|
10396
11217
|
opts.quiet
|
|
10397
11218
|
);
|
|
10398
11219
|
if (removeWorktree) {
|
|
10399
|
-
if (resolved.worktree && !resolved.worktree.prunable &&
|
|
10400
|
-
const rm = (0,
|
|
11220
|
+
if (resolved.worktree && !resolved.worktree.prunable && import_node_fs26.default.existsSync(resolved.worktreePath)) {
|
|
11221
|
+
const rm = (0, import_node_child_process22.spawnSync)("git", ["-C", mainRepo, "worktree", "remove", resolved.worktreePath], {
|
|
10401
11222
|
encoding: "utf-8"
|
|
10402
11223
|
});
|
|
10403
11224
|
if (rm.status !== 0) {
|
|
@@ -10415,7 +11236,7 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
|
|
|
10415
11236
|
}
|
|
10416
11237
|
return result;
|
|
10417
11238
|
};
|
|
10418
|
-
return resolved.worktree && !resolved.worktree.prunable &&
|
|
11239
|
+
return resolved.worktree && !resolved.worktree.prunable && import_node_fs26.default.existsSync(resolved.worktreePath) ? withWorkspaceLifecycleLock(resolved.worktreePath, operation) : operation();
|
|
10419
11240
|
}
|
|
10420
11241
|
async function mutateWorkspaceOwnedPath(action2, worktreePath, opts = {}) {
|
|
10421
11242
|
const mainRepo = resolveRepoPath(opts.repoPath);
|
|
@@ -10450,13 +11271,13 @@ async function workspaceStop(target, opts = {}) {
|
|
|
10450
11271
|
async function workspaceDown(target, opts = {}) {
|
|
10451
11272
|
return runWorkspaceLifecycle("down", target, opts);
|
|
10452
11273
|
}
|
|
10453
|
-
var
|
|
11274
|
+
var import_node_child_process22, import_node_fs26, import_node_path27, WORKSPACE_ALLOCATION_LOCK_WAIT_MS;
|
|
10454
11275
|
var init_workspace_lifecycle = __esm({
|
|
10455
11276
|
"src/core/workspace-lifecycle.ts"() {
|
|
10456
11277
|
"use strict";
|
|
10457
|
-
|
|
10458
|
-
|
|
10459
|
-
|
|
11278
|
+
import_node_child_process22 = require("child_process");
|
|
11279
|
+
import_node_fs26 = __toESM(require("fs"));
|
|
11280
|
+
import_node_path27 = __toESM(require("path"));
|
|
10460
11281
|
init_devpod_mutation();
|
|
10461
11282
|
init_devpod_workspaces();
|
|
10462
11283
|
init_repo_config();
|
|
@@ -10585,17 +11406,17 @@ async function devsyExec(repoPath, command) {
|
|
|
10585
11406
|
}
|
|
10586
11407
|
const args = ["workspace", "exec", "--result-format", "plain", workspace.id, "--", ...command];
|
|
10587
11408
|
return new Promise((resolve, reject) => {
|
|
10588
|
-
const child = (0,
|
|
11409
|
+
const child = (0, import_node_child_process23.spawn)("devsy", args, { stdio: "inherit" });
|
|
10589
11410
|
child.once("error", (error) => reject(new Error(`devsy exec failed: ${error.message}`)));
|
|
10590
11411
|
child.once("close", (code) => resolve(code ?? 1));
|
|
10591
11412
|
});
|
|
10592
11413
|
});
|
|
10593
11414
|
}
|
|
10594
|
-
var
|
|
11415
|
+
var import_node_child_process23;
|
|
10595
11416
|
var init_devsy_exec = __esm({
|
|
10596
11417
|
"src/core/devsy-exec.ts"() {
|
|
10597
11418
|
"use strict";
|
|
10598
|
-
|
|
11419
|
+
import_node_child_process23 = require("child_process");
|
|
10599
11420
|
init_devsy_workspaces();
|
|
10600
11421
|
init_workspace();
|
|
10601
11422
|
}
|
|
@@ -10617,7 +11438,7 @@ function ensureGuidance2(repoPath) {
|
|
|
10617
11438
|
return `Run 'devrouter ensure ${repoPath}' first.`;
|
|
10618
11439
|
}
|
|
10619
11440
|
function assertRunningDevpod(devpodId, repoPath) {
|
|
10620
|
-
const result = (0,
|
|
11441
|
+
const result = (0, import_node_child_process24.spawnSync)("devpod", ["status", devpodId, "--output", "json"], {
|
|
10621
11442
|
encoding: "utf-8"
|
|
10622
11443
|
});
|
|
10623
11444
|
if (result.status !== 0) {
|
|
@@ -10658,7 +11479,7 @@ async function devpodExec(repoPath, command) {
|
|
|
10658
11479
|
}
|
|
10659
11480
|
assertRunningDevpod(devpod.id, repoPath);
|
|
10660
11481
|
const workspaceDirectory = resolveWorkspaceDirectory(repoPath);
|
|
10661
|
-
const statusMarker = `__DEVROUTER_EXIT_${(0,
|
|
11482
|
+
const statusMarker = `__DEVROUTER_EXIT_${(0, import_node_crypto9.randomUUID)()}__:`;
|
|
10662
11483
|
const statusMarkerBytes = Buffer.from(statusMarker, "ascii");
|
|
10663
11484
|
const literalCommand = command.map(quotePosixArg).join(" ");
|
|
10664
11485
|
const wrappedCommand = `${literalCommand}; __devrouter_status=$?; printf '${statusMarker}%s\\n' "$__devrouter_status" >&2; exit 0`;
|
|
@@ -10676,7 +11497,7 @@ async function devpodExec(repoPath, command) {
|
|
|
10676
11497
|
wrappedCommand
|
|
10677
11498
|
];
|
|
10678
11499
|
return new Promise((resolve, reject) => {
|
|
10679
|
-
const child = (0,
|
|
11500
|
+
const child = (0, import_node_child_process24.spawn)("devpod", args, { stdio: ["inherit", "inherit", "pipe"] });
|
|
10680
11501
|
let pending = Buffer.alloc(0);
|
|
10681
11502
|
let statusBytes = Buffer.alloc(0);
|
|
10682
11503
|
let readingStatus = false;
|
|
@@ -10735,12 +11556,12 @@ async function devpodExec(repoPath, command) {
|
|
|
10735
11556
|
});
|
|
10736
11557
|
});
|
|
10737
11558
|
}
|
|
10738
|
-
var
|
|
11559
|
+
var import_node_child_process24, import_node_crypto9, DEVPOD_MISSING_EXIT_STATUS_DIAGNOSTIC;
|
|
10739
11560
|
var init_devpod_exec = __esm({
|
|
10740
11561
|
"src/core/devpod-exec.ts"() {
|
|
10741
11562
|
"use strict";
|
|
10742
|
-
|
|
10743
|
-
|
|
11563
|
+
import_node_child_process24 = require("child_process");
|
|
11564
|
+
import_node_crypto9 = require("crypto");
|
|
10744
11565
|
init_devpod_environment();
|
|
10745
11566
|
init_devpod_workspaces();
|
|
10746
11567
|
init_devsy_exec();
|
|
@@ -10926,7 +11747,7 @@ async function runOpenCommand(name) {
|
|
|
10926
11747
|
);
|
|
10927
11748
|
return;
|
|
10928
11749
|
}
|
|
10929
|
-
const result = (0,
|
|
11750
|
+
const result = (0, import_node_child_process25.spawnSync)("open", [url], { encoding: "utf-8" });
|
|
10930
11751
|
if (result.status !== 0) {
|
|
10931
11752
|
const details = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
10932
11753
|
throw new Error(`Unable to open '${url}': ${details || "unknown error"}`);
|
|
@@ -10934,11 +11755,11 @@ async function runOpenCommand(name) {
|
|
|
10934
11755
|
process.stdout.write(`Opened ${url}
|
|
10935
11756
|
`);
|
|
10936
11757
|
}
|
|
10937
|
-
var
|
|
11758
|
+
var import_node_child_process25;
|
|
10938
11759
|
var init_open = __esm({
|
|
10939
11760
|
"src/commands/open.ts"() {
|
|
10940
11761
|
"use strict";
|
|
10941
|
-
|
|
11762
|
+
import_node_child_process25 = require("child_process");
|
|
10942
11763
|
init_docker();
|
|
10943
11764
|
init_host_routes();
|
|
10944
11765
|
init_repo_config();
|
|
@@ -10961,7 +11782,7 @@ async function runLogsCommand(options) {
|
|
|
10961
11782
|
const args = ["logs", "--tail", tail, ROUTER_CONTAINER_NAME];
|
|
10962
11783
|
if (options.follow) {
|
|
10963
11784
|
args.splice(1, 0, "-f");
|
|
10964
|
-
const child = (0,
|
|
11785
|
+
const child = (0, import_node_child_process26.spawn)("docker", args, { stdio: "inherit" });
|
|
10965
11786
|
const onSignal = () => {
|
|
10966
11787
|
child.kill("SIGTERM");
|
|
10967
11788
|
};
|
|
@@ -10975,17 +11796,17 @@ async function runLogsCommand(options) {
|
|
|
10975
11796
|
});
|
|
10976
11797
|
});
|
|
10977
11798
|
} else {
|
|
10978
|
-
const result = (0,
|
|
11799
|
+
const result = (0, import_node_child_process26.spawnSync)("docker", args, { stdio: "inherit" });
|
|
10979
11800
|
if (result.status !== 0) {
|
|
10980
11801
|
throw new Error("Failed to retrieve router logs.");
|
|
10981
11802
|
}
|
|
10982
11803
|
}
|
|
10983
11804
|
}
|
|
10984
|
-
var
|
|
11805
|
+
var import_node_child_process26;
|
|
10985
11806
|
var init_logs = __esm({
|
|
10986
11807
|
"src/commands/logs.ts"() {
|
|
10987
11808
|
"use strict";
|
|
10988
|
-
|
|
11809
|
+
import_node_child_process26 = require("child_process");
|
|
10989
11810
|
init_docker();
|
|
10990
11811
|
init_router();
|
|
10991
11812
|
}
|
|
@@ -11025,17 +11846,17 @@ function asRecord3(value) {
|
|
|
11025
11846
|
return value;
|
|
11026
11847
|
}
|
|
11027
11848
|
function readJson(filePath) {
|
|
11028
|
-
if (!
|
|
11849
|
+
if (!import_node_fs27.default.existsSync(filePath)) {
|
|
11029
11850
|
return void 0;
|
|
11030
11851
|
}
|
|
11031
11852
|
try {
|
|
11032
|
-
return JSON.parse(
|
|
11853
|
+
return JSON.parse(import_node_fs27.default.readFileSync(filePath, "utf-8"));
|
|
11033
11854
|
} catch {
|
|
11034
11855
|
return void 0;
|
|
11035
11856
|
}
|
|
11036
11857
|
}
|
|
11037
11858
|
function relative(repoPath, filePath) {
|
|
11038
|
-
return
|
|
11859
|
+
return import_node_path28.default.relative(repoPath, filePath) || ".";
|
|
11039
11860
|
}
|
|
11040
11861
|
function redactEnvAssignments(value) {
|
|
11041
11862
|
return value.replace(
|
|
@@ -11076,7 +11897,7 @@ function inspectPackageManager(repoPath, pkg) {
|
|
|
11076
11897
|
["bun.lock", "bun"]
|
|
11077
11898
|
];
|
|
11078
11899
|
for (const [fileName, name] of lockfiles) {
|
|
11079
|
-
if (
|
|
11900
|
+
if (import_node_fs27.default.existsSync(import_node_path28.default.join(repoPath, fileName))) {
|
|
11080
11901
|
return { name, source: fileName };
|
|
11081
11902
|
}
|
|
11082
11903
|
}
|
|
@@ -11091,9 +11912,9 @@ function inspectNode(repoPath, pkg) {
|
|
|
11091
11912
|
if (typeof engines?.node === "string") {
|
|
11092
11913
|
return { version: engines.node, source: "package.json:engines.node" };
|
|
11093
11914
|
}
|
|
11094
|
-
const nvmrc =
|
|
11095
|
-
if (
|
|
11096
|
-
const version =
|
|
11915
|
+
const nvmrc = import_node_path28.default.join(repoPath, ".nvmrc");
|
|
11916
|
+
if (import_node_fs27.default.existsSync(nvmrc)) {
|
|
11917
|
+
const version = import_node_fs27.default.readFileSync(nvmrc, "utf-8").trim();
|
|
11097
11918
|
return { version, source: ".nvmrc" };
|
|
11098
11919
|
}
|
|
11099
11920
|
return void 0;
|
|
@@ -11154,7 +11975,7 @@ function configuredComposeFiles(repoPath) {
|
|
|
11154
11975
|
const files = config.apps.filter(
|
|
11155
11976
|
(app) => app.runtime === "docker"
|
|
11156
11977
|
).flatMap((app) => app.docker.composeFiles).filter(
|
|
11157
|
-
(fileName) => !
|
|
11978
|
+
(fileName) => !import_node_path28.default.isAbsolute(fileName) && !import_node_path28.default.normalize(fileName).startsWith("..")
|
|
11158
11979
|
);
|
|
11159
11980
|
return Array.from(new Set(files));
|
|
11160
11981
|
} catch {
|
|
@@ -11172,7 +11993,7 @@ function composeFiles(repoPath) {
|
|
|
11172
11993
|
...configuredComposeFiles(repoPath)
|
|
11173
11994
|
];
|
|
11174
11995
|
return Array.from(new Set(candidates)).filter(
|
|
11175
|
-
(fileName) =>
|
|
11996
|
+
(fileName) => import_node_fs27.default.existsSync(import_node_path28.default.join(repoPath, fileName))
|
|
11176
11997
|
);
|
|
11177
11998
|
}
|
|
11178
11999
|
function stringArray2(value) {
|
|
@@ -11210,7 +12031,7 @@ function inspectServices(repoPath) {
|
|
|
11210
12031
|
const services = [];
|
|
11211
12032
|
for (const fileName of composeFiles(repoPath)) {
|
|
11212
12033
|
try {
|
|
11213
|
-
const parsed = import_yaml6.default.parse(
|
|
12034
|
+
const parsed = import_yaml6.default.parse(import_node_fs27.default.readFileSync(import_node_path28.default.join(repoPath, fileName), "utf-8"));
|
|
11214
12035
|
const serviceMap = asRecord3(asRecord3(parsed)?.services);
|
|
11215
12036
|
for (const [name, value] of Object.entries(serviceMap ?? {})) {
|
|
11216
12037
|
const service = asRecord3(value);
|
|
@@ -11245,8 +12066,8 @@ function inspectServices(repoPath) {
|
|
|
11245
12066
|
return services;
|
|
11246
12067
|
}
|
|
11247
12068
|
function inspectEnvFiles(repoPath) {
|
|
11248
|
-
const files =
|
|
11249
|
-
const content =
|
|
12069
|
+
const files = import_node_fs27.default.readdirSync(repoPath).filter((fileName) => /^\.env(\.|$)/.test(fileName)).sort().map((fileName) => {
|
|
12070
|
+
const content = import_node_fs27.default.readFileSync(import_node_path28.default.join(repoPath, fileName), "utf-8");
|
|
11250
12071
|
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
12072
|
return { path: fileName, names };
|
|
11252
12073
|
});
|
|
@@ -11260,18 +12081,18 @@ function inspectEnvFiles(repoPath) {
|
|
|
11260
12081
|
};
|
|
11261
12082
|
}
|
|
11262
12083
|
function inspectDevcontainer(repoPath) {
|
|
11263
|
-
const dir =
|
|
11264
|
-
if (!
|
|
12084
|
+
const dir = import_node_path28.default.join(repoPath, ".devcontainer");
|
|
12085
|
+
if (!import_node_fs27.default.existsSync(dir)) {
|
|
11265
12086
|
return { exists: false, files: [] };
|
|
11266
12087
|
}
|
|
11267
12088
|
return {
|
|
11268
12089
|
exists: true,
|
|
11269
|
-
files:
|
|
12090
|
+
files: import_node_fs27.default.readdirSync(dir).filter((fileName) => import_node_fs27.default.statSync(import_node_path28.default.join(dir, fileName)).isFile()).sort().map((fileName) => `.devcontainer/${fileName}`)
|
|
11270
12091
|
};
|
|
11271
12092
|
}
|
|
11272
12093
|
function inspectDevrouter(repoPath) {
|
|
11273
12094
|
const configPath = getRepoConfigPath(repoPath);
|
|
11274
|
-
if (!
|
|
12095
|
+
if (!import_node_fs27.default.existsSync(configPath)) {
|
|
11275
12096
|
return {
|
|
11276
12097
|
exists: false,
|
|
11277
12098
|
configPath,
|
|
@@ -11315,15 +12136,15 @@ function inspectAgentGuidance(repoPath) {
|
|
|
11315
12136
|
["AGENTS.md", "agents"],
|
|
11316
12137
|
["CLAUDE.md", "claude"]
|
|
11317
12138
|
]) {
|
|
11318
|
-
if (
|
|
12139
|
+
if (import_node_fs27.default.existsSync(import_node_path28.default.join(repoPath, fileName))) {
|
|
11319
12140
|
results.push({ path: fileName, kind });
|
|
11320
12141
|
}
|
|
11321
12142
|
}
|
|
11322
|
-
const skillsDir =
|
|
11323
|
-
if (
|
|
11324
|
-
for (const name of
|
|
11325
|
-
const skillPath =
|
|
11326
|
-
if (
|
|
12143
|
+
const skillsDir = import_node_path28.default.join(repoPath, ".agents", "skills");
|
|
12144
|
+
if (import_node_fs27.default.existsSync(skillsDir)) {
|
|
12145
|
+
for (const name of import_node_fs27.default.readdirSync(skillsDir).sort()) {
|
|
12146
|
+
const skillPath = import_node_path28.default.join(skillsDir, name, "SKILL.md");
|
|
12147
|
+
if (import_node_fs27.default.existsSync(skillPath)) {
|
|
11327
12148
|
results.push({ path: relative(repoPath, skillPath), kind: "skill" });
|
|
11328
12149
|
}
|
|
11329
12150
|
}
|
|
@@ -11366,7 +12187,7 @@ function buildIssues(report) {
|
|
|
11366
12187
|
}
|
|
11367
12188
|
function inspectRepo(options = {}) {
|
|
11368
12189
|
const repoPath = resolveRepoPath(options.repo);
|
|
11369
|
-
const pkg = readJson(
|
|
12190
|
+
const pkg = readJson(import_node_path28.default.join(repoPath, "package.json"));
|
|
11370
12191
|
const scripts = inspectScripts(pkg);
|
|
11371
12192
|
const reportWithoutIssues = {
|
|
11372
12193
|
repoPath,
|
|
@@ -11385,12 +12206,12 @@ function inspectRepo(options = {}) {
|
|
|
11385
12206
|
issues: buildIssues(reportWithoutIssues)
|
|
11386
12207
|
};
|
|
11387
12208
|
}
|
|
11388
|
-
var
|
|
12209
|
+
var import_node_fs27, import_node_path28, import_yaml6;
|
|
11389
12210
|
var init_repo_inspect = __esm({
|
|
11390
12211
|
"src/core/repo-inspect.ts"() {
|
|
11391
12212
|
"use strict";
|
|
11392
|
-
|
|
11393
|
-
|
|
12213
|
+
import_node_fs27 = __toESM(require("fs"));
|
|
12214
|
+
import_node_path28 = __toESM(require("path"));
|
|
11394
12215
|
import_yaml6 = __toESM(require("yaml"));
|
|
11395
12216
|
init_repo_config();
|
|
11396
12217
|
}
|
|
@@ -11495,7 +12316,7 @@ function requiredFileChecks(repoPath) {
|
|
|
11495
12316
|
".devcontainer/docker-compose.yml",
|
|
11496
12317
|
".devrouter.yml"
|
|
11497
12318
|
];
|
|
11498
|
-
const missing = required.filter((fileName) => !
|
|
12319
|
+
const missing = required.filter((fileName) => !import_node_fs28.default.existsSync(import_node_path29.default.join(repoPath, fileName)));
|
|
11499
12320
|
return {
|
|
11500
12321
|
id: "repo.devcontainer.verify-files",
|
|
11501
12322
|
level: missing.length === 0 ? "ok" : "error",
|
|
@@ -11714,12 +12535,12 @@ async function verifyDevcontainer(options = {}) {
|
|
|
11714
12535
|
nextSteps: collectNextSteps3(checks)
|
|
11715
12536
|
};
|
|
11716
12537
|
}
|
|
11717
|
-
var
|
|
12538
|
+
var import_node_fs28, import_node_path29;
|
|
11718
12539
|
var init_devcontainer_verify = __esm({
|
|
11719
12540
|
"src/core/devcontainer-verify.ts"() {
|
|
11720
12541
|
"use strict";
|
|
11721
|
-
|
|
11722
|
-
|
|
12542
|
+
import_node_fs28 = __toESM(require("fs"));
|
|
12543
|
+
import_node_path29 = __toESM(require("path"));
|
|
11723
12544
|
init_capabilities();
|
|
11724
12545
|
init_doctor();
|
|
11725
12546
|
init_host_routes();
|
|
@@ -12016,7 +12837,7 @@ function packageManagerIssues(repo) {
|
|
|
12016
12837
|
}
|
|
12017
12838
|
function plannedFiles(repoPath, version) {
|
|
12018
12839
|
const repo = inspectRepo({ repo: repoPath });
|
|
12019
|
-
const projectName = sanitizeProjectName(
|
|
12840
|
+
const projectName = sanitizeProjectName(import_node_path30.default.basename(repo.repoPath));
|
|
12020
12841
|
const nodeMajor = majorVersion(repo.node?.version, "24");
|
|
12021
12842
|
const pnpmVersion = repo.packageManager?.name === "pnpm" && repo.packageManager.version ? repo.packageManager.version : DEFAULT_PNPM_VERSION;
|
|
12022
12843
|
const port = inferPort2(repo);
|
|
@@ -12061,8 +12882,8 @@ function plannedFiles(repoPath, version) {
|
|
|
12061
12882
|
};
|
|
12062
12883
|
}
|
|
12063
12884
|
function classifyFile(repoPath, file) {
|
|
12064
|
-
const absolutePath =
|
|
12065
|
-
if (!
|
|
12885
|
+
const absolutePath = import_node_path30.default.join(repoPath, file.relativePath);
|
|
12886
|
+
if (!import_node_fs29.default.existsSync(absolutePath)) {
|
|
12066
12887
|
return {
|
|
12067
12888
|
path: file.relativePath,
|
|
12068
12889
|
action: "create",
|
|
@@ -12070,7 +12891,7 @@ function classifyFile(repoPath, file) {
|
|
|
12070
12891
|
bytes: Buffer.byteLength(file.content)
|
|
12071
12892
|
};
|
|
12072
12893
|
}
|
|
12073
|
-
const current =
|
|
12894
|
+
const current = import_node_fs29.default.readFileSync(absolutePath, "utf-8");
|
|
12074
12895
|
if (!current.includes(MANAGED_MARKER2)) {
|
|
12075
12896
|
return {
|
|
12076
12897
|
path: file.relativePath,
|
|
@@ -12127,11 +12948,11 @@ function buildPlan(repoPath, dryRun, version) {
|
|
|
12127
12948
|
};
|
|
12128
12949
|
}
|
|
12129
12950
|
function writeFile(repoPath, file) {
|
|
12130
|
-
const absolutePath =
|
|
12131
|
-
|
|
12132
|
-
|
|
12951
|
+
const absolutePath = import_node_path30.default.join(repoPath, file.relativePath);
|
|
12952
|
+
import_node_fs29.default.mkdirSync(import_node_path30.default.dirname(absolutePath), { recursive: true });
|
|
12953
|
+
import_node_fs29.default.writeFileSync(absolutePath, file.content, "utf-8");
|
|
12133
12954
|
if (file.executable) {
|
|
12134
|
-
|
|
12955
|
+
import_node_fs29.default.chmodSync(absolutePath, 493);
|
|
12135
12956
|
}
|
|
12136
12957
|
}
|
|
12137
12958
|
function writeDevcontainer(options = {}) {
|
|
@@ -12169,12 +12990,12 @@ function writeDevcontainer(options = {}) {
|
|
|
12169
12990
|
nextSteps: postWriteNextSteps(repoPath)
|
|
12170
12991
|
};
|
|
12171
12992
|
}
|
|
12172
|
-
var
|
|
12993
|
+
var import_node_fs29, import_node_path30, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
|
|
12173
12994
|
var init_devcontainer_write = __esm({
|
|
12174
12995
|
"src/core/devcontainer-write.ts"() {
|
|
12175
12996
|
"use strict";
|
|
12176
|
-
|
|
12177
|
-
|
|
12997
|
+
import_node_fs29 = __toESM(require("fs"));
|
|
12998
|
+
import_node_path30 = __toESM(require("path"));
|
|
12178
12999
|
init_repo_config();
|
|
12179
13000
|
init_repo_inspect();
|
|
12180
13001
|
MANAGED_MARKER2 = "devrouter:managed devcontainer";
|
|
@@ -12283,6 +13104,119 @@ var init_repo_devcontainer = __esm({
|
|
|
12283
13104
|
}
|
|
12284
13105
|
});
|
|
12285
13106
|
|
|
13107
|
+
// src/core/profile-resolution.ts
|
|
13108
|
+
function sortedUnique2(values) {
|
|
13109
|
+
return Array.from(new Set(values)).sort();
|
|
13110
|
+
}
|
|
13111
|
+
function expandsAll(values) {
|
|
13112
|
+
return values?.length === 1 && values[0] === "*";
|
|
13113
|
+
}
|
|
13114
|
+
function selectedManagedResources(profile, selected, registry) {
|
|
13115
|
+
if (!profile || expandsAll(selected)) return sortedUnique2(registry);
|
|
13116
|
+
return sortedUnique2(selected ?? []);
|
|
13117
|
+
}
|
|
13118
|
+
function selectedReadiness(config, profileName, profile, routedApps) {
|
|
13119
|
+
const selectedNames = profileName.split(",");
|
|
13120
|
+
if (selectedNames.length === 1) {
|
|
13121
|
+
return sortedUnique2(
|
|
13122
|
+
profile?.readiness ?? routedApps.filter((app) => app.protocol === "http").map((app) => app.name)
|
|
13123
|
+
);
|
|
13124
|
+
}
|
|
13125
|
+
const readiness = /* @__PURE__ */ new Set();
|
|
13126
|
+
for (const selectedName of selectedNames) {
|
|
13127
|
+
const selected = resolveProfile(config, selectedName);
|
|
13128
|
+
const selectedApps = applyProfile(config, selected.profile).apps.filter(
|
|
13129
|
+
(app) => app.kind !== "dependency"
|
|
13130
|
+
);
|
|
13131
|
+
const selectedTargets = selected.profile?.readiness ?? selectedApps.filter((app) => app.protocol === "http").map((app) => app.name);
|
|
13132
|
+
for (const target of selectedTargets) readiness.add(target);
|
|
13133
|
+
}
|
|
13134
|
+
return sortedUnique2(readiness);
|
|
13135
|
+
}
|
|
13136
|
+
function buildProfileResolutionReport(config, repoPath, profileOverride) {
|
|
13137
|
+
const resolved = resolveProfile(config, profileOverride);
|
|
13138
|
+
const filtered = applyProfile(config, resolved.profile);
|
|
13139
|
+
const routedApps = filtered.apps.filter((app) => app.kind !== "dependency");
|
|
13140
|
+
const dependencies = filtered.apps.filter((app) => app.kind === "dependency");
|
|
13141
|
+
const readiness = selectedReadiness(config, resolved.name, resolved.profile, routedApps);
|
|
13142
|
+
const managedRuntime = config.managedRuntime;
|
|
13143
|
+
const baseServices = sortedUnique2(managedRuntime?.devcontainer.baseServices ?? []);
|
|
13144
|
+
const profileServices = managedRuntime ? selectedManagedResources(
|
|
13145
|
+
resolved.profile,
|
|
13146
|
+
resolved.profile?.devcontainerServices,
|
|
13147
|
+
managedRuntime.devcontainer.profileServices
|
|
13148
|
+
) : [];
|
|
13149
|
+
const processes = managedRuntime ? selectedManagedResources(
|
|
13150
|
+
resolved.profile,
|
|
13151
|
+
resolved.profile?.processes,
|
|
13152
|
+
managedRuntime.processes
|
|
13153
|
+
) : [];
|
|
13154
|
+
return {
|
|
13155
|
+
schemaVersion: 1,
|
|
13156
|
+
repoPath,
|
|
13157
|
+
profile: resolved.name,
|
|
13158
|
+
apps: sortedUnique2(routedApps.map((app) => app.name)),
|
|
13159
|
+
dependencies: sortedUnique2(dependencies.map((app) => app.name)),
|
|
13160
|
+
readiness: sortedUnique2(readiness),
|
|
13161
|
+
managedRuntime: {
|
|
13162
|
+
baseServices,
|
|
13163
|
+
profileServices,
|
|
13164
|
+
services: sortedUnique2([...baseServices, ...profileServices]),
|
|
13165
|
+
processes
|
|
13166
|
+
}
|
|
13167
|
+
};
|
|
13168
|
+
}
|
|
13169
|
+
function resolveProfileReport(options) {
|
|
13170
|
+
const repoPath = resolveRepoPath(options.repo);
|
|
13171
|
+
return buildProfileResolutionReport(loadRepoConfig(repoPath), repoPath, options.profile);
|
|
13172
|
+
}
|
|
13173
|
+
var init_profile_resolution = __esm({
|
|
13174
|
+
"src/core/profile-resolution.ts"() {
|
|
13175
|
+
"use strict";
|
|
13176
|
+
init_repo_config();
|
|
13177
|
+
}
|
|
13178
|
+
});
|
|
13179
|
+
|
|
13180
|
+
// src/commands/profile.ts
|
|
13181
|
+
var profile_exports = {};
|
|
13182
|
+
__export(profile_exports, {
|
|
13183
|
+
runProfileResolveCommand: () => runProfileResolveCommand
|
|
13184
|
+
});
|
|
13185
|
+
async function runProfileResolveCommand(options) {
|
|
13186
|
+
const report = resolveProfileReport(options);
|
|
13187
|
+
if (options.json) {
|
|
13188
|
+
printJSON(report);
|
|
13189
|
+
return;
|
|
13190
|
+
}
|
|
13191
|
+
printProfileResolutionSummary(report);
|
|
13192
|
+
}
|
|
13193
|
+
function renderValues(values) {
|
|
13194
|
+
return values.join(", ") || "-";
|
|
13195
|
+
}
|
|
13196
|
+
function printProfileResolutionSummary(report) {
|
|
13197
|
+
process.stdout.write(`Repo: ${report.repoPath}
|
|
13198
|
+
`);
|
|
13199
|
+
process.stdout.write(`Profile: ${report.profile}
|
|
13200
|
+
`);
|
|
13201
|
+
process.stdout.write(`Apps: ${renderValues(report.apps)}
|
|
13202
|
+
`);
|
|
13203
|
+
process.stdout.write(`Dependencies: ${renderValues(report.dependencies)}
|
|
13204
|
+
`);
|
|
13205
|
+
process.stdout.write(`Readiness: ${renderValues(report.readiness)}
|
|
13206
|
+
`);
|
|
13207
|
+
process.stdout.write(`Managed services: ${renderValues(report.managedRuntime.services)}
|
|
13208
|
+
`);
|
|
13209
|
+
process.stdout.write(`Managed processes: ${renderValues(report.managedRuntime.processes)}
|
|
13210
|
+
`);
|
|
13211
|
+
}
|
|
13212
|
+
var init_profile = __esm({
|
|
13213
|
+
"src/commands/profile.ts"() {
|
|
13214
|
+
"use strict";
|
|
13215
|
+
init_output();
|
|
13216
|
+
init_profile_resolution();
|
|
13217
|
+
}
|
|
13218
|
+
});
|
|
13219
|
+
|
|
12286
13220
|
// src/commands/app-add.ts
|
|
12287
13221
|
var app_add_exports = {};
|
|
12288
13222
|
__export(app_add_exports, {
|
|
@@ -12565,7 +13499,7 @@ function sanitizeRouterId(value) {
|
|
|
12565
13499
|
return value.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
12566
13500
|
}
|
|
12567
13501
|
function repoHash(repoPath) {
|
|
12568
|
-
return (0,
|
|
13502
|
+
return (0, import_node_crypto10.createHash)("sha1").update(import_node_path31.default.resolve(repoPath)).digest("hex").slice(0, 12);
|
|
12569
13503
|
}
|
|
12570
13504
|
function asDockerApp(app) {
|
|
12571
13505
|
return app.runtime === "docker";
|
|
@@ -12644,11 +13578,11 @@ function prepareDockerOverlay(repoPath, appName, apps, publishTcpPorts = false)
|
|
|
12644
13578
|
if (dockerApps.length === 0) {
|
|
12645
13579
|
throw new Error("No docker apps selected to prepare compose overlay.");
|
|
12646
13580
|
}
|
|
12647
|
-
const cachePath =
|
|
12648
|
-
|
|
12649
|
-
const overlayPath =
|
|
13581
|
+
const cachePath = import_node_path31.default.join(CACHE_DIR, repoHash(repoPath), sanitizeRouterId(appName));
|
|
13582
|
+
import_node_fs30.default.mkdirSync(cachePath, { recursive: true });
|
|
13583
|
+
const overlayPath = import_node_path31.default.join(cachePath, "compose.devrouter.yml");
|
|
12650
13584
|
const overlayDocument = buildOverlayDocument(dockerApps, publishTcpPorts);
|
|
12651
|
-
|
|
13585
|
+
import_node_fs30.default.writeFileSync(overlayPath, import_yaml7.default.stringify(overlayDocument, { lineWidth: 0 }), "utf-8");
|
|
12652
13586
|
return {
|
|
12653
13587
|
overlayPath,
|
|
12654
13588
|
composeFiles: ensureComposeFiles(dockerApps),
|
|
@@ -12662,7 +13596,7 @@ function runDockerComposeUp(repoPath, composeFiles2, overlayPath, services) {
|
|
|
12662
13596
|
fileArgs.push("-f", resolved);
|
|
12663
13597
|
}
|
|
12664
13598
|
const args = ["compose", ...fileArgs, "-f", overlayPath, "up", "-d", "--wait", ...services];
|
|
12665
|
-
const result = (0,
|
|
13599
|
+
const result = (0, import_node_child_process27.spawnSync)("docker", args, {
|
|
12666
13600
|
encoding: "utf-8",
|
|
12667
13601
|
cwd: repoPath
|
|
12668
13602
|
});
|
|
@@ -12690,7 +13624,7 @@ function queryRunningComposeServices(repoPath, composeFiles2, overlayPath, servi
|
|
|
12690
13624
|
"--services",
|
|
12691
13625
|
...services
|
|
12692
13626
|
];
|
|
12693
|
-
const result = (0,
|
|
13627
|
+
const result = (0, import_node_child_process27.spawnSync)("docker", args, {
|
|
12694
13628
|
encoding: "utf-8",
|
|
12695
13629
|
cwd: repoPath
|
|
12696
13630
|
});
|
|
@@ -12716,7 +13650,7 @@ function runDockerComposeStop(repoPath, composeFiles2, overlayPath, services) {
|
|
|
12716
13650
|
fileArgs.push("-f", resolved);
|
|
12717
13651
|
}
|
|
12718
13652
|
const args = ["compose", ...fileArgs, "-f", overlayPath, "stop", ...services];
|
|
12719
|
-
const result = (0,
|
|
13653
|
+
const result = (0, import_node_child_process27.spawnSync)("docker", args, {
|
|
12720
13654
|
encoding: "utf-8",
|
|
12721
13655
|
cwd: repoPath
|
|
12722
13656
|
});
|
|
@@ -12742,7 +13676,7 @@ function runDockerComposeLogs(repoPath, composeFiles2, overlayPath, services, ta
|
|
|
12742
13676
|
String(tail),
|
|
12743
13677
|
...services
|
|
12744
13678
|
];
|
|
12745
|
-
(0,
|
|
13679
|
+
(0, import_node_child_process27.spawnSync)("docker", args, {
|
|
12746
13680
|
stdio: "inherit",
|
|
12747
13681
|
cwd: repoPath
|
|
12748
13682
|
});
|
|
@@ -12754,7 +13688,7 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
|
|
|
12754
13688
|
fileArgs.push("-f", resolved);
|
|
12755
13689
|
}
|
|
12756
13690
|
const args = ["compose", ...fileArgs, "-f", overlayPath, "port", service, String(internalPort)];
|
|
12757
|
-
const result = (0,
|
|
13691
|
+
const result = (0, import_node_child_process27.spawnSync)("docker", args, {
|
|
12758
13692
|
encoding: "utf-8",
|
|
12759
13693
|
cwd: repoPath
|
|
12760
13694
|
});
|
|
@@ -12768,14 +13702,14 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
|
|
|
12768
13702
|
const port = Number(match[1]);
|
|
12769
13703
|
return Number.isInteger(port) && port > 0 ? port : void 0;
|
|
12770
13704
|
}
|
|
12771
|
-
var
|
|
13705
|
+
var import_node_child_process27, import_node_crypto10, import_node_fs30, import_node_path31, import_yaml7;
|
|
12772
13706
|
var init_docker_run = __esm({
|
|
12773
13707
|
"src/core/docker-run.ts"() {
|
|
12774
13708
|
"use strict";
|
|
12775
|
-
|
|
12776
|
-
|
|
12777
|
-
|
|
12778
|
-
|
|
13709
|
+
import_node_child_process27 = require("child_process");
|
|
13710
|
+
import_node_crypto10 = require("crypto");
|
|
13711
|
+
import_node_fs30 = __toESM(require("fs"));
|
|
13712
|
+
import_node_path31 = __toESM(require("path"));
|
|
12779
13713
|
import_yaml7 = __toESM(require("yaml"));
|
|
12780
13714
|
init_docker_error_guidance();
|
|
12781
13715
|
init_paths();
|
|
@@ -12812,7 +13746,7 @@ function resolveSmCommand(command, defaultEnv, overrideEnv) {
|
|
|
12812
13746
|
}
|
|
12813
13747
|
return command.replace(/\{env\}/g, env);
|
|
12814
13748
|
}
|
|
12815
|
-
function
|
|
13749
|
+
function sleep3(ms) {
|
|
12816
13750
|
return new Promise((resolve) => {
|
|
12817
13751
|
setTimeout(resolve, ms);
|
|
12818
13752
|
});
|
|
@@ -12865,7 +13799,7 @@ function isProcessRunning(pid) {
|
|
|
12865
13799
|
}
|
|
12866
13800
|
}
|
|
12867
13801
|
function readProcessTree(rootPid) {
|
|
12868
|
-
const result = (0,
|
|
13802
|
+
const result = (0, import_node_child_process28.spawnSync)("ps", ["-ax", "-o", "pid=,ppid="], { encoding: "utf-8" });
|
|
12869
13803
|
if (result.status !== 0) {
|
|
12870
13804
|
return [rootPid];
|
|
12871
13805
|
}
|
|
@@ -12923,7 +13857,7 @@ async function terminateProcessTree(rootPid) {
|
|
|
12923
13857
|
if (!isProcessRunning(rootPid)) {
|
|
12924
13858
|
return;
|
|
12925
13859
|
}
|
|
12926
|
-
await
|
|
13860
|
+
await sleep3(100);
|
|
12927
13861
|
}
|
|
12928
13862
|
if (isProcessRunning(rootPid)) {
|
|
12929
13863
|
killProcessTree(rootPid, "SIGKILL");
|
|
@@ -12976,12 +13910,12 @@ function detectListeningPorts(pids) {
|
|
|
12976
13910
|
if (pids.length === 0) {
|
|
12977
13911
|
return [];
|
|
12978
13912
|
}
|
|
12979
|
-
const result = (0,
|
|
13913
|
+
const result = (0, import_node_child_process28.spawnSync)("lsof", ["-nP", "-iTCP", "-sTCP:LISTEN", "-a", "-p", pids.join(",")], {
|
|
12980
13914
|
encoding: "utf-8"
|
|
12981
13915
|
});
|
|
12982
13916
|
if (result.error && result.error.code === "ENOENT") {
|
|
12983
13917
|
if (process.platform === "linux") {
|
|
12984
|
-
const ssResult = (0,
|
|
13918
|
+
const ssResult = (0, import_node_child_process28.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
|
|
12985
13919
|
if (ssResult.status === 0 && ssResult.stdout) {
|
|
12986
13920
|
return parseSsListeningPorts(ssResult.stdout, new Set(pids));
|
|
12987
13921
|
}
|
|
@@ -13040,7 +13974,7 @@ async function runHostApp(repoPath, app, extraEnv = {}, secretManager, env, work
|
|
|
13040
13974
|
app.hostRun.command,
|
|
13041
13975
|
true
|
|
13042
13976
|
) : app.hostRun.command;
|
|
13043
|
-
const child = (0,
|
|
13977
|
+
const child = (0, import_node_child_process28.spawn)(spawnCommand, {
|
|
13044
13978
|
cwd: commandCwd,
|
|
13045
13979
|
stdio: "inherit",
|
|
13046
13980
|
shell: true,
|
|
@@ -13110,7 +14044,7 @@ async function runHostApp(repoPath, app, extraEnv = {}, secretManager, env, work
|
|
|
13110
14044
|
);
|
|
13111
14045
|
}
|
|
13112
14046
|
}
|
|
13113
|
-
await
|
|
14047
|
+
await sleep3(POLL_INTERVAL_MS2);
|
|
13114
14048
|
}
|
|
13115
14049
|
} catch (error) {
|
|
13116
14050
|
fatalError = toError(error);
|
|
@@ -13408,7 +14342,7 @@ async function execWithAppEnv(options) {
|
|
|
13408
14342
|
options.command[0],
|
|
13409
14343
|
true
|
|
13410
14344
|
);
|
|
13411
|
-
child = (0,
|
|
14345
|
+
child = (0, import_node_child_process28.spawn)(wrapped, {
|
|
13412
14346
|
cwd: deps.repoPath,
|
|
13413
14347
|
stdio: "inherit",
|
|
13414
14348
|
shell: true,
|
|
@@ -13422,7 +14356,7 @@ async function execWithAppEnv(options) {
|
|
|
13422
14356
|
false
|
|
13423
14357
|
);
|
|
13424
14358
|
const [cmd, ...wrappedArgs] = wrapped;
|
|
13425
|
-
child = (0,
|
|
14359
|
+
child = (0, import_node_child_process28.spawn)(cmd, wrappedArgs, {
|
|
13426
14360
|
cwd: deps.repoPath,
|
|
13427
14361
|
stdio: "inherit",
|
|
13428
14362
|
shell: false,
|
|
@@ -13430,7 +14364,7 @@ async function execWithAppEnv(options) {
|
|
|
13430
14364
|
});
|
|
13431
14365
|
}
|
|
13432
14366
|
} else if (options.shell) {
|
|
13433
|
-
child = (0,
|
|
14367
|
+
child = (0, import_node_child_process28.spawn)(options.command[0], {
|
|
13434
14368
|
cwd: deps.repoPath,
|
|
13435
14369
|
stdio: "inherit",
|
|
13436
14370
|
shell: true,
|
|
@@ -13438,7 +14372,7 @@ async function execWithAppEnv(options) {
|
|
|
13438
14372
|
});
|
|
13439
14373
|
} else {
|
|
13440
14374
|
const [command, ...args] = options.command;
|
|
13441
|
-
child = (0,
|
|
14375
|
+
child = (0, import_node_child_process28.spawn)(command, args, {
|
|
13442
14376
|
cwd: deps.repoPath,
|
|
13443
14377
|
stdio: "inherit",
|
|
13444
14378
|
shell: false,
|
|
@@ -13459,11 +14393,11 @@ async function execWithAppEnv(options) {
|
|
|
13459
14393
|
deps.stopDeps();
|
|
13460
14394
|
}
|
|
13461
14395
|
}
|
|
13462
|
-
var
|
|
14396
|
+
var import_node_child_process28, import_node_net, import_node_process, import_promises, POLL_INTERVAL_MS2, DEFAULT_PORT_TIMEOUT_MS, PROCESS_TERMINATION_GRACE_MS;
|
|
13463
14397
|
var init_app_run = __esm({
|
|
13464
14398
|
"src/core/app-run.ts"() {
|
|
13465
14399
|
"use strict";
|
|
13466
|
-
|
|
14400
|
+
import_node_child_process28 = require("child_process");
|
|
13467
14401
|
import_node_net = __toESM(require("net"));
|
|
13468
14402
|
import_node_process = require("process");
|
|
13469
14403
|
import_promises = require("readline/promises");
|
|
@@ -13597,13 +14531,13 @@ function measureWorktreeConsumption(worktreePath, options) {
|
|
|
13597
14531
|
const startedAt = Date.now();
|
|
13598
14532
|
let rootStat;
|
|
13599
14533
|
try {
|
|
13600
|
-
rootStat =
|
|
14534
|
+
rootStat = import_node_fs31.default.lstatSync(worktreePath);
|
|
13601
14535
|
} catch (error) {
|
|
13602
14536
|
return { status: "unknown", reason: describeError(error, worktreePath) };
|
|
13603
14537
|
}
|
|
13604
14538
|
let rootEntries;
|
|
13605
14539
|
try {
|
|
13606
|
-
rootEntries =
|
|
14540
|
+
rootEntries = import_node_fs31.default.readdirSync(worktreePath, { withFileTypes: true });
|
|
13607
14541
|
} catch (error) {
|
|
13608
14542
|
return { status: "unknown", reason: describeError(error, worktreePath) };
|
|
13609
14543
|
}
|
|
@@ -13629,10 +14563,10 @@ function measureWorktreeConsumption(worktreePath, options) {
|
|
|
13629
14563
|
timedOut = true;
|
|
13630
14564
|
break;
|
|
13631
14565
|
}
|
|
13632
|
-
const entryPath =
|
|
14566
|
+
const entryPath = import_node_path32.default.join(dirPath, entry.name);
|
|
13633
14567
|
let entryStat;
|
|
13634
14568
|
try {
|
|
13635
|
-
entryStat =
|
|
14569
|
+
entryStat = import_node_fs31.default.lstatSync(entryPath);
|
|
13636
14570
|
} catch (error) {
|
|
13637
14571
|
if (error?.code === "ENOENT") continue;
|
|
13638
14572
|
unreadableReason = describeIncompleteWalk(error);
|
|
@@ -13642,7 +14576,7 @@ function measureWorktreeConsumption(worktreePath, options) {
|
|
|
13642
14576
|
if (!entryStat.isDirectory()) continue;
|
|
13643
14577
|
let childEntries;
|
|
13644
14578
|
try {
|
|
13645
|
-
childEntries =
|
|
14579
|
+
childEntries = import_node_fs31.default.readdirSync(entryPath, { withFileTypes: true });
|
|
13646
14580
|
} catch (error) {
|
|
13647
14581
|
unreadableReason = describeIncompleteWalk(error);
|
|
13648
14582
|
break;
|
|
@@ -13706,12 +14640,12 @@ function describeError(error, worktreePath) {
|
|
|
13706
14640
|
}
|
|
13707
14641
|
return `could not stat worktree path '${worktreePath}': ${error?.message ?? String(error)}`;
|
|
13708
14642
|
}
|
|
13709
|
-
var
|
|
14643
|
+
var import_node_fs31, import_node_path32, DEFAULT_DEADLINE_MS, BLOCK_SIZE_BYTES;
|
|
13710
14644
|
var init_workspace_consumption = __esm({
|
|
13711
14645
|
"src/core/workspace-consumption.ts"() {
|
|
13712
14646
|
"use strict";
|
|
13713
|
-
|
|
13714
|
-
|
|
14647
|
+
import_node_fs31 = __toESM(require("fs"));
|
|
14648
|
+
import_node_path32 = __toESM(require("path"));
|
|
13715
14649
|
init_devpod_environment();
|
|
13716
14650
|
DEFAULT_DEADLINE_MS = 1e4;
|
|
13717
14651
|
BLOCK_SIZE_BYTES = 512;
|
|
@@ -13812,7 +14746,7 @@ function evaluateWorkspaceActivity(evidence, cutoff) {
|
|
|
13812
14746
|
}
|
|
13813
14747
|
function readGitSnapshot(worktree, commandRunner) {
|
|
13814
14748
|
const comparablePath = comparableWorkspacePath(worktree.path);
|
|
13815
|
-
if (worktree.prunable || !
|
|
14749
|
+
if (worktree.prunable || !import_node_fs32.default.existsSync(comparablePath)) {
|
|
13816
14750
|
return { worktree, checkout: "missing", head: null, committerDate: null };
|
|
13817
14751
|
}
|
|
13818
14752
|
const head = gitOutput(comparablePath, ["rev-parse", "--verify", "HEAD"], commandRunner);
|
|
@@ -14337,7 +15271,7 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
|
|
|
14337
15271
|
containers = measureContainersFn(worktreePaths);
|
|
14338
15272
|
} catch (error) {
|
|
14339
15273
|
const reason = `container measurement failed: ${describeCause(error)}`;
|
|
14340
|
-
containers = new Map(worktreePaths.map((
|
|
15274
|
+
containers = new Map(worktreePaths.map((path32) => [path32, unknownContainers(reason)]));
|
|
14341
15275
|
}
|
|
14342
15276
|
}
|
|
14343
15277
|
const rows = records.map((record) => {
|
|
@@ -14386,12 +15320,12 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
|
|
|
14386
15320
|
workspaces: rows
|
|
14387
15321
|
};
|
|
14388
15322
|
}
|
|
14389
|
-
var
|
|
15323
|
+
var import_node_child_process29, import_node_fs32, DEFAULT_INACTIVE_FOR, READ_ONLY_GIT_ENV2, ACTIVITY_SOURCES, defaultCommandRunner;
|
|
14390
15324
|
var init_workspace_cleanup = __esm({
|
|
14391
15325
|
"src/core/workspace-cleanup.ts"() {
|
|
14392
15326
|
"use strict";
|
|
14393
|
-
|
|
14394
|
-
|
|
15327
|
+
import_node_child_process29 = require("child_process");
|
|
15328
|
+
import_node_fs32 = __toESM(require("fs"));
|
|
14395
15329
|
init_devpod_workspaces();
|
|
14396
15330
|
init_host_routes();
|
|
14397
15331
|
init_repo_config();
|
|
@@ -14408,7 +15342,7 @@ var init_workspace_cleanup = __esm({
|
|
|
14408
15342
|
];
|
|
14409
15343
|
defaultCommandRunner = (command, args, input2) => {
|
|
14410
15344
|
const environment = command === "git" ? READ_ONLY_GIT_ENV2 : { ...process.env, LC_ALL: "C" };
|
|
14411
|
-
const result = (0,
|
|
15345
|
+
const result = (0, import_node_child_process29.spawnSync)(command, args, {
|
|
14412
15346
|
encoding: "utf-8",
|
|
14413
15347
|
env: environment,
|
|
14414
15348
|
...input2 === void 0 ? {} : { input: input2 }
|
|
@@ -14568,7 +15502,7 @@ var init_version = __esm({
|
|
|
14568
15502
|
|
|
14569
15503
|
// src/cli.ts
|
|
14570
15504
|
var import_commander = require("commander");
|
|
14571
|
-
var CLI_VERSION = true ? "0.0.
|
|
15505
|
+
var CLI_VERSION = true ? "0.0.48" : "0.0.0-dev";
|
|
14572
15506
|
var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
|
|
14573
15507
|
function withErrorHandling(action2) {
|
|
14574
15508
|
return async (...args) => {
|
|
@@ -14726,6 +15660,16 @@ repoDevcontainerCommand.command("verify").description("Verify devcontainer onboa
|
|
|
14726
15660
|
}
|
|
14727
15661
|
)
|
|
14728
15662
|
);
|
|
15663
|
+
var profileCommand = program.command("profile").description("Resolve profiles from `.devrouter.yml` without starting a runtime");
|
|
15664
|
+
profileCommand.command("resolve").description("Print the exact apps, dependencies, services, and processes for a profile").option("--repo <path>", "Repository path (defaults to current directory)").option(
|
|
15665
|
+
"--profile <name>",
|
|
15666
|
+
"Profile name or comma-separated selection (defaults to configured profile)"
|
|
15667
|
+
).option("--json", "Output stable JSON").action(
|
|
15668
|
+
withErrorHandling(async (options) => {
|
|
15669
|
+
const { runProfileResolveCommand: runProfileResolveCommand2 } = await Promise.resolve().then(() => (init_profile(), profile_exports));
|
|
15670
|
+
await runProfileResolveCommand2(options);
|
|
15671
|
+
})
|
|
15672
|
+
);
|
|
14729
15673
|
var appCommand = program.command("app").description("Manage app entries and runtime actions from `.devrouter.yml`");
|
|
14730
15674
|
appCommand.command("add").description("Add or update one app definition in `.devrouter.yml`").requiredOption("--name <name>", "App name").option("--kind <kind>", "app or dependency", "app").option("--host <host>", "Hostname ending with .localhost (required for --kind app)").option("--protocol <protocol>", "http or tcp (required for --kind app)").option(
|
|
14731
15675
|
"--runtime <runtime>",
|