@devrouter/cli 0.0.32 → 0.0.34
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 +5 -5
- package/dist/devrouter.js +287 -173
- package/package.json +1 -1
- package/upgrade-prompts/0.0.33.md +21 -0
- package/upgrade-prompts/0.0.34.md +28 -0
package/README.md
CHANGED
|
@@ -118,11 +118,11 @@ vars. Use it when you are not (yet) on a devcontainer. Fully supported.
|
|
|
118
118
|
The current `devrouter repo devcontainer write` scaffold is intentionally narrow:
|
|
119
119
|
Node + pnpm + Postgres. Other package managers stop with a JSON diagnostic
|
|
120
120
|
instead of writing files that would need manual repair.
|
|
121
|
-
The generated image
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
commands and environment setup remain
|
|
125
|
-
part of `devrouter ensure`.
|
|
121
|
+
The generated image contains no Devrouter package or helper. `devrouter ensure`
|
|
122
|
+
delivers its matching `devrouter-process` helper to the exact running container,
|
|
123
|
+
then invokes the repository-owned `post-start.sh` for locked, owned, idempotent
|
|
124
|
+
background startup. Application commands and environment setup remain
|
|
125
|
+
repository-owned; route readiness remains part of `devrouter ensure`.
|
|
126
126
|
Use `devrouter repo devcontainer verify --json` for read-only PR evidence; add
|
|
127
127
|
`--live --yes` only for compatibility checks. Normal startup uses `ensure`.
|
|
128
128
|
|
package/dist/devrouter.js
CHANGED
|
@@ -43,6 +43,7 @@ function buildDevrouterSection() {
|
|
|
43
43
|
`\`${DEVROUTER_SKILL_REL_PATH}\``,
|
|
44
44
|
"",
|
|
45
45
|
"Quick validation sequence:",
|
|
46
|
+
"- Managed devcontainer consumer images contain no devrouter package or helper; `devrouter ensure` delivers the matching helper at runtime.",
|
|
46
47
|
"- `devrouter up`",
|
|
47
48
|
"- `devrouter tls install` (required when repo defines tcp/postgres apps)",
|
|
48
49
|
"- `devrouter app ls --repo .`",
|
|
@@ -347,6 +348,7 @@ For host/docker runtime apps only:
|
|
|
347
348
|
|
|
348
349
|
## Runtime behavior notes
|
|
349
350
|
|
|
351
|
+
- Managed devcontainer images contain no devrouter package or helper. \`devrouter ensure\` delivers its matching process helper to the exact running container and invokes the repository-owned \`.devcontainer/post-start.sh\`; keep \`.devrouter.yml\` as the only consumer-side devrouter version pin.
|
|
350
352
|
- \`devrouter app run\` auto-starts Docker dependencies and waits for health. Host app runs stop auto-started docker deps on exit; docker app runs leave target services running until explicit cleanup.
|
|
351
353
|
- Host-runtime dependencies are NOT auto-started (v1).
|
|
352
354
|
- \`kind=dependency\` entries do not create routes and cannot be direct targets for \`devrouter app run\`, \`devrouter app exec\`, or \`devrouter open\`.
|
|
@@ -1794,7 +1796,7 @@ function loadRepoConfig(repoPath) {
|
|
|
1794
1796
|
const config = parseConfig(parsed ?? {}, configPath);
|
|
1795
1797
|
const requiredVersion = config.devrouter?.version;
|
|
1796
1798
|
if (requiredVersion && !hasWarnedVersionMismatch) {
|
|
1797
|
-
const cliVersion = true ? "0.0.
|
|
1799
|
+
const cliVersion = true ? "0.0.34" : "0.0.0-dev";
|
|
1798
1800
|
if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
|
|
1799
1801
|
hasWarnedVersionMismatch = true;
|
|
1800
1802
|
process.stderr.write(
|
|
@@ -2298,7 +2300,7 @@ function buildOnboardingPrompt(options = {}) {
|
|
|
2298
2300
|
"- The owner record survives linked-worktree removal and binds the exact path to its DevPod ID. First use reuses an exact-path DevPod or derives a sanitized branch/path identity. Later flags or `DEVROUTER_WORKSPACE` may repeat but cannot rename it. Ambiguous identities fail closed. The primary checkout stays non-namespaced.",
|
|
2299
2301
|
`- 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. The runtime config is computed in memory only \u2014 the committed \`.devrouter.yml\` is never rewritten.`,
|
|
2300
2302
|
"- 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.",
|
|
2301
|
-
"- 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. Use `devrouter stop .` for a non-destructive pause and `devrouter exec . -- <command...>` for container commands. `workspace up` creates linked worktrees; destructive down/GC remains ledger-scoped.",
|
|
2303
|
+
"- 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 the repository-owned post-start adapter. Keep `.devrouter.yml` as the only consumer-side version pin. Use `devrouter stop .` for a non-destructive pause and `devrouter exec . -- <command...>` for container commands. `workspace up` creates linked worktrees; destructive down/GC remains ledger-scoped.",
|
|
2302
2304
|
"- 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.",
|
|
2303
2305
|
"- Workspace commands require Git. Normal config, app, status, and doctor flows work from a `.devrouter.yml` folder without `.git`. Git has no worktree-removal hook; use `workspace ls`, doctor, or dry-run GC after out-of-band removal.",
|
|
2304
2306
|
"- devcontainer integration: `devcontainer.json` lists the base compose file then `${localEnv:DEVCONTAINER_COMPOSE_OVERLAY:docker-compose.default.yml}`. The default overlay contains `services: {}`; `.devcontainer/docker-compose.devrouter.yml` passes `WORKSPACE` and `DEVROUTER_WORKSPACE` into the app and bind-mounts `${DEVROUTER_GIT_COMMON_DIR}` to the same absolute app-container path. Ensure proves exact DevPod ownership, overlay/Git mounts, env, aliases, health, Git, HTTP route reachability, and unique running TCP upstream ownership before success.",
|
|
@@ -3291,6 +3293,19 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
|
|
|
3291
3293
|
details: compose.publishedPorts.length > 0 ? compose.publishedPorts.join(", ") : void 0,
|
|
3292
3294
|
suggestion: compose.parseError || compose.publishedPorts.length > 0 ? "Remove published ports and route services through devnet aliases with devrouter proxy apps." : void 0
|
|
3293
3295
|
});
|
|
3296
|
+
const dockerfilePath = import_node_path7.default.join(devcontainerDir, "Dockerfile");
|
|
3297
|
+
const dockerfileExists = import_node_fs8.default.existsSync(dockerfilePath);
|
|
3298
|
+
const dockerfile = dockerfileExists ? import_node_fs8.default.readFileSync(dockerfilePath, "utf-8") : "";
|
|
3299
|
+
const devrouterArtifacts = ["@devrouter/cli", "devrouter-process"].filter(
|
|
3300
|
+
(artifact) => dockerfile.includes(artifact)
|
|
3301
|
+
);
|
|
3302
|
+
checks.push({
|
|
3303
|
+
id: "repo.devcontainer.no-devrouter-image-install",
|
|
3304
|
+
level: !dockerfileExists ? "warn" : devrouterArtifacts.length === 0 ? "ok" : "error",
|
|
3305
|
+
summary: !dockerfileExists ? "No .devcontainer/Dockerfile found to inspect for devrouter artifacts." : devrouterArtifacts.length === 0 ? "Consumer image does not install or extract devrouter artifacts." : "Consumer image installs or extracts devrouter artifacts.",
|
|
3306
|
+
details: devrouterArtifacts.length > 0 ? `artifacts=${devrouterArtifacts.join(", ")}` : void 0,
|
|
3307
|
+
suggestion: !dockerfileExists ? "Inspect the consumer image definition and keep devrouter package/helper installation out of it." : devrouterArtifacts.length > 0 ? "Remove devrouter package/helper installation from the Dockerfile; devrouter ensure delivers the helper at runtime." : void 0
|
|
3308
|
+
});
|
|
3294
3309
|
if (!config) {
|
|
3295
3310
|
checks.push({
|
|
3296
3311
|
id: "repo.devcontainer.upstream-alias-match",
|
|
@@ -5087,7 +5102,7 @@ async function buildDoctorReport(options = {}) {
|
|
|
5087
5102
|
const config = runtimeConfig.config;
|
|
5088
5103
|
loadedConfig = config;
|
|
5089
5104
|
loadedWorkspace = runtimeConfig.workspace;
|
|
5090
|
-
const cliVersion = true ? "0.0.
|
|
5105
|
+
const cliVersion = true ? "0.0.34" : "0.0.0-dev";
|
|
5091
5106
|
const configVersion = config.devrouter?.version;
|
|
5092
5107
|
if (configVersion && cliVersion !== "0.0.0-dev" && compareSemver(configVersion, cliVersion) > 0) {
|
|
5093
5108
|
addCheck(checks, {
|
|
@@ -5760,6 +5775,107 @@ var init_http_route_probe = __esm({
|
|
|
5760
5775
|
}
|
|
5761
5776
|
});
|
|
5762
5777
|
|
|
5778
|
+
// src/core/managed-post-start.ts
|
|
5779
|
+
function commandFailure(result) {
|
|
5780
|
+
return [result.error?.message, result.stderr, result.stdout].filter(Boolean).map((value) => String(value).trim()).filter(Boolean).join("\n");
|
|
5781
|
+
}
|
|
5782
|
+
function readRegularFile(filePath) {
|
|
5783
|
+
if (!import_node_fs15.default.existsSync(filePath) || !import_node_fs15.default.lstatSync(filePath).isFile()) return void 0;
|
|
5784
|
+
return import_node_fs15.default.readFileSync(filePath, "utf-8");
|
|
5785
|
+
}
|
|
5786
|
+
function resolveProcessHelperPath() {
|
|
5787
|
+
const candidates = [
|
|
5788
|
+
import_node_path15.default.resolve(__dirname, "..", "bin", "devrouter-process"),
|
|
5789
|
+
import_node_path15.default.resolve(__dirname, "..", "..", "bin", "devrouter-process")
|
|
5790
|
+
];
|
|
5791
|
+
const helperPath = candidates.find((candidate) => import_node_fs15.default.existsSync(candidate));
|
|
5792
|
+
if (!helperPath) {
|
|
5793
|
+
throw new Error("Could not locate the packaged devrouter-process helper.");
|
|
5794
|
+
}
|
|
5795
|
+
return helperPath;
|
|
5796
|
+
}
|
|
5797
|
+
function resolveManagedPostStartPlan(repoPath) {
|
|
5798
|
+
const adapterPath = import_node_path15.default.join(repoPath, MANAGED_ADAPTER_PATH);
|
|
5799
|
+
const adapter = readRegularFile(adapterPath);
|
|
5800
|
+
if (adapter === void 0) return { kind: "unmanaged" };
|
|
5801
|
+
if (!adapter.includes(MANAGED_MARKER)) return { kind: "unmanaged" };
|
|
5802
|
+
if (adapter.includes("DEVROUTER_PROCESS_HELPER")) {
|
|
5803
|
+
return { kind: "runtime", adapterPath: MANAGED_ADAPTER_PATH };
|
|
5804
|
+
}
|
|
5805
|
+
const dockerfilePath = import_node_path15.default.join(repoPath, ".devcontainer", "Dockerfile");
|
|
5806
|
+
const devcontainerPath = import_node_path15.default.join(repoPath, ".devcontainer", "devcontainer.json");
|
|
5807
|
+
const dockerfile = readRegularFile(dockerfilePath) ?? "";
|
|
5808
|
+
const devcontainer = readRegularFile(devcontainerPath) ?? "";
|
|
5809
|
+
if (dockerfile.includes("devrouter-process") && devcontainer.includes("postStartCommand")) {
|
|
5810
|
+
return { kind: "legacy" };
|
|
5811
|
+
}
|
|
5812
|
+
throw new Error(
|
|
5813
|
+
"Managed post-start must use DEVROUTER_PROCESS_HELPER before removing the legacy image helper and postStartCommand. Regenerate or migrate the devcontainer, then retry devrouter ensure."
|
|
5814
|
+
);
|
|
5815
|
+
}
|
|
5816
|
+
function runManagedPostStart(options) {
|
|
5817
|
+
if (options.plan.kind !== "runtime") return;
|
|
5818
|
+
const helper = import_node_fs15.default.readFileSync(resolveProcessHelperPath());
|
|
5819
|
+
const delivered = (0, import_node_child_process11.spawnSync)(
|
|
5820
|
+
"docker",
|
|
5821
|
+
["exec", "-i", options.container.id, "sh", "-c", DELIVERY_SCRIPT],
|
|
5822
|
+
{ input: helper, encoding: "utf-8" }
|
|
5823
|
+
);
|
|
5824
|
+
if (delivered.status !== 0) {
|
|
5825
|
+
const details = commandFailure(delivered);
|
|
5826
|
+
throw new Error(
|
|
5827
|
+
`Could not deliver the managed process helper${details ? `: ${details}` : "."}`
|
|
5828
|
+
);
|
|
5829
|
+
}
|
|
5830
|
+
const started = (0, import_node_child_process11.spawnSync)(
|
|
5831
|
+
"docker",
|
|
5832
|
+
[
|
|
5833
|
+
"exec",
|
|
5834
|
+
"--workdir",
|
|
5835
|
+
options.container.workspacePath,
|
|
5836
|
+
"--env",
|
|
5837
|
+
`DEVROUTER_PROCESS_HELPER=${RUNTIME_HELPER_PATH}`,
|
|
5838
|
+
options.container.id,
|
|
5839
|
+
"bash",
|
|
5840
|
+
options.plan.adapterPath
|
|
5841
|
+
],
|
|
5842
|
+
{ stdio: options.quiet ? ["ignore", 2, "inherit"] : "inherit" }
|
|
5843
|
+
);
|
|
5844
|
+
if (started.status !== 0) {
|
|
5845
|
+
const details = commandFailure(started);
|
|
5846
|
+
throw new Error(`Managed post-start failed${details ? `: ${details}` : "."}`);
|
|
5847
|
+
}
|
|
5848
|
+
}
|
|
5849
|
+
var import_node_child_process11, import_node_fs15, import_node_path15, MANAGED_MARKER, MANAGED_ADAPTER_PATH, RUNTIME_HELPER_PATH, DELIVERY_SCRIPT;
|
|
5850
|
+
var init_managed_post_start = __esm({
|
|
5851
|
+
"src/core/managed-post-start.ts"() {
|
|
5852
|
+
"use strict";
|
|
5853
|
+
import_node_child_process11 = require("child_process");
|
|
5854
|
+
import_node_fs15 = __toESM(require("fs"));
|
|
5855
|
+
import_node_path15 = __toESM(require("path"));
|
|
5856
|
+
MANAGED_MARKER = "devrouter:managed devcontainer";
|
|
5857
|
+
MANAGED_ADAPTER_PATH = ".devcontainer/post-start.sh";
|
|
5858
|
+
RUNTIME_HELPER_PATH = "/tmp/devrouter/bin/devrouter-process";
|
|
5859
|
+
DELIVERY_SCRIPT = `set -eu
|
|
5860
|
+
umask 077
|
|
5861
|
+
runtime_root=/tmp/devrouter
|
|
5862
|
+
runtime_bin="$runtime_root/bin"
|
|
5863
|
+
if [ -L "$runtime_root" ] || [ -L "$runtime_bin" ]; then
|
|
5864
|
+
echo "Refusing symlinked devrouter runtime path." >&2
|
|
5865
|
+
exit 1
|
|
5866
|
+
fi
|
|
5867
|
+
mkdir -p "$runtime_bin"
|
|
5868
|
+
chmod 700 "$runtime_root" "$runtime_bin"
|
|
5869
|
+
temporary="$(mktemp "$runtime_bin/.devrouter-process.XXXXXX")"
|
|
5870
|
+
trap 'rm -f "$temporary"' EXIT
|
|
5871
|
+
cat > "$temporary"
|
|
5872
|
+
chmod 700 "$temporary"
|
|
5873
|
+
mv -f "$temporary" "${RUNTIME_HELPER_PATH}"
|
|
5874
|
+
trap - EXIT
|
|
5875
|
+
`;
|
|
5876
|
+
}
|
|
5877
|
+
});
|
|
5878
|
+
|
|
5763
5879
|
// src/core/route-publication.ts
|
|
5764
5880
|
function routedAppsFromConfig(config) {
|
|
5765
5881
|
return config.apps.filter((app) => app.kind !== "dependency");
|
|
@@ -5833,11 +5949,11 @@ var init_route_publication = __esm({
|
|
|
5833
5949
|
// src/core/workspace-ensure.ts
|
|
5834
5950
|
function assertOverlay(container, repoPath) {
|
|
5835
5951
|
const workingDir = container.labels["com.docker.compose.project.working_dir"];
|
|
5836
|
-
if (!workingDir || !sameWorkspacePath(workingDir,
|
|
5952
|
+
if (!workingDir || !sameWorkspacePath(workingDir, import_node_path16.default.join(repoPath, ".devcontainer"))) {
|
|
5837
5953
|
throw new Error(`Container '${container.id}' does not belong to the exact worktree.`);
|
|
5838
5954
|
}
|
|
5839
5955
|
const configFiles = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").filter(Boolean);
|
|
5840
|
-
const expectedOverlay =
|
|
5956
|
+
const expectedOverlay = import_node_path16.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
|
|
5841
5957
|
if (!configFiles.some((configFile) => sameWorkspacePath(configFile, expectedOverlay))) {
|
|
5842
5958
|
throw new Error(`Container '${container.id}' was not started with ${DEVCONTAINER_OVERLAY}.`);
|
|
5843
5959
|
}
|
|
@@ -5911,7 +6027,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
5911
6027
|
target
|
|
5912
6028
|
});
|
|
5913
6029
|
if (target.kind === "linked") {
|
|
5914
|
-
const workspaceEnv = (0,
|
|
6030
|
+
const workspaceEnv = (0, import_node_child_process12.spawnSync)(
|
|
5915
6031
|
"docker",
|
|
5916
6032
|
["exec", appContainer.id, "printenv", "WORKSPACE"],
|
|
5917
6033
|
{ encoding: "utf-8" }
|
|
@@ -5921,7 +6037,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
5921
6037
|
`Workspace app container must expose WORKSPACE='${target.workspace}' (got '${workspaceEnv.stdout.trim() || "(empty)"}').`
|
|
5922
6038
|
);
|
|
5923
6039
|
}
|
|
5924
|
-
const devrouterWorkspaceEnv = (0,
|
|
6040
|
+
const devrouterWorkspaceEnv = (0, import_node_child_process12.spawnSync)(
|
|
5925
6041
|
"docker",
|
|
5926
6042
|
["exec", appContainer.id, "printenv", "DEVROUTER_WORKSPACE"],
|
|
5927
6043
|
{ encoding: "utf-8" }
|
|
@@ -5932,7 +6048,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
5932
6048
|
);
|
|
5933
6049
|
}
|
|
5934
6050
|
}
|
|
5935
|
-
const gitCheck = (0,
|
|
6051
|
+
const gitCheck = (0, import_node_child_process12.spawnSync)(
|
|
5936
6052
|
"docker",
|
|
5937
6053
|
[
|
|
5938
6054
|
"exec",
|
|
@@ -5948,7 +6064,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
5948
6064
|
if (gitCheck.status !== 0 || !sameWorkspacePath(gitCheck.stdout.trim(), appContainer.workspacePath)) {
|
|
5949
6065
|
throw new Error("Git does not resolve the expected checkout inside the app container.");
|
|
5950
6066
|
}
|
|
5951
|
-
return;
|
|
6067
|
+
return appContainer;
|
|
5952
6068
|
} catch (error) {
|
|
5953
6069
|
lastError = error;
|
|
5954
6070
|
}
|
|
@@ -6019,7 +6135,7 @@ function resolvePrimaryTarget(repoPath) {
|
|
|
6019
6135
|
}
|
|
6020
6136
|
function isPrimaryCheckout(repoPath) {
|
|
6021
6137
|
try {
|
|
6022
|
-
return
|
|
6138
|
+
return import_node_fs16.default.statSync(import_node_path16.default.join(repoPath, ".git")).isDirectory();
|
|
6023
6139
|
} catch {
|
|
6024
6140
|
return false;
|
|
6025
6141
|
}
|
|
@@ -6056,7 +6172,7 @@ function startDevpod(repoPath, target, recreate = false, quiet = false) {
|
|
|
6056
6172
|
delete env.DEVROUTER_GIT_COMMON_DIR;
|
|
6057
6173
|
delete env.DEVCONTAINER_COMPOSE_OVERLAY;
|
|
6058
6174
|
}
|
|
6059
|
-
const result = (0,
|
|
6175
|
+
const result = (0, import_node_child_process12.spawnSync)("devpod", args, {
|
|
6060
6176
|
stdio: quiet ? ["inherit", 2, "inherit"] : "inherit",
|
|
6061
6177
|
env
|
|
6062
6178
|
});
|
|
@@ -6075,7 +6191,7 @@ function assertDevpodAttachment(repoPath, expectedId) {
|
|
|
6075
6191
|
}
|
|
6076
6192
|
function openUrls(urls) {
|
|
6077
6193
|
for (const url of urls) {
|
|
6078
|
-
const opened = (0,
|
|
6194
|
+
const opened = (0, import_node_child_process12.spawnSync)("open", [url], { encoding: "utf-8" });
|
|
6079
6195
|
if (opened.status !== 0) {
|
|
6080
6196
|
process.stderr.write(`Warning: could not open '${url}'.
|
|
6081
6197
|
`);
|
|
@@ -6105,18 +6221,19 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
6105
6221
|
const target = linked ? resolveLinkedTarget(repoPath) : resolvePrimaryTarget(repoPath);
|
|
6106
6222
|
let devpodId = target.devpodId;
|
|
6107
6223
|
if (target.kind === "linked") {
|
|
6108
|
-
const overlayPath =
|
|
6109
|
-
if (!
|
|
6224
|
+
const overlayPath = import_node_path16.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
|
|
6225
|
+
if (!import_node_fs16.default.existsSync(overlayPath)) {
|
|
6110
6226
|
throw new Error(`Missing required DevPod compose overlay: ${overlayPath}`);
|
|
6111
6227
|
}
|
|
6112
6228
|
}
|
|
6229
|
+
const managedPostStart = resolveManagedPostStartPlan(repoPath);
|
|
6113
6230
|
const runtime = loadRuntimeConfig(
|
|
6114
6231
|
repoPath,
|
|
6115
6232
|
target.kind === "primary" ? "" : target.workspace
|
|
6116
6233
|
);
|
|
6117
6234
|
const apps = proxyAppsFromConfig(runtime.config);
|
|
6118
6235
|
const parsedUpstreams = apps.map((app) => parseUpstream(app.upstream));
|
|
6119
|
-
const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ??
|
|
6236
|
+
const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ?? import_node_path16.default.basename(repoPath)) ?? "app";
|
|
6120
6237
|
for (const [index, app] of apps.entries()) {
|
|
6121
6238
|
if (app.protocol === "tcp" && !parsedUpstreams[index].host.startsWith(`${aliasPrefix}-`)) {
|
|
6122
6239
|
const owner = target.kind === "linked" ? "workspace" : "checkout";
|
|
@@ -6144,36 +6261,38 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
6144
6261
|
writeWorkspaceOwnership(repoPath, ownership);
|
|
6145
6262
|
}
|
|
6146
6263
|
};
|
|
6147
|
-
const
|
|
6264
|
+
const preflight = (timeoutMs) => waitForContainerPreflight(repoPath, currentTarget(), upstreamHosts, timeoutMs);
|
|
6265
|
+
const recreateAndPreflight = async () => {
|
|
6148
6266
|
startAndProveAttachment(true);
|
|
6149
|
-
|
|
6150
|
-
repoPath,
|
|
6151
|
-
currentTarget(),
|
|
6152
|
-
upstreamHosts,
|
|
6153
|
-
options.containerTimeoutMs ?? DEFAULT_READINESS_TIMEOUT_MS
|
|
6154
|
-
);
|
|
6267
|
+
return preflight(options.containerTimeoutMs ?? DEFAULT_READINESS_TIMEOUT_MS);
|
|
6155
6268
|
};
|
|
6156
6269
|
let recreated = false;
|
|
6270
|
+
let container;
|
|
6157
6271
|
try {
|
|
6158
6272
|
startAndProveAttachment();
|
|
6159
6273
|
} catch (error) {
|
|
6160
6274
|
if (!target.hadExactDevpod) {
|
|
6161
6275
|
throw error;
|
|
6162
6276
|
}
|
|
6163
|
-
await
|
|
6277
|
+
container = await recreateAndPreflight();
|
|
6164
6278
|
recreated = true;
|
|
6165
6279
|
}
|
|
6166
|
-
if (!
|
|
6280
|
+
if (!container) {
|
|
6167
6281
|
try {
|
|
6168
|
-
await
|
|
6282
|
+
container = await preflight(0);
|
|
6169
6283
|
} catch (error) {
|
|
6170
6284
|
if (!target.hadExactDevpod) {
|
|
6171
6285
|
throw error;
|
|
6172
6286
|
}
|
|
6173
|
-
await
|
|
6287
|
+
container = await recreateAndPreflight();
|
|
6174
6288
|
recreated = true;
|
|
6175
6289
|
}
|
|
6176
6290
|
}
|
|
6291
|
+
runManagedPostStart({
|
|
6292
|
+
plan: managedPostStart,
|
|
6293
|
+
container,
|
|
6294
|
+
quiet: options.quiet
|
|
6295
|
+
});
|
|
6177
6296
|
const publication = await replacePublishedProxyRoutes(
|
|
6178
6297
|
repoPath,
|
|
6179
6298
|
runtime.config,
|
|
@@ -6190,7 +6309,12 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
6190
6309
|
if (!target.hadExactDevpod || recreated) {
|
|
6191
6310
|
throw error;
|
|
6192
6311
|
}
|
|
6193
|
-
await
|
|
6312
|
+
const recoveredContainer = await recreateAndPreflight();
|
|
6313
|
+
runManagedPostStart({
|
|
6314
|
+
plan: managedPostStart,
|
|
6315
|
+
container: recoveredContainer,
|
|
6316
|
+
quiet: options.quiet
|
|
6317
|
+
});
|
|
6194
6318
|
recreated = true;
|
|
6195
6319
|
replaceHostRoutesForRepo(repoPath, publication.routes);
|
|
6196
6320
|
await waitForHttpRoutes(
|
|
@@ -6233,17 +6357,18 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
6233
6357
|
}
|
|
6234
6358
|
});
|
|
6235
6359
|
}
|
|
6236
|
-
var
|
|
6360
|
+
var import_node_child_process12, import_node_fs16, import_node_path16, DEVCONTAINER_OVERLAY, DEFAULT_READINESS_TIMEOUT_MS, POLL_INTERVAL_MS;
|
|
6237
6361
|
var init_workspace_ensure = __esm({
|
|
6238
6362
|
"src/core/workspace-ensure.ts"() {
|
|
6239
6363
|
"use strict";
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
|
|
6364
|
+
import_node_child_process12 = require("child_process");
|
|
6365
|
+
import_node_fs16 = __toESM(require("fs"));
|
|
6366
|
+
import_node_path16 = __toESM(require("path"));
|
|
6243
6367
|
init_devpod_environment();
|
|
6244
6368
|
init_devpod_workspaces();
|
|
6245
6369
|
init_host_routes();
|
|
6246
6370
|
init_http_route_probe();
|
|
6371
|
+
init_managed_post_start();
|
|
6247
6372
|
init_repo_config();
|
|
6248
6373
|
init_route_publication();
|
|
6249
6374
|
init_router();
|
|
@@ -6313,20 +6438,20 @@ function warnMissingWorkspaceOwnership(repoPath) {
|
|
|
6313
6438
|
);
|
|
6314
6439
|
}
|
|
6315
6440
|
function defaultWorktreePath(mainRepo, ws) {
|
|
6316
|
-
return
|
|
6441
|
+
return import_node_path17.default.join(mainRepo, "trees", ws);
|
|
6317
6442
|
}
|
|
6318
6443
|
function assertDefaultWorktreeRootIgnored(mainRepo) {
|
|
6319
|
-
const ignored = (0,
|
|
6444
|
+
const ignored = (0, import_node_child_process13.spawnSync)("git", ["-C", mainRepo, "check-ignore", "-q", "--no-index", "trees/"], {
|
|
6320
6445
|
encoding: "utf-8"
|
|
6321
6446
|
});
|
|
6322
6447
|
if (ignored.status !== 0) {
|
|
6323
6448
|
throw new Error(
|
|
6324
|
-
`Default worktree root '${
|
|
6449
|
+
`Default worktree root '${import_node_path17.default.join(mainRepo, "trees")}' is not ignored. Add 'trees/' to '${import_node_path17.default.join(mainRepo, ".gitignore")}' or use --path.`
|
|
6325
6450
|
);
|
|
6326
6451
|
}
|
|
6327
6452
|
}
|
|
6328
6453
|
function legacyDefaultWorktreePath(mainRepo, ws) {
|
|
6329
|
-
return
|
|
6454
|
+
return import_node_path17.default.join(import_node_path17.default.dirname(mainRepo), `${import_node_path17.default.basename(mainRepo)}-${ws}`);
|
|
6330
6455
|
}
|
|
6331
6456
|
function teardownFallbackPath(mainRepo, workspace) {
|
|
6332
6457
|
const candidates = [
|
|
@@ -6439,13 +6564,13 @@ function assertFullDownPreflight(mainRepo, target) {
|
|
|
6439
6564
|
if (sameWorkspacePath(target.worktreePath, mainRepo)) {
|
|
6440
6565
|
throw new Error("Refusing to remove the primary Git checkout.");
|
|
6441
6566
|
}
|
|
6442
|
-
if (!target.worktree || target.worktree.prunable || !
|
|
6567
|
+
if (!target.worktree || target.worktree.prunable || !import_node_fs17.default.existsSync(target.worktreePath)) return;
|
|
6443
6568
|
if (target.worktree.locked) {
|
|
6444
6569
|
throw new Error(
|
|
6445
6570
|
`Worktree '${target.worktreePath}' is locked; unlock it before workspace down.`
|
|
6446
6571
|
);
|
|
6447
6572
|
}
|
|
6448
|
-
const status = (0,
|
|
6573
|
+
const status = (0, import_node_child_process13.spawnSync)(
|
|
6449
6574
|
"git",
|
|
6450
6575
|
["-C", target.worktreePath, "status", "--porcelain", "--untracked-files=normal"],
|
|
6451
6576
|
{ encoding: "utf-8" }
|
|
@@ -6466,8 +6591,8 @@ async function workspaceUp(branch, opts = {}) {
|
|
|
6466
6591
|
if (!ws) {
|
|
6467
6592
|
throw new Error(`Branch '${branch}' does not yield a valid workspace token.`);
|
|
6468
6593
|
}
|
|
6469
|
-
const worktreePath = opts.path ?
|
|
6470
|
-
if (
|
|
6594
|
+
const worktreePath = opts.path ? import_node_path17.default.resolve(opts.path) : defaultWorktreePath(mainRepo, ws);
|
|
6595
|
+
if (import_node_fs17.default.existsSync(worktreePath)) {
|
|
6471
6596
|
const registered = listGitWorktrees(mainRepo).find(
|
|
6472
6597
|
(worktree) => sameWorkspacePath(worktree.path, worktreePath)
|
|
6473
6598
|
);
|
|
@@ -6485,11 +6610,11 @@ async function workspaceUp(branch, opts = {}) {
|
|
|
6485
6610
|
if (!opts.path) {
|
|
6486
6611
|
assertDefaultWorktreeRootIgnored(mainRepo);
|
|
6487
6612
|
}
|
|
6488
|
-
const add = (0,
|
|
6613
|
+
const add = (0, import_node_child_process13.spawnSync)("git", ["-C", mainRepo, "worktree", "add", worktreePath, branch], {
|
|
6489
6614
|
encoding: "utf-8"
|
|
6490
6615
|
});
|
|
6491
6616
|
if (add.status !== 0) {
|
|
6492
|
-
const addNew = (0,
|
|
6617
|
+
const addNew = (0, import_node_child_process13.spawnSync)(
|
|
6493
6618
|
"git",
|
|
6494
6619
|
["-C", mainRepo, "worktree", "add", "-b", branch, worktreePath],
|
|
6495
6620
|
{
|
|
@@ -6590,8 +6715,8 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
|
|
|
6590
6715
|
}
|
|
6591
6716
|
const result = mutateWorkspaceRuntime(action2, resolved, worktrees, opts.quiet);
|
|
6592
6717
|
if (removeWorktree) {
|
|
6593
|
-
if (resolved.worktree && !resolved.worktree.prunable &&
|
|
6594
|
-
const rm = (0,
|
|
6718
|
+
if (resolved.worktree && !resolved.worktree.prunable && import_node_fs17.default.existsSync(resolved.worktreePath)) {
|
|
6719
|
+
const rm = (0, import_node_child_process13.spawnSync)("git", ["-C", mainRepo, "worktree", "remove", resolved.worktreePath], {
|
|
6595
6720
|
encoding: "utf-8"
|
|
6596
6721
|
});
|
|
6597
6722
|
if (rm.status !== 0) {
|
|
@@ -6609,7 +6734,7 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
|
|
|
6609
6734
|
}
|
|
6610
6735
|
return result;
|
|
6611
6736
|
};
|
|
6612
|
-
return resolved.worktree && !resolved.worktree.prunable &&
|
|
6737
|
+
return resolved.worktree && !resolved.worktree.prunable && import_node_fs17.default.existsSync(resolved.worktreePath) ? withWorkspaceLifecycleLock(resolved.worktreePath, operation) : operation();
|
|
6613
6738
|
}
|
|
6614
6739
|
async function workspaceStopOwnedPath(worktreePath, opts = {}) {
|
|
6615
6740
|
const mainRepo = resolveRepoPath(opts.repoPath);
|
|
@@ -6638,13 +6763,13 @@ async function workspaceStop(target, opts = {}) {
|
|
|
6638
6763
|
async function workspaceDown(target, opts = {}) {
|
|
6639
6764
|
return runWorkspaceLifecycle("down", target, opts);
|
|
6640
6765
|
}
|
|
6641
|
-
var
|
|
6766
|
+
var import_node_child_process13, import_node_fs17, import_node_path17;
|
|
6642
6767
|
var init_workspace_lifecycle = __esm({
|
|
6643
6768
|
"src/core/workspace-lifecycle.ts"() {
|
|
6644
6769
|
"use strict";
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
6770
|
+
import_node_child_process13 = require("child_process");
|
|
6771
|
+
import_node_fs17 = __toESM(require("fs"));
|
|
6772
|
+
import_node_path17 = __toESM(require("path"));
|
|
6648
6773
|
init_devpod_workspaces();
|
|
6649
6774
|
init_repo_config();
|
|
6650
6775
|
init_route_state();
|
|
@@ -6751,6 +6876,14 @@ var init_stop = __esm({
|
|
|
6751
6876
|
});
|
|
6752
6877
|
|
|
6753
6878
|
// src/core/devpod-exec.ts
|
|
6879
|
+
function stripDevpodCompletionDiagnostic(value) {
|
|
6880
|
+
const index = value.indexOf(DEVPOD_MISSING_EXIT_STATUS_DIAGNOSTIC);
|
|
6881
|
+
if (index < 0) return value;
|
|
6882
|
+
return Buffer.concat([
|
|
6883
|
+
value.subarray(0, index),
|
|
6884
|
+
value.subarray(index + DEVPOD_MISSING_EXIT_STATUS_DIAGNOSTIC.length)
|
|
6885
|
+
]);
|
|
6886
|
+
}
|
|
6754
6887
|
function quotePosixArg(value) {
|
|
6755
6888
|
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
6756
6889
|
}
|
|
@@ -6758,7 +6891,7 @@ function ensureGuidance(repoPath) {
|
|
|
6758
6891
|
return `Run 'devrouter ensure ${repoPath}' first.`;
|
|
6759
6892
|
}
|
|
6760
6893
|
function assertRunningDevpod(devpodId, repoPath) {
|
|
6761
|
-
const result = (0,
|
|
6894
|
+
const result = (0, import_node_child_process14.spawnSync)("devpod", ["status", devpodId, "--output", "json"], {
|
|
6762
6895
|
encoding: "utf-8"
|
|
6763
6896
|
});
|
|
6764
6897
|
if (result.status !== 0) {
|
|
@@ -6814,7 +6947,7 @@ async function devpodExec(repoPath, command) {
|
|
|
6814
6947
|
wrappedCommand
|
|
6815
6948
|
];
|
|
6816
6949
|
return new Promise((resolve, reject) => {
|
|
6817
|
-
const child = (0,
|
|
6950
|
+
const child = (0, import_node_child_process14.spawn)("devpod", args, { stdio: ["inherit", "inherit", "pipe"] });
|
|
6818
6951
|
let pending = Buffer.alloc(0);
|
|
6819
6952
|
let statusBytes = Buffer.alloc(0);
|
|
6820
6953
|
let readingStatus = false;
|
|
@@ -6825,6 +6958,7 @@ async function devpodExec(repoPath, command) {
|
|
|
6825
6958
|
child.stderr.on("data", (chunk) => {
|
|
6826
6959
|
pending = Buffer.concat([pending, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)]);
|
|
6827
6960
|
for (; ; ) {
|
|
6961
|
+
if (remoteStatus !== void 0) break;
|
|
6828
6962
|
if (readingStatus) {
|
|
6829
6963
|
const newline = pending.indexOf(10);
|
|
6830
6964
|
if (newline < 0) {
|
|
@@ -6858,7 +6992,9 @@ async function devpodExec(repoPath, command) {
|
|
|
6858
6992
|
reject(new Error(`devpod ssh terminated by signal ${signal ?? "unknown"}.`));
|
|
6859
6993
|
return;
|
|
6860
6994
|
}
|
|
6861
|
-
if (!readingStatus)
|
|
6995
|
+
if (!readingStatus) {
|
|
6996
|
+
forward(remoteStatus === void 0 ? pending : stripDevpodCompletionDiagnostic(pending));
|
|
6997
|
+
}
|
|
6862
6998
|
if (remoteStatus === void 0) {
|
|
6863
6999
|
reject(
|
|
6864
7000
|
new Error(`DevPod command did not report its exit status (devpod exited ${code}).`)
|
|
@@ -6870,15 +7006,18 @@ async function devpodExec(repoPath, command) {
|
|
|
6870
7006
|
});
|
|
6871
7007
|
});
|
|
6872
7008
|
}
|
|
6873
|
-
var
|
|
7009
|
+
var import_node_child_process14, import_node_crypto4, DEVPOD_MISSING_EXIT_STATUS_DIAGNOSTIC;
|
|
6874
7010
|
var init_devpod_exec = __esm({
|
|
6875
7011
|
"src/core/devpod-exec.ts"() {
|
|
6876
7012
|
"use strict";
|
|
6877
|
-
|
|
7013
|
+
import_node_child_process14 = require("child_process");
|
|
6878
7014
|
import_node_crypto4 = require("crypto");
|
|
6879
7015
|
init_devpod_environment();
|
|
6880
7016
|
init_devpod_workspaces();
|
|
6881
7017
|
init_workspace();
|
|
7018
|
+
DEVPOD_MISSING_EXIT_STATUS_DIAGNOSTIC = Buffer.from(
|
|
7019
|
+
"Error tunneling to container: wait: remote command exited without exit status or exit signal\n"
|
|
7020
|
+
);
|
|
6882
7021
|
}
|
|
6883
7022
|
});
|
|
6884
7023
|
|
|
@@ -7056,7 +7195,7 @@ async function runOpenCommand(name) {
|
|
|
7056
7195
|
);
|
|
7057
7196
|
return;
|
|
7058
7197
|
}
|
|
7059
|
-
const result = (0,
|
|
7198
|
+
const result = (0, import_node_child_process15.spawnSync)("open", [url], { encoding: "utf-8" });
|
|
7060
7199
|
if (result.status !== 0) {
|
|
7061
7200
|
const details = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
7062
7201
|
throw new Error(`Unable to open '${url}': ${details || "unknown error"}`);
|
|
@@ -7064,11 +7203,11 @@ async function runOpenCommand(name) {
|
|
|
7064
7203
|
process.stdout.write(`Opened ${url}
|
|
7065
7204
|
`);
|
|
7066
7205
|
}
|
|
7067
|
-
var
|
|
7206
|
+
var import_node_child_process15;
|
|
7068
7207
|
var init_open = __esm({
|
|
7069
7208
|
"src/commands/open.ts"() {
|
|
7070
7209
|
"use strict";
|
|
7071
|
-
|
|
7210
|
+
import_node_child_process15 = require("child_process");
|
|
7072
7211
|
init_docker();
|
|
7073
7212
|
init_host_routes();
|
|
7074
7213
|
init_repo_config();
|
|
@@ -7091,7 +7230,7 @@ async function runLogsCommand(options) {
|
|
|
7091
7230
|
const args = ["logs", "--tail", tail, ROUTER_CONTAINER_NAME];
|
|
7092
7231
|
if (options.follow) {
|
|
7093
7232
|
args.splice(1, 0, "-f");
|
|
7094
|
-
const child = (0,
|
|
7233
|
+
const child = (0, import_node_child_process16.spawn)("docker", args, { stdio: "inherit" });
|
|
7095
7234
|
const onSignal = () => {
|
|
7096
7235
|
child.kill("SIGTERM");
|
|
7097
7236
|
};
|
|
@@ -7105,17 +7244,17 @@ async function runLogsCommand(options) {
|
|
|
7105
7244
|
});
|
|
7106
7245
|
});
|
|
7107
7246
|
} else {
|
|
7108
|
-
const result = (0,
|
|
7247
|
+
const result = (0, import_node_child_process16.spawnSync)("docker", args, { stdio: "inherit" });
|
|
7109
7248
|
if (result.status !== 0) {
|
|
7110
7249
|
throw new Error("Failed to retrieve router logs.");
|
|
7111
7250
|
}
|
|
7112
7251
|
}
|
|
7113
7252
|
}
|
|
7114
|
-
var
|
|
7253
|
+
var import_node_child_process16;
|
|
7115
7254
|
var init_logs = __esm({
|
|
7116
7255
|
"src/commands/logs.ts"() {
|
|
7117
7256
|
"use strict";
|
|
7118
|
-
|
|
7257
|
+
import_node_child_process16 = require("child_process");
|
|
7119
7258
|
init_docker();
|
|
7120
7259
|
init_router();
|
|
7121
7260
|
}
|
|
@@ -7155,17 +7294,17 @@ function asRecord3(value) {
|
|
|
7155
7294
|
return value;
|
|
7156
7295
|
}
|
|
7157
7296
|
function readJson(filePath) {
|
|
7158
|
-
if (!
|
|
7297
|
+
if (!import_node_fs18.default.existsSync(filePath)) {
|
|
7159
7298
|
return void 0;
|
|
7160
7299
|
}
|
|
7161
7300
|
try {
|
|
7162
|
-
return JSON.parse(
|
|
7301
|
+
return JSON.parse(import_node_fs18.default.readFileSync(filePath, "utf-8"));
|
|
7163
7302
|
} catch {
|
|
7164
7303
|
return void 0;
|
|
7165
7304
|
}
|
|
7166
7305
|
}
|
|
7167
7306
|
function relative(repoPath, filePath) {
|
|
7168
|
-
return
|
|
7307
|
+
return import_node_path18.default.relative(repoPath, filePath) || ".";
|
|
7169
7308
|
}
|
|
7170
7309
|
function redactEnvAssignments(value) {
|
|
7171
7310
|
return value.replace(
|
|
@@ -7206,7 +7345,7 @@ function inspectPackageManager(repoPath, pkg) {
|
|
|
7206
7345
|
["bun.lock", "bun"]
|
|
7207
7346
|
];
|
|
7208
7347
|
for (const [fileName, name] of lockfiles) {
|
|
7209
|
-
if (
|
|
7348
|
+
if (import_node_fs18.default.existsSync(import_node_path18.default.join(repoPath, fileName))) {
|
|
7210
7349
|
return { name, source: fileName };
|
|
7211
7350
|
}
|
|
7212
7351
|
}
|
|
@@ -7221,9 +7360,9 @@ function inspectNode(repoPath, pkg) {
|
|
|
7221
7360
|
if (typeof engines?.node === "string") {
|
|
7222
7361
|
return { version: engines.node, source: "package.json:engines.node" };
|
|
7223
7362
|
}
|
|
7224
|
-
const nvmrc =
|
|
7225
|
-
if (
|
|
7226
|
-
const version =
|
|
7363
|
+
const nvmrc = import_node_path18.default.join(repoPath, ".nvmrc");
|
|
7364
|
+
if (import_node_fs18.default.existsSync(nvmrc)) {
|
|
7365
|
+
const version = import_node_fs18.default.readFileSync(nvmrc, "utf-8").trim();
|
|
7227
7366
|
return { version, source: ".nvmrc" };
|
|
7228
7367
|
}
|
|
7229
7368
|
return void 0;
|
|
@@ -7284,7 +7423,7 @@ function configuredComposeFiles(repoPath) {
|
|
|
7284
7423
|
const files = config.apps.filter(
|
|
7285
7424
|
(app) => app.runtime === "docker"
|
|
7286
7425
|
).flatMap((app) => app.docker.composeFiles).filter(
|
|
7287
|
-
(fileName) => !
|
|
7426
|
+
(fileName) => !import_node_path18.default.isAbsolute(fileName) && !import_node_path18.default.normalize(fileName).startsWith("..")
|
|
7288
7427
|
);
|
|
7289
7428
|
return Array.from(new Set(files));
|
|
7290
7429
|
} catch {
|
|
@@ -7302,7 +7441,7 @@ function composeFiles(repoPath) {
|
|
|
7302
7441
|
...configuredComposeFiles(repoPath)
|
|
7303
7442
|
];
|
|
7304
7443
|
return Array.from(new Set(candidates)).filter(
|
|
7305
|
-
(fileName) =>
|
|
7444
|
+
(fileName) => import_node_fs18.default.existsSync(import_node_path18.default.join(repoPath, fileName))
|
|
7306
7445
|
);
|
|
7307
7446
|
}
|
|
7308
7447
|
function stringArray(value) {
|
|
@@ -7340,7 +7479,7 @@ function inspectServices(repoPath) {
|
|
|
7340
7479
|
const services = [];
|
|
7341
7480
|
for (const fileName of composeFiles(repoPath)) {
|
|
7342
7481
|
try {
|
|
7343
|
-
const parsed = import_yaml5.default.parse(
|
|
7482
|
+
const parsed = import_yaml5.default.parse(import_node_fs18.default.readFileSync(import_node_path18.default.join(repoPath, fileName), "utf-8"));
|
|
7344
7483
|
const serviceMap = asRecord3(asRecord3(parsed)?.services);
|
|
7345
7484
|
for (const [name, value] of Object.entries(serviceMap ?? {})) {
|
|
7346
7485
|
const service = asRecord3(value);
|
|
@@ -7375,8 +7514,8 @@ function inspectServices(repoPath) {
|
|
|
7375
7514
|
return services;
|
|
7376
7515
|
}
|
|
7377
7516
|
function inspectEnvFiles(repoPath) {
|
|
7378
|
-
const files =
|
|
7379
|
-
const content =
|
|
7517
|
+
const files = import_node_fs18.default.readdirSync(repoPath).filter((fileName) => /^\.env(\.|$)/.test(fileName)).sort().map((fileName) => {
|
|
7518
|
+
const content = import_node_fs18.default.readFileSync(import_node_path18.default.join(repoPath, fileName), "utf-8");
|
|
7380
7519
|
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();
|
|
7381
7520
|
return { path: fileName, names };
|
|
7382
7521
|
});
|
|
@@ -7390,18 +7529,18 @@ function inspectEnvFiles(repoPath) {
|
|
|
7390
7529
|
};
|
|
7391
7530
|
}
|
|
7392
7531
|
function inspectDevcontainer(repoPath) {
|
|
7393
|
-
const dir =
|
|
7394
|
-
if (!
|
|
7532
|
+
const dir = import_node_path18.default.join(repoPath, ".devcontainer");
|
|
7533
|
+
if (!import_node_fs18.default.existsSync(dir)) {
|
|
7395
7534
|
return { exists: false, files: [] };
|
|
7396
7535
|
}
|
|
7397
7536
|
return {
|
|
7398
7537
|
exists: true,
|
|
7399
|
-
files:
|
|
7538
|
+
files: import_node_fs18.default.readdirSync(dir).filter((fileName) => import_node_fs18.default.statSync(import_node_path18.default.join(dir, fileName)).isFile()).sort().map((fileName) => `.devcontainer/${fileName}`)
|
|
7400
7539
|
};
|
|
7401
7540
|
}
|
|
7402
7541
|
function inspectDevrouter(repoPath) {
|
|
7403
7542
|
const configPath = getRepoConfigPath(repoPath);
|
|
7404
|
-
if (!
|
|
7543
|
+
if (!import_node_fs18.default.existsSync(configPath)) {
|
|
7405
7544
|
return {
|
|
7406
7545
|
exists: false,
|
|
7407
7546
|
configPath,
|
|
@@ -7445,15 +7584,15 @@ function inspectAgentGuidance(repoPath) {
|
|
|
7445
7584
|
["AGENTS.md", "agents"],
|
|
7446
7585
|
["CLAUDE.md", "claude"]
|
|
7447
7586
|
]) {
|
|
7448
|
-
if (
|
|
7587
|
+
if (import_node_fs18.default.existsSync(import_node_path18.default.join(repoPath, fileName))) {
|
|
7449
7588
|
results.push({ path: fileName, kind });
|
|
7450
7589
|
}
|
|
7451
7590
|
}
|
|
7452
|
-
const skillsDir =
|
|
7453
|
-
if (
|
|
7454
|
-
for (const name of
|
|
7455
|
-
const skillPath =
|
|
7456
|
-
if (
|
|
7591
|
+
const skillsDir = import_node_path18.default.join(repoPath, ".agents", "skills");
|
|
7592
|
+
if (import_node_fs18.default.existsSync(skillsDir)) {
|
|
7593
|
+
for (const name of import_node_fs18.default.readdirSync(skillsDir).sort()) {
|
|
7594
|
+
const skillPath = import_node_path18.default.join(skillsDir, name, "SKILL.md");
|
|
7595
|
+
if (import_node_fs18.default.existsSync(skillPath)) {
|
|
7457
7596
|
results.push({ path: relative(repoPath, skillPath), kind: "skill" });
|
|
7458
7597
|
}
|
|
7459
7598
|
}
|
|
@@ -7496,7 +7635,7 @@ function buildIssues(report) {
|
|
|
7496
7635
|
}
|
|
7497
7636
|
function inspectRepo(options = {}) {
|
|
7498
7637
|
const repoPath = resolveRepoPath(options.repo);
|
|
7499
|
-
const pkg = readJson(
|
|
7638
|
+
const pkg = readJson(import_node_path18.default.join(repoPath, "package.json"));
|
|
7500
7639
|
const scripts = inspectScripts(pkg);
|
|
7501
7640
|
const reportWithoutIssues = {
|
|
7502
7641
|
repoPath,
|
|
@@ -7515,12 +7654,12 @@ function inspectRepo(options = {}) {
|
|
|
7515
7654
|
issues: buildIssues(reportWithoutIssues)
|
|
7516
7655
|
};
|
|
7517
7656
|
}
|
|
7518
|
-
var
|
|
7657
|
+
var import_node_fs18, import_node_path18, import_yaml5;
|
|
7519
7658
|
var init_repo_inspect = __esm({
|
|
7520
7659
|
"src/core/repo-inspect.ts"() {
|
|
7521
7660
|
"use strict";
|
|
7522
|
-
|
|
7523
|
-
|
|
7661
|
+
import_node_fs18 = __toESM(require("fs"));
|
|
7662
|
+
import_node_path18 = __toESM(require("path"));
|
|
7524
7663
|
import_yaml5 = __toESM(require("yaml"));
|
|
7525
7664
|
init_repo_config();
|
|
7526
7665
|
}
|
|
@@ -7625,7 +7764,7 @@ function requiredFileChecks(repoPath) {
|
|
|
7625
7764
|
".devcontainer/docker-compose.yml",
|
|
7626
7765
|
".devrouter.yml"
|
|
7627
7766
|
];
|
|
7628
|
-
const missing = required.filter((fileName) => !
|
|
7767
|
+
const missing = required.filter((fileName) => !import_node_fs19.default.existsSync(import_node_path19.default.join(repoPath, fileName)));
|
|
7629
7768
|
return {
|
|
7630
7769
|
id: "repo.devcontainer.verify-files",
|
|
7631
7770
|
level: missing.length === 0 ? "ok" : "error",
|
|
@@ -7844,12 +7983,12 @@ async function verifyDevcontainer(options = {}) {
|
|
|
7844
7983
|
nextSteps: collectNextSteps3(checks)
|
|
7845
7984
|
};
|
|
7846
7985
|
}
|
|
7847
|
-
var
|
|
7986
|
+
var import_node_fs19, import_node_path19;
|
|
7848
7987
|
var init_devcontainer_verify = __esm({
|
|
7849
7988
|
"src/core/devcontainer-verify.ts"() {
|
|
7850
7989
|
"use strict";
|
|
7851
|
-
|
|
7852
|
-
|
|
7990
|
+
import_node_fs19 = __toESM(require("fs"));
|
|
7991
|
+
import_node_path19 = __toESM(require("path"));
|
|
7853
7992
|
init_capabilities();
|
|
7854
7993
|
init_doctor();
|
|
7855
7994
|
init_host_routes();
|
|
@@ -7887,30 +8026,22 @@ function inferDevProcess(repo) {
|
|
|
7887
8026
|
function inferPort2(repo) {
|
|
7888
8027
|
return repo.apps.find((app) => app.port)?.port ?? 3e3;
|
|
7889
8028
|
}
|
|
7890
|
-
function renderDockerfile(nodeMajor, pnpmVersion
|
|
8029
|
+
function renderDockerfile(nodeMajor, pnpmVersion) {
|
|
7891
8030
|
const pnpmPackageSpec = `pnpm@${pnpmVersion}`;
|
|
7892
|
-
|
|
7893
|
-
const devrouterTarball = `devrouter-cli-${version}.tgz`;
|
|
7894
|
-
return `# ${MANAGED_MARKER}
|
|
8031
|
+
return `# ${MANAGED_MARKER2}
|
|
7895
8032
|
FROM node:${nodeMajor}-bookworm-slim
|
|
7896
8033
|
|
|
7897
8034
|
RUN apt-get update \\
|
|
7898
|
-
&& apt-get install -y --no-install-recommends git ca-certificates curl procps openssl
|
|
8035
|
+
&& apt-get install -y --no-install-recommends git ca-certificates curl procps openssl util-linux \\
|
|
7899
8036
|
&& rm -rf /var/lib/apt/lists/*
|
|
7900
8037
|
|
|
7901
8038
|
RUN npm install -g ${shellSingleQuote(pnpmPackageSpec)}
|
|
7902
8039
|
|
|
7903
|
-
RUN npm pack --silent ${shellSingleQuote(devrouterPackageSpec)} \\
|
|
7904
|
-
&& tar -xzf ${shellSingleQuote(devrouterTarball)} --strip-components=2 \\
|
|
7905
|
-
-C /usr/local/bin package/bin/devrouter-process \\
|
|
7906
|
-
&& chmod +x /usr/local/bin/devrouter-process \\
|
|
7907
|
-
&& rm ${shellSingleQuote(devrouterTarball)}
|
|
7908
|
-
|
|
7909
8040
|
WORKDIR /workspaces/app
|
|
7910
8041
|
`;
|
|
7911
8042
|
}
|
|
7912
8043
|
function renderCompose(projectName) {
|
|
7913
|
-
return `# ${
|
|
8044
|
+
return `# ${MANAGED_MARKER2}
|
|
7914
8045
|
services:
|
|
7915
8046
|
postgres:
|
|
7916
8047
|
image: postgres:16-alpine
|
|
@@ -7963,12 +8094,12 @@ volumes:
|
|
|
7963
8094
|
`;
|
|
7964
8095
|
}
|
|
7965
8096
|
function renderDefaultComposeOverlay() {
|
|
7966
|
-
return `# ${
|
|
8097
|
+
return `# ${MANAGED_MARKER2}
|
|
7967
8098
|
services: {}
|
|
7968
8099
|
`;
|
|
7969
8100
|
}
|
|
7970
8101
|
function renderDevrouterComposeOverlay() {
|
|
7971
|
-
return `# ${
|
|
8102
|
+
return `# ${MANAGED_MARKER2}
|
|
7972
8103
|
# A linked worktree's .git file points at the host repository's common Git
|
|
7973
8104
|
# directory. workspace ensure sets this absolute path before DevPod starts.
|
|
7974
8105
|
services:
|
|
@@ -7984,7 +8115,7 @@ services:
|
|
|
7984
8115
|
}
|
|
7985
8116
|
function renderInitDb() {
|
|
7986
8117
|
return `#!/usr/bin/env bash
|
|
7987
|
-
# ${
|
|
8118
|
+
# ${MANAGED_MARKER2}
|
|
7988
8119
|
set -euo pipefail
|
|
7989
8120
|
|
|
7990
8121
|
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<'SQL'
|
|
@@ -8003,10 +8134,9 @@ function renderDevcontainerJson(projectName) {
|
|
|
8003
8134
|
"service": "app",
|
|
8004
8135
|
"workspaceFolder": "/workspaces/${projectName}",
|
|
8005
8136
|
"postCreateCommand": "bash .devcontainer/post-create.sh",
|
|
8006
|
-
"postStartCommand": "bash .devcontainer/post-start.sh",
|
|
8007
8137
|
"customizations": {
|
|
8008
8138
|
"devrouter": {
|
|
8009
|
-
"managed": "${
|
|
8139
|
+
"managed": "${MANAGED_MARKER2}"
|
|
8010
8140
|
},
|
|
8011
8141
|
"vscode": {
|
|
8012
8142
|
"extensions": []
|
|
@@ -8016,7 +8146,7 @@ function renderDevcontainerJson(projectName) {
|
|
|
8016
8146
|
`;
|
|
8017
8147
|
}
|
|
8018
8148
|
function renderEnv(projectName, port) {
|
|
8019
|
-
return `# ${
|
|
8149
|
+
return `# ${MANAGED_MARKER2}
|
|
8020
8150
|
WORKSPACE=${projectName}
|
|
8021
8151
|
HOST=0.0.0.0
|
|
8022
8152
|
HOSTNAME=0.0.0.0
|
|
@@ -8027,7 +8157,7 @@ SHADOW_DATABASE_URL=postgres://prisma:prisma@postgres:5432/shadow
|
|
|
8027
8157
|
}
|
|
8028
8158
|
function renderPostCreate() {
|
|
8029
8159
|
return `#!/usr/bin/env bash
|
|
8030
|
-
# ${
|
|
8160
|
+
# ${MANAGED_MARKER2}
|
|
8031
8161
|
set -euo pipefail
|
|
8032
8162
|
|
|
8033
8163
|
export CI=true
|
|
@@ -8043,7 +8173,7 @@ function shellSingleQuote(value) {
|
|
|
8043
8173
|
}
|
|
8044
8174
|
function renderPostStart(devProcess) {
|
|
8045
8175
|
return `#!/usr/bin/env bash
|
|
8046
|
-
# ${
|
|
8176
|
+
# ${MANAGED_MARKER2}
|
|
8047
8177
|
set -euo pipefail
|
|
8048
8178
|
|
|
8049
8179
|
export CI=true
|
|
@@ -8052,7 +8182,9 @@ set -a
|
|
|
8052
8182
|
. .devcontainer/devcontainer.env
|
|
8053
8183
|
set +a
|
|
8054
8184
|
|
|
8055
|
-
devrouter
|
|
8185
|
+
: "\${DEVROUTER_PROCESS_HELPER:?Run devrouter ensure to start this managed application process.}"
|
|
8186
|
+
|
|
8187
|
+
"$DEVROUTER_PROCESS_HELPER" ensure \\
|
|
8056
8188
|
--name app \\
|
|
8057
8189
|
--match ${shellSingleQuote(devProcess.match)} \\
|
|
8058
8190
|
--log /tmp/devrouter-app.log \\
|
|
@@ -8060,7 +8192,7 @@ devrouter-process ensure \\
|
|
|
8060
8192
|
`;
|
|
8061
8193
|
}
|
|
8062
8194
|
function renderDevrouter(projectName, port, version) {
|
|
8063
|
-
return `# ${
|
|
8195
|
+
return `# ${MANAGED_MARKER2}
|
|
8064
8196
|
version: 1
|
|
8065
8197
|
devrouter:
|
|
8066
8198
|
version: ${version}
|
|
@@ -8082,24 +8214,16 @@ apps:
|
|
|
8082
8214
|
`;
|
|
8083
8215
|
}
|
|
8084
8216
|
function renderReadme(projectName) {
|
|
8085
|
-
return `<!-- ${
|
|
8217
|
+
return `<!-- ${MANAGED_MARKER2} -->
|
|
8086
8218
|
# Devcontainer
|
|
8087
8219
|
|
|
8088
8220
|
Use this repo through the devcontainer, with devrouter providing stable local routes.
|
|
8089
8221
|
|
|
8090
|
-
Primary checkout:
|
|
8222
|
+
Primary or linked checkout:
|
|
8091
8223
|
|
|
8092
8224
|
\`\`\`bash
|
|
8093
8225
|
devrouter setup --yes
|
|
8094
|
-
|
|
8095
|
-
devrouter app run app --repo . --yes
|
|
8096
|
-
devrouter app run db --repo . --yes
|
|
8097
|
-
\`\`\`
|
|
8098
|
-
|
|
8099
|
-
Existing linked worktree:
|
|
8100
|
-
|
|
8101
|
-
\`\`\`bash
|
|
8102
|
-
devrouter workspace ensure .
|
|
8226
|
+
devrouter ensure .
|
|
8103
8227
|
\`\`\`
|
|
8104
8228
|
|
|
8105
8229
|
- App: https://${projectName}.localhost
|
|
@@ -8108,18 +8232,9 @@ devrouter workspace ensure .
|
|
|
8108
8232
|
}
|
|
8109
8233
|
function postWriteNextSteps(repoPath) {
|
|
8110
8234
|
const quotedRepoPath = shellSingleQuote(repoPath);
|
|
8111
|
-
if (isLinkedWorktree(repoPath)) {
|
|
8112
|
-
return [
|
|
8113
|
-
`Run: devrouter setup --repo ${quotedRepoPath} --yes`,
|
|
8114
|
-
`Run: devrouter workspace ensure ${quotedRepoPath}`,
|
|
8115
|
-
`Optional: devrouter repo agents --repo ${quotedRepoPath}`
|
|
8116
|
-
];
|
|
8117
|
-
}
|
|
8118
8235
|
return [
|
|
8119
8236
|
`Run: devrouter setup --repo ${quotedRepoPath} --yes`,
|
|
8120
|
-
`Run:
|
|
8121
|
-
`Run: devrouter app run app --repo ${quotedRepoPath} --yes`,
|
|
8122
|
-
`Run: devrouter app run db --repo ${quotedRepoPath} --yes`,
|
|
8237
|
+
`Run: devrouter ensure ${quotedRepoPath}`,
|
|
8123
8238
|
`Optional: devrouter repo agents --repo ${quotedRepoPath}`
|
|
8124
8239
|
];
|
|
8125
8240
|
}
|
|
@@ -8169,7 +8284,7 @@ function packageManagerIssues(repo) {
|
|
|
8169
8284
|
}
|
|
8170
8285
|
function plannedFiles(repoPath, version) {
|
|
8171
8286
|
const repo = inspectRepo({ repo: repoPath });
|
|
8172
|
-
const projectName = sanitizeProjectName(
|
|
8287
|
+
const projectName = sanitizeProjectName(import_node_path20.default.basename(repo.repoPath));
|
|
8173
8288
|
const nodeMajor = majorVersion(repo.node?.version, "24");
|
|
8174
8289
|
const pnpmVersion = repo.packageManager?.name === "pnpm" && repo.packageManager.version ? repo.packageManager.version : DEFAULT_PNPM_VERSION;
|
|
8175
8290
|
const port = inferPort2(repo);
|
|
@@ -8181,7 +8296,7 @@ function plannedFiles(repoPath, version) {
|
|
|
8181
8296
|
files: [
|
|
8182
8297
|
{
|
|
8183
8298
|
relativePath: ".devcontainer/Dockerfile",
|
|
8184
|
-
content: renderDockerfile(nodeMajor, pnpmVersion
|
|
8299
|
+
content: renderDockerfile(nodeMajor, pnpmVersion)
|
|
8185
8300
|
},
|
|
8186
8301
|
{ relativePath: ".devcontainer/docker-compose.yml", content: renderCompose(projectName) },
|
|
8187
8302
|
{
|
|
@@ -8214,8 +8329,8 @@ function plannedFiles(repoPath, version) {
|
|
|
8214
8329
|
};
|
|
8215
8330
|
}
|
|
8216
8331
|
function classifyFile(repoPath, file) {
|
|
8217
|
-
const absolutePath =
|
|
8218
|
-
if (!
|
|
8332
|
+
const absolutePath = import_node_path20.default.join(repoPath, file.relativePath);
|
|
8333
|
+
if (!import_node_fs20.default.existsSync(absolutePath)) {
|
|
8219
8334
|
return {
|
|
8220
8335
|
path: file.relativePath,
|
|
8221
8336
|
action: "create",
|
|
@@ -8223,8 +8338,8 @@ function classifyFile(repoPath, file) {
|
|
|
8223
8338
|
bytes: Buffer.byteLength(file.content)
|
|
8224
8339
|
};
|
|
8225
8340
|
}
|
|
8226
|
-
const current =
|
|
8227
|
-
if (!current.includes(
|
|
8341
|
+
const current = import_node_fs20.default.readFileSync(absolutePath, "utf-8");
|
|
8342
|
+
if (!current.includes(MANAGED_MARKER2)) {
|
|
8228
8343
|
return {
|
|
8229
8344
|
path: file.relativePath,
|
|
8230
8345
|
action: "conflict",
|
|
@@ -8280,11 +8395,11 @@ function buildPlan(repoPath, dryRun, version) {
|
|
|
8280
8395
|
};
|
|
8281
8396
|
}
|
|
8282
8397
|
function writeFile(repoPath, file) {
|
|
8283
|
-
const absolutePath =
|
|
8284
|
-
|
|
8285
|
-
|
|
8398
|
+
const absolutePath = import_node_path20.default.join(repoPath, file.relativePath);
|
|
8399
|
+
import_node_fs20.default.mkdirSync(import_node_path20.default.dirname(absolutePath), { recursive: true });
|
|
8400
|
+
import_node_fs20.default.writeFileSync(absolutePath, file.content, "utf-8");
|
|
8286
8401
|
if (file.executable) {
|
|
8287
|
-
|
|
8402
|
+
import_node_fs20.default.chmodSync(absolutePath, 493);
|
|
8288
8403
|
}
|
|
8289
8404
|
}
|
|
8290
8405
|
function writeDevcontainer(options = {}) {
|
|
@@ -8322,16 +8437,15 @@ function writeDevcontainer(options = {}) {
|
|
|
8322
8437
|
nextSteps: postWriteNextSteps(repoPath)
|
|
8323
8438
|
};
|
|
8324
8439
|
}
|
|
8325
|
-
var
|
|
8440
|
+
var import_node_fs20, import_node_path20, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
|
|
8326
8441
|
var init_devcontainer_write = __esm({
|
|
8327
8442
|
"src/core/devcontainer-write.ts"() {
|
|
8328
8443
|
"use strict";
|
|
8329
|
-
|
|
8330
|
-
|
|
8444
|
+
import_node_fs20 = __toESM(require("fs"));
|
|
8445
|
+
import_node_path20 = __toESM(require("path"));
|
|
8331
8446
|
init_repo_config();
|
|
8332
8447
|
init_repo_inspect();
|
|
8333
|
-
|
|
8334
|
-
MANAGED_MARKER = "devrouter:managed devcontainer";
|
|
8448
|
+
MANAGED_MARKER2 = "devrouter:managed devcontainer";
|
|
8335
8449
|
DEFAULT_DEVROUTER_VERSION = "0.0.0";
|
|
8336
8450
|
DEFAULT_PNPM_VERSION = "11.6.0";
|
|
8337
8451
|
VALID_PACKAGE_VERSION_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
@@ -8719,7 +8833,7 @@ function sanitizeRouterId(value) {
|
|
|
8719
8833
|
return value.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
8720
8834
|
}
|
|
8721
8835
|
function repoHash(repoPath) {
|
|
8722
|
-
return (0, import_node_crypto5.createHash)("sha1").update(
|
|
8836
|
+
return (0, import_node_crypto5.createHash)("sha1").update(import_node_path21.default.resolve(repoPath)).digest("hex").slice(0, 12);
|
|
8723
8837
|
}
|
|
8724
8838
|
function asDockerApp(app) {
|
|
8725
8839
|
return app.runtime === "docker";
|
|
@@ -8798,11 +8912,11 @@ function prepareDockerOverlay(repoPath, appName, apps, publishTcpPorts = false)
|
|
|
8798
8912
|
if (dockerApps.length === 0) {
|
|
8799
8913
|
throw new Error("No docker apps selected to prepare compose overlay.");
|
|
8800
8914
|
}
|
|
8801
|
-
const cachePath =
|
|
8802
|
-
|
|
8803
|
-
const overlayPath =
|
|
8915
|
+
const cachePath = import_node_path21.default.join(CACHE_DIR, repoHash(repoPath), sanitizeRouterId(appName));
|
|
8916
|
+
import_node_fs21.default.mkdirSync(cachePath, { recursive: true });
|
|
8917
|
+
const overlayPath = import_node_path21.default.join(cachePath, "compose.devrouter.yml");
|
|
8804
8918
|
const overlayDocument = buildOverlayDocument(dockerApps, publishTcpPorts);
|
|
8805
|
-
|
|
8919
|
+
import_node_fs21.default.writeFileSync(overlayPath, import_yaml6.default.stringify(overlayDocument, { lineWidth: 0 }), "utf-8");
|
|
8806
8920
|
return {
|
|
8807
8921
|
overlayPath,
|
|
8808
8922
|
composeFiles: ensureComposeFiles(dockerApps),
|
|
@@ -8816,7 +8930,7 @@ function runDockerComposeUp(repoPath, composeFiles2, overlayPath, services) {
|
|
|
8816
8930
|
fileArgs.push("-f", resolved);
|
|
8817
8931
|
}
|
|
8818
8932
|
const args = ["compose", ...fileArgs, "-f", overlayPath, "up", "-d", "--wait", ...services];
|
|
8819
|
-
const result = (0,
|
|
8933
|
+
const result = (0, import_node_child_process17.spawnSync)("docker", args, {
|
|
8820
8934
|
encoding: "utf-8",
|
|
8821
8935
|
cwd: repoPath
|
|
8822
8936
|
});
|
|
@@ -8844,7 +8958,7 @@ function queryRunningComposeServices(repoPath, composeFiles2, overlayPath, servi
|
|
|
8844
8958
|
"--services",
|
|
8845
8959
|
...services
|
|
8846
8960
|
];
|
|
8847
|
-
const result = (0,
|
|
8961
|
+
const result = (0, import_node_child_process17.spawnSync)("docker", args, {
|
|
8848
8962
|
encoding: "utf-8",
|
|
8849
8963
|
cwd: repoPath
|
|
8850
8964
|
});
|
|
@@ -8870,7 +8984,7 @@ function runDockerComposeStop(repoPath, composeFiles2, overlayPath, services) {
|
|
|
8870
8984
|
fileArgs.push("-f", resolved);
|
|
8871
8985
|
}
|
|
8872
8986
|
const args = ["compose", ...fileArgs, "-f", overlayPath, "stop", ...services];
|
|
8873
|
-
const result = (0,
|
|
8987
|
+
const result = (0, import_node_child_process17.spawnSync)("docker", args, {
|
|
8874
8988
|
encoding: "utf-8",
|
|
8875
8989
|
cwd: repoPath
|
|
8876
8990
|
});
|
|
@@ -8896,7 +9010,7 @@ function runDockerComposeLogs(repoPath, composeFiles2, overlayPath, services, ta
|
|
|
8896
9010
|
String(tail),
|
|
8897
9011
|
...services
|
|
8898
9012
|
];
|
|
8899
|
-
(0,
|
|
9013
|
+
(0, import_node_child_process17.spawnSync)("docker", args, {
|
|
8900
9014
|
stdio: "inherit",
|
|
8901
9015
|
cwd: repoPath
|
|
8902
9016
|
});
|
|
@@ -8908,7 +9022,7 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
|
|
|
8908
9022
|
fileArgs.push("-f", resolved);
|
|
8909
9023
|
}
|
|
8910
9024
|
const args = ["compose", ...fileArgs, "-f", overlayPath, "port", service, String(internalPort)];
|
|
8911
|
-
const result = (0,
|
|
9025
|
+
const result = (0, import_node_child_process17.spawnSync)("docker", args, {
|
|
8912
9026
|
encoding: "utf-8",
|
|
8913
9027
|
cwd: repoPath
|
|
8914
9028
|
});
|
|
@@ -8922,14 +9036,14 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
|
|
|
8922
9036
|
const port = Number(match[1]);
|
|
8923
9037
|
return Number.isInteger(port) && port > 0 ? port : void 0;
|
|
8924
9038
|
}
|
|
8925
|
-
var
|
|
9039
|
+
var import_node_child_process17, import_node_crypto5, import_node_fs21, import_node_path21, import_yaml6;
|
|
8926
9040
|
var init_docker_run = __esm({
|
|
8927
9041
|
"src/core/docker-run.ts"() {
|
|
8928
9042
|
"use strict";
|
|
8929
|
-
|
|
9043
|
+
import_node_child_process17 = require("child_process");
|
|
8930
9044
|
import_node_crypto5 = require("crypto");
|
|
8931
|
-
|
|
8932
|
-
|
|
9045
|
+
import_node_fs21 = __toESM(require("fs"));
|
|
9046
|
+
import_node_path21 = __toESM(require("path"));
|
|
8933
9047
|
import_yaml6 = __toESM(require("yaml"));
|
|
8934
9048
|
init_docker_error_guidance();
|
|
8935
9049
|
init_paths();
|
|
@@ -9019,7 +9133,7 @@ function isProcessRunning(pid) {
|
|
|
9019
9133
|
}
|
|
9020
9134
|
}
|
|
9021
9135
|
function readProcessTree(rootPid) {
|
|
9022
|
-
const result = (0,
|
|
9136
|
+
const result = (0, import_node_child_process18.spawnSync)("ps", ["-ax", "-o", "pid=,ppid="], { encoding: "utf-8" });
|
|
9023
9137
|
if (result.status !== 0) {
|
|
9024
9138
|
return [rootPid];
|
|
9025
9139
|
}
|
|
@@ -9130,12 +9244,12 @@ function detectListeningPorts(pids) {
|
|
|
9130
9244
|
if (pids.length === 0) {
|
|
9131
9245
|
return [];
|
|
9132
9246
|
}
|
|
9133
|
-
const result = (0,
|
|
9247
|
+
const result = (0, import_node_child_process18.spawnSync)("lsof", ["-nP", "-iTCP", "-sTCP:LISTEN", "-a", "-p", pids.join(",")], {
|
|
9134
9248
|
encoding: "utf-8"
|
|
9135
9249
|
});
|
|
9136
9250
|
if (result.error && result.error.code === "ENOENT") {
|
|
9137
9251
|
if (process.platform === "linux") {
|
|
9138
|
-
const ssResult = (0,
|
|
9252
|
+
const ssResult = (0, import_node_child_process18.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
|
|
9139
9253
|
if (ssResult.status === 0 && ssResult.stdout) {
|
|
9140
9254
|
return parseSsListeningPorts(ssResult.stdout, new Set(pids));
|
|
9141
9255
|
}
|
|
@@ -9194,7 +9308,7 @@ async function runHostApp(repoPath, app, extraEnv = {}, secretManager, env, work
|
|
|
9194
9308
|
app.hostRun.command,
|
|
9195
9309
|
true
|
|
9196
9310
|
) : app.hostRun.command;
|
|
9197
|
-
const child = (0,
|
|
9311
|
+
const child = (0, import_node_child_process18.spawn)(spawnCommand, {
|
|
9198
9312
|
cwd: commandCwd,
|
|
9199
9313
|
stdio: "inherit",
|
|
9200
9314
|
shell: true,
|
|
@@ -9562,7 +9676,7 @@ async function execWithAppEnv(options) {
|
|
|
9562
9676
|
options.command[0],
|
|
9563
9677
|
true
|
|
9564
9678
|
);
|
|
9565
|
-
child = (0,
|
|
9679
|
+
child = (0, import_node_child_process18.spawn)(wrapped, {
|
|
9566
9680
|
cwd: deps.repoPath,
|
|
9567
9681
|
stdio: "inherit",
|
|
9568
9682
|
shell: true,
|
|
@@ -9576,7 +9690,7 @@ async function execWithAppEnv(options) {
|
|
|
9576
9690
|
false
|
|
9577
9691
|
);
|
|
9578
9692
|
const [cmd, ...wrappedArgs] = wrapped;
|
|
9579
|
-
child = (0,
|
|
9693
|
+
child = (0, import_node_child_process18.spawn)(cmd, wrappedArgs, {
|
|
9580
9694
|
cwd: deps.repoPath,
|
|
9581
9695
|
stdio: "inherit",
|
|
9582
9696
|
shell: false,
|
|
@@ -9584,7 +9698,7 @@ async function execWithAppEnv(options) {
|
|
|
9584
9698
|
});
|
|
9585
9699
|
}
|
|
9586
9700
|
} else if (options.shell) {
|
|
9587
|
-
child = (0,
|
|
9701
|
+
child = (0, import_node_child_process18.spawn)(options.command[0], {
|
|
9588
9702
|
cwd: deps.repoPath,
|
|
9589
9703
|
stdio: "inherit",
|
|
9590
9704
|
shell: true,
|
|
@@ -9592,7 +9706,7 @@ async function execWithAppEnv(options) {
|
|
|
9592
9706
|
});
|
|
9593
9707
|
} else {
|
|
9594
9708
|
const [command, ...args] = options.command;
|
|
9595
|
-
child = (0,
|
|
9709
|
+
child = (0, import_node_child_process18.spawn)(command, args, {
|
|
9596
9710
|
cwd: deps.repoPath,
|
|
9597
9711
|
stdio: "inherit",
|
|
9598
9712
|
shell: false,
|
|
@@ -9613,11 +9727,11 @@ async function execWithAppEnv(options) {
|
|
|
9613
9727
|
deps.stopDeps();
|
|
9614
9728
|
}
|
|
9615
9729
|
}
|
|
9616
|
-
var
|
|
9730
|
+
var import_node_child_process18, import_node_net, import_node_process, import_promises, POLL_INTERVAL_MS2, DEFAULT_PORT_TIMEOUT_MS, PROCESS_TERMINATION_GRACE_MS;
|
|
9617
9731
|
var init_app_run = __esm({
|
|
9618
9732
|
"src/core/app-run.ts"() {
|
|
9619
9733
|
"use strict";
|
|
9620
|
-
|
|
9734
|
+
import_node_child_process18 = require("child_process");
|
|
9621
9735
|
import_node_net = __toESM(require("net"));
|
|
9622
9736
|
import_node_process = require("process");
|
|
9623
9737
|
import_promises = require("readline/promises");
|
|
@@ -9878,7 +9992,7 @@ var init_version = __esm({
|
|
|
9878
9992
|
|
|
9879
9993
|
// src/cli.ts
|
|
9880
9994
|
var import_commander = require("commander");
|
|
9881
|
-
var CLI_VERSION = true ? "0.0.
|
|
9995
|
+
var CLI_VERSION = true ? "0.0.34" : "0.0.0-dev";
|
|
9882
9996
|
var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
|
|
9883
9997
|
function withErrorHandling(action2) {
|
|
9884
9998
|
return async (...args) => {
|
package/package.json
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Upgrade to devrouter 0.0.33
|
|
2
|
+
|
|
3
|
+
Devrouter now hides a known DevPod transport diagnostic emitted after a one-shot command has completed and devrouter has already recovered its real exit status. Real command stderr and exact non-zero statuses remain unchanged.
|
|
4
|
+
|
|
5
|
+
1. Install `@devrouter/cli@0.0.33` on the host and bump `.devrouter.yml` to `devrouter.version: 0.0.33`.
|
|
6
|
+
2. Keep the `0.0.32` lifecycle adoption unchanged: use `ensure` for startup, `stop` for a non-destructive pause, and `exec` for one-shot commands.
|
|
7
|
+
3. Run `devrouter repo agents --repo <checkout>` only if the repository has not already adopted the `0.0.32` lifecycle guidance.
|
|
8
|
+
|
|
9
|
+
Verification:
|
|
10
|
+
|
|
11
|
+
- Run `devrouter exec <checkout> -- true` and confirm it exits `0` without a transport diagnostic.
|
|
12
|
+
- Run a command that exits `7` and confirm devrouter returns `7` without a transport diagnostic.
|
|
13
|
+
- Write a line to command stderr and confirm that line remains visible.
|
|
14
|
+
|
|
15
|
+
Report template:
|
|
16
|
+
|
|
17
|
+
- CLI/config version: `0.0.33`
|
|
18
|
+
- Successful exec: `<exit status and stderr>`
|
|
19
|
+
- Non-zero exec: `<exit status and stderr>`
|
|
20
|
+
- Real command stderr: `<preserved or failure details>`
|
|
21
|
+
- Remaining blockers: `<none or details>`
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Upgrade to devrouter 0.0.34
|
|
2
|
+
|
|
3
|
+
Devrouter now supplies its managed process helper at reconciliation time. Consumer images must not download, install, extract, or version-pin devrouter; `.devrouter.yml` is the only consumer-side devrouter version pin.
|
|
4
|
+
|
|
5
|
+
1. Install `@devrouter/cli@0.0.34` on the host and bump `.devrouter.yml` to `devrouter.version: 0.0.34`.
|
|
6
|
+
2. In a devrouter-managed `.devcontainer/post-start.sh`, keep the repository-owned environment and application command, require `DEVROUTER_PROCESS_HELPER`, and replace direct `devrouter-process ensure` with `"$DEVROUTER_PROCESS_HELPER" ensure`.
|
|
7
|
+
3. Remove `postStartCommand` from `.devcontainer/devcontainer.json`. Canonical `devrouter ensure` delivers the helper and invokes the managed adapter after exact-container proof.
|
|
8
|
+
4. Remove any `@devrouter/cli` download or `devrouter-process` installation/extraction from the consumer Dockerfile. Retain `procps` and `util-linux`; remove `tar` if the application does not otherwise need it.
|
|
9
|
+
5. Run `devrouter repo agents --repo <checkout>` to refresh generated guidance.
|
|
10
|
+
|
|
11
|
+
Apply the adapter, devcontainer JSON, and Dockerfile migration atomically. A fully legacy image-plus-hook scaffold remains temporarily supported, but a legacy adapter paired with a helper-free image fails before DevPod startup with migration guidance.
|
|
12
|
+
|
|
13
|
+
Verification:
|
|
14
|
+
|
|
15
|
+
- Run `devrouter repo devcontainer verify --repo <checkout> --json` and confirm `repo.devcontainer.no-devrouter-image-install` is `ok`.
|
|
16
|
+
- Recreate from the helper-free Dockerfile, then run `devrouter ensure <checkout> --json` twice and confirm cold startup plus warm reuse succeed.
|
|
17
|
+
- Run `devrouter exec <checkout> -- sh -c 'test ! -e /usr/local/bin/devrouter-process'` to prove the helper is absent from the image.
|
|
18
|
+
- Run `devrouter exec <checkout> -- sh -c 'test -x /tmp/devrouter/bin/devrouter-process'` to prove the matching helper was delivered only at runtime.
|
|
19
|
+
|
|
20
|
+
Report template:
|
|
21
|
+
|
|
22
|
+
- CLI/config version: `0.0.34`
|
|
23
|
+
- Adapter migration: `<file changed or not applicable>`
|
|
24
|
+
- Image coupling diagnostic: `<ok or details>`
|
|
25
|
+
- Cold/warm ensure: `<DevPod id and route evidence>`
|
|
26
|
+
- Image helper absent: `<passed or failure details>`
|
|
27
|
+
- Runtime helper present: `<passed or failure details>`
|
|
28
|
+
- Remaining blockers: `<none or details>`
|