@devrouter/cli 0.0.33 → 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 +272 -172
- package/package.json +1 -1
- 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();
|
|
@@ -6766,7 +6891,7 @@ function ensureGuidance(repoPath) {
|
|
|
6766
6891
|
return `Run 'devrouter ensure ${repoPath}' first.`;
|
|
6767
6892
|
}
|
|
6768
6893
|
function assertRunningDevpod(devpodId, repoPath) {
|
|
6769
|
-
const result = (0,
|
|
6894
|
+
const result = (0, import_node_child_process14.spawnSync)("devpod", ["status", devpodId, "--output", "json"], {
|
|
6770
6895
|
encoding: "utf-8"
|
|
6771
6896
|
});
|
|
6772
6897
|
if (result.status !== 0) {
|
|
@@ -6822,7 +6947,7 @@ async function devpodExec(repoPath, command) {
|
|
|
6822
6947
|
wrappedCommand
|
|
6823
6948
|
];
|
|
6824
6949
|
return new Promise((resolve, reject) => {
|
|
6825
|
-
const child = (0,
|
|
6950
|
+
const child = (0, import_node_child_process14.spawn)("devpod", args, { stdio: ["inherit", "inherit", "pipe"] });
|
|
6826
6951
|
let pending = Buffer.alloc(0);
|
|
6827
6952
|
let statusBytes = Buffer.alloc(0);
|
|
6828
6953
|
let readingStatus = false;
|
|
@@ -6881,11 +7006,11 @@ async function devpodExec(repoPath, command) {
|
|
|
6881
7006
|
});
|
|
6882
7007
|
});
|
|
6883
7008
|
}
|
|
6884
|
-
var
|
|
7009
|
+
var import_node_child_process14, import_node_crypto4, DEVPOD_MISSING_EXIT_STATUS_DIAGNOSTIC;
|
|
6885
7010
|
var init_devpod_exec = __esm({
|
|
6886
7011
|
"src/core/devpod-exec.ts"() {
|
|
6887
7012
|
"use strict";
|
|
6888
|
-
|
|
7013
|
+
import_node_child_process14 = require("child_process");
|
|
6889
7014
|
import_node_crypto4 = require("crypto");
|
|
6890
7015
|
init_devpod_environment();
|
|
6891
7016
|
init_devpod_workspaces();
|
|
@@ -7070,7 +7195,7 @@ async function runOpenCommand(name) {
|
|
|
7070
7195
|
);
|
|
7071
7196
|
return;
|
|
7072
7197
|
}
|
|
7073
|
-
const result = (0,
|
|
7198
|
+
const result = (0, import_node_child_process15.spawnSync)("open", [url], { encoding: "utf-8" });
|
|
7074
7199
|
if (result.status !== 0) {
|
|
7075
7200
|
const details = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
7076
7201
|
throw new Error(`Unable to open '${url}': ${details || "unknown error"}`);
|
|
@@ -7078,11 +7203,11 @@ async function runOpenCommand(name) {
|
|
|
7078
7203
|
process.stdout.write(`Opened ${url}
|
|
7079
7204
|
`);
|
|
7080
7205
|
}
|
|
7081
|
-
var
|
|
7206
|
+
var import_node_child_process15;
|
|
7082
7207
|
var init_open = __esm({
|
|
7083
7208
|
"src/commands/open.ts"() {
|
|
7084
7209
|
"use strict";
|
|
7085
|
-
|
|
7210
|
+
import_node_child_process15 = require("child_process");
|
|
7086
7211
|
init_docker();
|
|
7087
7212
|
init_host_routes();
|
|
7088
7213
|
init_repo_config();
|
|
@@ -7105,7 +7230,7 @@ async function runLogsCommand(options) {
|
|
|
7105
7230
|
const args = ["logs", "--tail", tail, ROUTER_CONTAINER_NAME];
|
|
7106
7231
|
if (options.follow) {
|
|
7107
7232
|
args.splice(1, 0, "-f");
|
|
7108
|
-
const child = (0,
|
|
7233
|
+
const child = (0, import_node_child_process16.spawn)("docker", args, { stdio: "inherit" });
|
|
7109
7234
|
const onSignal = () => {
|
|
7110
7235
|
child.kill("SIGTERM");
|
|
7111
7236
|
};
|
|
@@ -7119,17 +7244,17 @@ async function runLogsCommand(options) {
|
|
|
7119
7244
|
});
|
|
7120
7245
|
});
|
|
7121
7246
|
} else {
|
|
7122
|
-
const result = (0,
|
|
7247
|
+
const result = (0, import_node_child_process16.spawnSync)("docker", args, { stdio: "inherit" });
|
|
7123
7248
|
if (result.status !== 0) {
|
|
7124
7249
|
throw new Error("Failed to retrieve router logs.");
|
|
7125
7250
|
}
|
|
7126
7251
|
}
|
|
7127
7252
|
}
|
|
7128
|
-
var
|
|
7253
|
+
var import_node_child_process16;
|
|
7129
7254
|
var init_logs = __esm({
|
|
7130
7255
|
"src/commands/logs.ts"() {
|
|
7131
7256
|
"use strict";
|
|
7132
|
-
|
|
7257
|
+
import_node_child_process16 = require("child_process");
|
|
7133
7258
|
init_docker();
|
|
7134
7259
|
init_router();
|
|
7135
7260
|
}
|
|
@@ -7169,17 +7294,17 @@ function asRecord3(value) {
|
|
|
7169
7294
|
return value;
|
|
7170
7295
|
}
|
|
7171
7296
|
function readJson(filePath) {
|
|
7172
|
-
if (!
|
|
7297
|
+
if (!import_node_fs18.default.existsSync(filePath)) {
|
|
7173
7298
|
return void 0;
|
|
7174
7299
|
}
|
|
7175
7300
|
try {
|
|
7176
|
-
return JSON.parse(
|
|
7301
|
+
return JSON.parse(import_node_fs18.default.readFileSync(filePath, "utf-8"));
|
|
7177
7302
|
} catch {
|
|
7178
7303
|
return void 0;
|
|
7179
7304
|
}
|
|
7180
7305
|
}
|
|
7181
7306
|
function relative(repoPath, filePath) {
|
|
7182
|
-
return
|
|
7307
|
+
return import_node_path18.default.relative(repoPath, filePath) || ".";
|
|
7183
7308
|
}
|
|
7184
7309
|
function redactEnvAssignments(value) {
|
|
7185
7310
|
return value.replace(
|
|
@@ -7220,7 +7345,7 @@ function inspectPackageManager(repoPath, pkg) {
|
|
|
7220
7345
|
["bun.lock", "bun"]
|
|
7221
7346
|
];
|
|
7222
7347
|
for (const [fileName, name] of lockfiles) {
|
|
7223
|
-
if (
|
|
7348
|
+
if (import_node_fs18.default.existsSync(import_node_path18.default.join(repoPath, fileName))) {
|
|
7224
7349
|
return { name, source: fileName };
|
|
7225
7350
|
}
|
|
7226
7351
|
}
|
|
@@ -7235,9 +7360,9 @@ function inspectNode(repoPath, pkg) {
|
|
|
7235
7360
|
if (typeof engines?.node === "string") {
|
|
7236
7361
|
return { version: engines.node, source: "package.json:engines.node" };
|
|
7237
7362
|
}
|
|
7238
|
-
const nvmrc =
|
|
7239
|
-
if (
|
|
7240
|
-
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();
|
|
7241
7366
|
return { version, source: ".nvmrc" };
|
|
7242
7367
|
}
|
|
7243
7368
|
return void 0;
|
|
@@ -7298,7 +7423,7 @@ function configuredComposeFiles(repoPath) {
|
|
|
7298
7423
|
const files = config.apps.filter(
|
|
7299
7424
|
(app) => app.runtime === "docker"
|
|
7300
7425
|
).flatMap((app) => app.docker.composeFiles).filter(
|
|
7301
|
-
(fileName) => !
|
|
7426
|
+
(fileName) => !import_node_path18.default.isAbsolute(fileName) && !import_node_path18.default.normalize(fileName).startsWith("..")
|
|
7302
7427
|
);
|
|
7303
7428
|
return Array.from(new Set(files));
|
|
7304
7429
|
} catch {
|
|
@@ -7316,7 +7441,7 @@ function composeFiles(repoPath) {
|
|
|
7316
7441
|
...configuredComposeFiles(repoPath)
|
|
7317
7442
|
];
|
|
7318
7443
|
return Array.from(new Set(candidates)).filter(
|
|
7319
|
-
(fileName) =>
|
|
7444
|
+
(fileName) => import_node_fs18.default.existsSync(import_node_path18.default.join(repoPath, fileName))
|
|
7320
7445
|
);
|
|
7321
7446
|
}
|
|
7322
7447
|
function stringArray(value) {
|
|
@@ -7354,7 +7479,7 @@ function inspectServices(repoPath) {
|
|
|
7354
7479
|
const services = [];
|
|
7355
7480
|
for (const fileName of composeFiles(repoPath)) {
|
|
7356
7481
|
try {
|
|
7357
|
-
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"));
|
|
7358
7483
|
const serviceMap = asRecord3(asRecord3(parsed)?.services);
|
|
7359
7484
|
for (const [name, value] of Object.entries(serviceMap ?? {})) {
|
|
7360
7485
|
const service = asRecord3(value);
|
|
@@ -7389,8 +7514,8 @@ function inspectServices(repoPath) {
|
|
|
7389
7514
|
return services;
|
|
7390
7515
|
}
|
|
7391
7516
|
function inspectEnvFiles(repoPath) {
|
|
7392
|
-
const files =
|
|
7393
|
-
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");
|
|
7394
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();
|
|
7395
7520
|
return { path: fileName, names };
|
|
7396
7521
|
});
|
|
@@ -7404,18 +7529,18 @@ function inspectEnvFiles(repoPath) {
|
|
|
7404
7529
|
};
|
|
7405
7530
|
}
|
|
7406
7531
|
function inspectDevcontainer(repoPath) {
|
|
7407
|
-
const dir =
|
|
7408
|
-
if (!
|
|
7532
|
+
const dir = import_node_path18.default.join(repoPath, ".devcontainer");
|
|
7533
|
+
if (!import_node_fs18.default.existsSync(dir)) {
|
|
7409
7534
|
return { exists: false, files: [] };
|
|
7410
7535
|
}
|
|
7411
7536
|
return {
|
|
7412
7537
|
exists: true,
|
|
7413
|
-
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}`)
|
|
7414
7539
|
};
|
|
7415
7540
|
}
|
|
7416
7541
|
function inspectDevrouter(repoPath) {
|
|
7417
7542
|
const configPath = getRepoConfigPath(repoPath);
|
|
7418
|
-
if (!
|
|
7543
|
+
if (!import_node_fs18.default.existsSync(configPath)) {
|
|
7419
7544
|
return {
|
|
7420
7545
|
exists: false,
|
|
7421
7546
|
configPath,
|
|
@@ -7459,15 +7584,15 @@ function inspectAgentGuidance(repoPath) {
|
|
|
7459
7584
|
["AGENTS.md", "agents"],
|
|
7460
7585
|
["CLAUDE.md", "claude"]
|
|
7461
7586
|
]) {
|
|
7462
|
-
if (
|
|
7587
|
+
if (import_node_fs18.default.existsSync(import_node_path18.default.join(repoPath, fileName))) {
|
|
7463
7588
|
results.push({ path: fileName, kind });
|
|
7464
7589
|
}
|
|
7465
7590
|
}
|
|
7466
|
-
const skillsDir =
|
|
7467
|
-
if (
|
|
7468
|
-
for (const name of
|
|
7469
|
-
const skillPath =
|
|
7470
|
-
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)) {
|
|
7471
7596
|
results.push({ path: relative(repoPath, skillPath), kind: "skill" });
|
|
7472
7597
|
}
|
|
7473
7598
|
}
|
|
@@ -7510,7 +7635,7 @@ function buildIssues(report) {
|
|
|
7510
7635
|
}
|
|
7511
7636
|
function inspectRepo(options = {}) {
|
|
7512
7637
|
const repoPath = resolveRepoPath(options.repo);
|
|
7513
|
-
const pkg = readJson(
|
|
7638
|
+
const pkg = readJson(import_node_path18.default.join(repoPath, "package.json"));
|
|
7514
7639
|
const scripts = inspectScripts(pkg);
|
|
7515
7640
|
const reportWithoutIssues = {
|
|
7516
7641
|
repoPath,
|
|
@@ -7529,12 +7654,12 @@ function inspectRepo(options = {}) {
|
|
|
7529
7654
|
issues: buildIssues(reportWithoutIssues)
|
|
7530
7655
|
};
|
|
7531
7656
|
}
|
|
7532
|
-
var
|
|
7657
|
+
var import_node_fs18, import_node_path18, import_yaml5;
|
|
7533
7658
|
var init_repo_inspect = __esm({
|
|
7534
7659
|
"src/core/repo-inspect.ts"() {
|
|
7535
7660
|
"use strict";
|
|
7536
|
-
|
|
7537
|
-
|
|
7661
|
+
import_node_fs18 = __toESM(require("fs"));
|
|
7662
|
+
import_node_path18 = __toESM(require("path"));
|
|
7538
7663
|
import_yaml5 = __toESM(require("yaml"));
|
|
7539
7664
|
init_repo_config();
|
|
7540
7665
|
}
|
|
@@ -7639,7 +7764,7 @@ function requiredFileChecks(repoPath) {
|
|
|
7639
7764
|
".devcontainer/docker-compose.yml",
|
|
7640
7765
|
".devrouter.yml"
|
|
7641
7766
|
];
|
|
7642
|
-
const missing = required.filter((fileName) => !
|
|
7767
|
+
const missing = required.filter((fileName) => !import_node_fs19.default.existsSync(import_node_path19.default.join(repoPath, fileName)));
|
|
7643
7768
|
return {
|
|
7644
7769
|
id: "repo.devcontainer.verify-files",
|
|
7645
7770
|
level: missing.length === 0 ? "ok" : "error",
|
|
@@ -7858,12 +7983,12 @@ async function verifyDevcontainer(options = {}) {
|
|
|
7858
7983
|
nextSteps: collectNextSteps3(checks)
|
|
7859
7984
|
};
|
|
7860
7985
|
}
|
|
7861
|
-
var
|
|
7986
|
+
var import_node_fs19, import_node_path19;
|
|
7862
7987
|
var init_devcontainer_verify = __esm({
|
|
7863
7988
|
"src/core/devcontainer-verify.ts"() {
|
|
7864
7989
|
"use strict";
|
|
7865
|
-
|
|
7866
|
-
|
|
7990
|
+
import_node_fs19 = __toESM(require("fs"));
|
|
7991
|
+
import_node_path19 = __toESM(require("path"));
|
|
7867
7992
|
init_capabilities();
|
|
7868
7993
|
init_doctor();
|
|
7869
7994
|
init_host_routes();
|
|
@@ -7901,30 +8026,22 @@ function inferDevProcess(repo) {
|
|
|
7901
8026
|
function inferPort2(repo) {
|
|
7902
8027
|
return repo.apps.find((app) => app.port)?.port ?? 3e3;
|
|
7903
8028
|
}
|
|
7904
|
-
function renderDockerfile(nodeMajor, pnpmVersion
|
|
8029
|
+
function renderDockerfile(nodeMajor, pnpmVersion) {
|
|
7905
8030
|
const pnpmPackageSpec = `pnpm@${pnpmVersion}`;
|
|
7906
|
-
|
|
7907
|
-
const devrouterTarball = `devrouter-cli-${version}.tgz`;
|
|
7908
|
-
return `# ${MANAGED_MARKER}
|
|
8031
|
+
return `# ${MANAGED_MARKER2}
|
|
7909
8032
|
FROM node:${nodeMajor}-bookworm-slim
|
|
7910
8033
|
|
|
7911
8034
|
RUN apt-get update \\
|
|
7912
|
-
&& 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 \\
|
|
7913
8036
|
&& rm -rf /var/lib/apt/lists/*
|
|
7914
8037
|
|
|
7915
8038
|
RUN npm install -g ${shellSingleQuote(pnpmPackageSpec)}
|
|
7916
8039
|
|
|
7917
|
-
RUN npm pack --silent ${shellSingleQuote(devrouterPackageSpec)} \\
|
|
7918
|
-
&& tar -xzf ${shellSingleQuote(devrouterTarball)} --strip-components=2 \\
|
|
7919
|
-
-C /usr/local/bin package/bin/devrouter-process \\
|
|
7920
|
-
&& chmod +x /usr/local/bin/devrouter-process \\
|
|
7921
|
-
&& rm ${shellSingleQuote(devrouterTarball)}
|
|
7922
|
-
|
|
7923
8040
|
WORKDIR /workspaces/app
|
|
7924
8041
|
`;
|
|
7925
8042
|
}
|
|
7926
8043
|
function renderCompose(projectName) {
|
|
7927
|
-
return `# ${
|
|
8044
|
+
return `# ${MANAGED_MARKER2}
|
|
7928
8045
|
services:
|
|
7929
8046
|
postgres:
|
|
7930
8047
|
image: postgres:16-alpine
|
|
@@ -7977,12 +8094,12 @@ volumes:
|
|
|
7977
8094
|
`;
|
|
7978
8095
|
}
|
|
7979
8096
|
function renderDefaultComposeOverlay() {
|
|
7980
|
-
return `# ${
|
|
8097
|
+
return `# ${MANAGED_MARKER2}
|
|
7981
8098
|
services: {}
|
|
7982
8099
|
`;
|
|
7983
8100
|
}
|
|
7984
8101
|
function renderDevrouterComposeOverlay() {
|
|
7985
|
-
return `# ${
|
|
8102
|
+
return `# ${MANAGED_MARKER2}
|
|
7986
8103
|
# A linked worktree's .git file points at the host repository's common Git
|
|
7987
8104
|
# directory. workspace ensure sets this absolute path before DevPod starts.
|
|
7988
8105
|
services:
|
|
@@ -7998,7 +8115,7 @@ services:
|
|
|
7998
8115
|
}
|
|
7999
8116
|
function renderInitDb() {
|
|
8000
8117
|
return `#!/usr/bin/env bash
|
|
8001
|
-
# ${
|
|
8118
|
+
# ${MANAGED_MARKER2}
|
|
8002
8119
|
set -euo pipefail
|
|
8003
8120
|
|
|
8004
8121
|
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<'SQL'
|
|
@@ -8017,10 +8134,9 @@ function renderDevcontainerJson(projectName) {
|
|
|
8017
8134
|
"service": "app",
|
|
8018
8135
|
"workspaceFolder": "/workspaces/${projectName}",
|
|
8019
8136
|
"postCreateCommand": "bash .devcontainer/post-create.sh",
|
|
8020
|
-
"postStartCommand": "bash .devcontainer/post-start.sh",
|
|
8021
8137
|
"customizations": {
|
|
8022
8138
|
"devrouter": {
|
|
8023
|
-
"managed": "${
|
|
8139
|
+
"managed": "${MANAGED_MARKER2}"
|
|
8024
8140
|
},
|
|
8025
8141
|
"vscode": {
|
|
8026
8142
|
"extensions": []
|
|
@@ -8030,7 +8146,7 @@ function renderDevcontainerJson(projectName) {
|
|
|
8030
8146
|
`;
|
|
8031
8147
|
}
|
|
8032
8148
|
function renderEnv(projectName, port) {
|
|
8033
|
-
return `# ${
|
|
8149
|
+
return `# ${MANAGED_MARKER2}
|
|
8034
8150
|
WORKSPACE=${projectName}
|
|
8035
8151
|
HOST=0.0.0.0
|
|
8036
8152
|
HOSTNAME=0.0.0.0
|
|
@@ -8041,7 +8157,7 @@ SHADOW_DATABASE_URL=postgres://prisma:prisma@postgres:5432/shadow
|
|
|
8041
8157
|
}
|
|
8042
8158
|
function renderPostCreate() {
|
|
8043
8159
|
return `#!/usr/bin/env bash
|
|
8044
|
-
# ${
|
|
8160
|
+
# ${MANAGED_MARKER2}
|
|
8045
8161
|
set -euo pipefail
|
|
8046
8162
|
|
|
8047
8163
|
export CI=true
|
|
@@ -8057,7 +8173,7 @@ function shellSingleQuote(value) {
|
|
|
8057
8173
|
}
|
|
8058
8174
|
function renderPostStart(devProcess) {
|
|
8059
8175
|
return `#!/usr/bin/env bash
|
|
8060
|
-
# ${
|
|
8176
|
+
# ${MANAGED_MARKER2}
|
|
8061
8177
|
set -euo pipefail
|
|
8062
8178
|
|
|
8063
8179
|
export CI=true
|
|
@@ -8066,7 +8182,9 @@ set -a
|
|
|
8066
8182
|
. .devcontainer/devcontainer.env
|
|
8067
8183
|
set +a
|
|
8068
8184
|
|
|
8069
|
-
devrouter
|
|
8185
|
+
: "\${DEVROUTER_PROCESS_HELPER:?Run devrouter ensure to start this managed application process.}"
|
|
8186
|
+
|
|
8187
|
+
"$DEVROUTER_PROCESS_HELPER" ensure \\
|
|
8070
8188
|
--name app \\
|
|
8071
8189
|
--match ${shellSingleQuote(devProcess.match)} \\
|
|
8072
8190
|
--log /tmp/devrouter-app.log \\
|
|
@@ -8074,7 +8192,7 @@ devrouter-process ensure \\
|
|
|
8074
8192
|
`;
|
|
8075
8193
|
}
|
|
8076
8194
|
function renderDevrouter(projectName, port, version) {
|
|
8077
|
-
return `# ${
|
|
8195
|
+
return `# ${MANAGED_MARKER2}
|
|
8078
8196
|
version: 1
|
|
8079
8197
|
devrouter:
|
|
8080
8198
|
version: ${version}
|
|
@@ -8096,24 +8214,16 @@ apps:
|
|
|
8096
8214
|
`;
|
|
8097
8215
|
}
|
|
8098
8216
|
function renderReadme(projectName) {
|
|
8099
|
-
return `<!-- ${
|
|
8217
|
+
return `<!-- ${MANAGED_MARKER2} -->
|
|
8100
8218
|
# Devcontainer
|
|
8101
8219
|
|
|
8102
8220
|
Use this repo through the devcontainer, with devrouter providing stable local routes.
|
|
8103
8221
|
|
|
8104
|
-
Primary checkout:
|
|
8222
|
+
Primary or linked checkout:
|
|
8105
8223
|
|
|
8106
8224
|
\`\`\`bash
|
|
8107
8225
|
devrouter setup --yes
|
|
8108
|
-
|
|
8109
|
-
devrouter app run app --repo . --yes
|
|
8110
|
-
devrouter app run db --repo . --yes
|
|
8111
|
-
\`\`\`
|
|
8112
|
-
|
|
8113
|
-
Existing linked worktree:
|
|
8114
|
-
|
|
8115
|
-
\`\`\`bash
|
|
8116
|
-
devrouter workspace ensure .
|
|
8226
|
+
devrouter ensure .
|
|
8117
8227
|
\`\`\`
|
|
8118
8228
|
|
|
8119
8229
|
- App: https://${projectName}.localhost
|
|
@@ -8122,18 +8232,9 @@ devrouter workspace ensure .
|
|
|
8122
8232
|
}
|
|
8123
8233
|
function postWriteNextSteps(repoPath) {
|
|
8124
8234
|
const quotedRepoPath = shellSingleQuote(repoPath);
|
|
8125
|
-
if (isLinkedWorktree(repoPath)) {
|
|
8126
|
-
return [
|
|
8127
|
-
`Run: devrouter setup --repo ${quotedRepoPath} --yes`,
|
|
8128
|
-
`Run: devrouter workspace ensure ${quotedRepoPath}`,
|
|
8129
|
-
`Optional: devrouter repo agents --repo ${quotedRepoPath}`
|
|
8130
|
-
];
|
|
8131
|
-
}
|
|
8132
8235
|
return [
|
|
8133
8236
|
`Run: devrouter setup --repo ${quotedRepoPath} --yes`,
|
|
8134
|
-
`Run:
|
|
8135
|
-
`Run: devrouter app run app --repo ${quotedRepoPath} --yes`,
|
|
8136
|
-
`Run: devrouter app run db --repo ${quotedRepoPath} --yes`,
|
|
8237
|
+
`Run: devrouter ensure ${quotedRepoPath}`,
|
|
8137
8238
|
`Optional: devrouter repo agents --repo ${quotedRepoPath}`
|
|
8138
8239
|
];
|
|
8139
8240
|
}
|
|
@@ -8183,7 +8284,7 @@ function packageManagerIssues(repo) {
|
|
|
8183
8284
|
}
|
|
8184
8285
|
function plannedFiles(repoPath, version) {
|
|
8185
8286
|
const repo = inspectRepo({ repo: repoPath });
|
|
8186
|
-
const projectName = sanitizeProjectName(
|
|
8287
|
+
const projectName = sanitizeProjectName(import_node_path20.default.basename(repo.repoPath));
|
|
8187
8288
|
const nodeMajor = majorVersion(repo.node?.version, "24");
|
|
8188
8289
|
const pnpmVersion = repo.packageManager?.name === "pnpm" && repo.packageManager.version ? repo.packageManager.version : DEFAULT_PNPM_VERSION;
|
|
8189
8290
|
const port = inferPort2(repo);
|
|
@@ -8195,7 +8296,7 @@ function plannedFiles(repoPath, version) {
|
|
|
8195
8296
|
files: [
|
|
8196
8297
|
{
|
|
8197
8298
|
relativePath: ".devcontainer/Dockerfile",
|
|
8198
|
-
content: renderDockerfile(nodeMajor, pnpmVersion
|
|
8299
|
+
content: renderDockerfile(nodeMajor, pnpmVersion)
|
|
8199
8300
|
},
|
|
8200
8301
|
{ relativePath: ".devcontainer/docker-compose.yml", content: renderCompose(projectName) },
|
|
8201
8302
|
{
|
|
@@ -8228,8 +8329,8 @@ function plannedFiles(repoPath, version) {
|
|
|
8228
8329
|
};
|
|
8229
8330
|
}
|
|
8230
8331
|
function classifyFile(repoPath, file) {
|
|
8231
|
-
const absolutePath =
|
|
8232
|
-
if (!
|
|
8332
|
+
const absolutePath = import_node_path20.default.join(repoPath, file.relativePath);
|
|
8333
|
+
if (!import_node_fs20.default.existsSync(absolutePath)) {
|
|
8233
8334
|
return {
|
|
8234
8335
|
path: file.relativePath,
|
|
8235
8336
|
action: "create",
|
|
@@ -8237,8 +8338,8 @@ function classifyFile(repoPath, file) {
|
|
|
8237
8338
|
bytes: Buffer.byteLength(file.content)
|
|
8238
8339
|
};
|
|
8239
8340
|
}
|
|
8240
|
-
const current =
|
|
8241
|
-
if (!current.includes(
|
|
8341
|
+
const current = import_node_fs20.default.readFileSync(absolutePath, "utf-8");
|
|
8342
|
+
if (!current.includes(MANAGED_MARKER2)) {
|
|
8242
8343
|
return {
|
|
8243
8344
|
path: file.relativePath,
|
|
8244
8345
|
action: "conflict",
|
|
@@ -8294,11 +8395,11 @@ function buildPlan(repoPath, dryRun, version) {
|
|
|
8294
8395
|
};
|
|
8295
8396
|
}
|
|
8296
8397
|
function writeFile(repoPath, file) {
|
|
8297
|
-
const absolutePath =
|
|
8298
|
-
|
|
8299
|
-
|
|
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");
|
|
8300
8401
|
if (file.executable) {
|
|
8301
|
-
|
|
8402
|
+
import_node_fs20.default.chmodSync(absolutePath, 493);
|
|
8302
8403
|
}
|
|
8303
8404
|
}
|
|
8304
8405
|
function writeDevcontainer(options = {}) {
|
|
@@ -8336,16 +8437,15 @@ function writeDevcontainer(options = {}) {
|
|
|
8336
8437
|
nextSteps: postWriteNextSteps(repoPath)
|
|
8337
8438
|
};
|
|
8338
8439
|
}
|
|
8339
|
-
var
|
|
8440
|
+
var import_node_fs20, import_node_path20, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
|
|
8340
8441
|
var init_devcontainer_write = __esm({
|
|
8341
8442
|
"src/core/devcontainer-write.ts"() {
|
|
8342
8443
|
"use strict";
|
|
8343
|
-
|
|
8344
|
-
|
|
8444
|
+
import_node_fs20 = __toESM(require("fs"));
|
|
8445
|
+
import_node_path20 = __toESM(require("path"));
|
|
8345
8446
|
init_repo_config();
|
|
8346
8447
|
init_repo_inspect();
|
|
8347
|
-
|
|
8348
|
-
MANAGED_MARKER = "devrouter:managed devcontainer";
|
|
8448
|
+
MANAGED_MARKER2 = "devrouter:managed devcontainer";
|
|
8349
8449
|
DEFAULT_DEVROUTER_VERSION = "0.0.0";
|
|
8350
8450
|
DEFAULT_PNPM_VERSION = "11.6.0";
|
|
8351
8451
|
VALID_PACKAGE_VERSION_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
@@ -8733,7 +8833,7 @@ function sanitizeRouterId(value) {
|
|
|
8733
8833
|
return value.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
8734
8834
|
}
|
|
8735
8835
|
function repoHash(repoPath) {
|
|
8736
|
-
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);
|
|
8737
8837
|
}
|
|
8738
8838
|
function asDockerApp(app) {
|
|
8739
8839
|
return app.runtime === "docker";
|
|
@@ -8812,11 +8912,11 @@ function prepareDockerOverlay(repoPath, appName, apps, publishTcpPorts = false)
|
|
|
8812
8912
|
if (dockerApps.length === 0) {
|
|
8813
8913
|
throw new Error("No docker apps selected to prepare compose overlay.");
|
|
8814
8914
|
}
|
|
8815
|
-
const cachePath =
|
|
8816
|
-
|
|
8817
|
-
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");
|
|
8818
8918
|
const overlayDocument = buildOverlayDocument(dockerApps, publishTcpPorts);
|
|
8819
|
-
|
|
8919
|
+
import_node_fs21.default.writeFileSync(overlayPath, import_yaml6.default.stringify(overlayDocument, { lineWidth: 0 }), "utf-8");
|
|
8820
8920
|
return {
|
|
8821
8921
|
overlayPath,
|
|
8822
8922
|
composeFiles: ensureComposeFiles(dockerApps),
|
|
@@ -8830,7 +8930,7 @@ function runDockerComposeUp(repoPath, composeFiles2, overlayPath, services) {
|
|
|
8830
8930
|
fileArgs.push("-f", resolved);
|
|
8831
8931
|
}
|
|
8832
8932
|
const args = ["compose", ...fileArgs, "-f", overlayPath, "up", "-d", "--wait", ...services];
|
|
8833
|
-
const result = (0,
|
|
8933
|
+
const result = (0, import_node_child_process17.spawnSync)("docker", args, {
|
|
8834
8934
|
encoding: "utf-8",
|
|
8835
8935
|
cwd: repoPath
|
|
8836
8936
|
});
|
|
@@ -8858,7 +8958,7 @@ function queryRunningComposeServices(repoPath, composeFiles2, overlayPath, servi
|
|
|
8858
8958
|
"--services",
|
|
8859
8959
|
...services
|
|
8860
8960
|
];
|
|
8861
|
-
const result = (0,
|
|
8961
|
+
const result = (0, import_node_child_process17.spawnSync)("docker", args, {
|
|
8862
8962
|
encoding: "utf-8",
|
|
8863
8963
|
cwd: repoPath
|
|
8864
8964
|
});
|
|
@@ -8884,7 +8984,7 @@ function runDockerComposeStop(repoPath, composeFiles2, overlayPath, services) {
|
|
|
8884
8984
|
fileArgs.push("-f", resolved);
|
|
8885
8985
|
}
|
|
8886
8986
|
const args = ["compose", ...fileArgs, "-f", overlayPath, "stop", ...services];
|
|
8887
|
-
const result = (0,
|
|
8987
|
+
const result = (0, import_node_child_process17.spawnSync)("docker", args, {
|
|
8888
8988
|
encoding: "utf-8",
|
|
8889
8989
|
cwd: repoPath
|
|
8890
8990
|
});
|
|
@@ -8910,7 +9010,7 @@ function runDockerComposeLogs(repoPath, composeFiles2, overlayPath, services, ta
|
|
|
8910
9010
|
String(tail),
|
|
8911
9011
|
...services
|
|
8912
9012
|
];
|
|
8913
|
-
(0,
|
|
9013
|
+
(0, import_node_child_process17.spawnSync)("docker", args, {
|
|
8914
9014
|
stdio: "inherit",
|
|
8915
9015
|
cwd: repoPath
|
|
8916
9016
|
});
|
|
@@ -8922,7 +9022,7 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
|
|
|
8922
9022
|
fileArgs.push("-f", resolved);
|
|
8923
9023
|
}
|
|
8924
9024
|
const args = ["compose", ...fileArgs, "-f", overlayPath, "port", service, String(internalPort)];
|
|
8925
|
-
const result = (0,
|
|
9025
|
+
const result = (0, import_node_child_process17.spawnSync)("docker", args, {
|
|
8926
9026
|
encoding: "utf-8",
|
|
8927
9027
|
cwd: repoPath
|
|
8928
9028
|
});
|
|
@@ -8936,14 +9036,14 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
|
|
|
8936
9036
|
const port = Number(match[1]);
|
|
8937
9037
|
return Number.isInteger(port) && port > 0 ? port : void 0;
|
|
8938
9038
|
}
|
|
8939
|
-
var
|
|
9039
|
+
var import_node_child_process17, import_node_crypto5, import_node_fs21, import_node_path21, import_yaml6;
|
|
8940
9040
|
var init_docker_run = __esm({
|
|
8941
9041
|
"src/core/docker-run.ts"() {
|
|
8942
9042
|
"use strict";
|
|
8943
|
-
|
|
9043
|
+
import_node_child_process17 = require("child_process");
|
|
8944
9044
|
import_node_crypto5 = require("crypto");
|
|
8945
|
-
|
|
8946
|
-
|
|
9045
|
+
import_node_fs21 = __toESM(require("fs"));
|
|
9046
|
+
import_node_path21 = __toESM(require("path"));
|
|
8947
9047
|
import_yaml6 = __toESM(require("yaml"));
|
|
8948
9048
|
init_docker_error_guidance();
|
|
8949
9049
|
init_paths();
|
|
@@ -9033,7 +9133,7 @@ function isProcessRunning(pid) {
|
|
|
9033
9133
|
}
|
|
9034
9134
|
}
|
|
9035
9135
|
function readProcessTree(rootPid) {
|
|
9036
|
-
const result = (0,
|
|
9136
|
+
const result = (0, import_node_child_process18.spawnSync)("ps", ["-ax", "-o", "pid=,ppid="], { encoding: "utf-8" });
|
|
9037
9137
|
if (result.status !== 0) {
|
|
9038
9138
|
return [rootPid];
|
|
9039
9139
|
}
|
|
@@ -9144,12 +9244,12 @@ function detectListeningPorts(pids) {
|
|
|
9144
9244
|
if (pids.length === 0) {
|
|
9145
9245
|
return [];
|
|
9146
9246
|
}
|
|
9147
|
-
const result = (0,
|
|
9247
|
+
const result = (0, import_node_child_process18.spawnSync)("lsof", ["-nP", "-iTCP", "-sTCP:LISTEN", "-a", "-p", pids.join(",")], {
|
|
9148
9248
|
encoding: "utf-8"
|
|
9149
9249
|
});
|
|
9150
9250
|
if (result.error && result.error.code === "ENOENT") {
|
|
9151
9251
|
if (process.platform === "linux") {
|
|
9152
|
-
const ssResult = (0,
|
|
9252
|
+
const ssResult = (0, import_node_child_process18.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
|
|
9153
9253
|
if (ssResult.status === 0 && ssResult.stdout) {
|
|
9154
9254
|
return parseSsListeningPorts(ssResult.stdout, new Set(pids));
|
|
9155
9255
|
}
|
|
@@ -9208,7 +9308,7 @@ async function runHostApp(repoPath, app, extraEnv = {}, secretManager, env, work
|
|
|
9208
9308
|
app.hostRun.command,
|
|
9209
9309
|
true
|
|
9210
9310
|
) : app.hostRun.command;
|
|
9211
|
-
const child = (0,
|
|
9311
|
+
const child = (0, import_node_child_process18.spawn)(spawnCommand, {
|
|
9212
9312
|
cwd: commandCwd,
|
|
9213
9313
|
stdio: "inherit",
|
|
9214
9314
|
shell: true,
|
|
@@ -9576,7 +9676,7 @@ async function execWithAppEnv(options) {
|
|
|
9576
9676
|
options.command[0],
|
|
9577
9677
|
true
|
|
9578
9678
|
);
|
|
9579
|
-
child = (0,
|
|
9679
|
+
child = (0, import_node_child_process18.spawn)(wrapped, {
|
|
9580
9680
|
cwd: deps.repoPath,
|
|
9581
9681
|
stdio: "inherit",
|
|
9582
9682
|
shell: true,
|
|
@@ -9590,7 +9690,7 @@ async function execWithAppEnv(options) {
|
|
|
9590
9690
|
false
|
|
9591
9691
|
);
|
|
9592
9692
|
const [cmd, ...wrappedArgs] = wrapped;
|
|
9593
|
-
child = (0,
|
|
9693
|
+
child = (0, import_node_child_process18.spawn)(cmd, wrappedArgs, {
|
|
9594
9694
|
cwd: deps.repoPath,
|
|
9595
9695
|
stdio: "inherit",
|
|
9596
9696
|
shell: false,
|
|
@@ -9598,7 +9698,7 @@ async function execWithAppEnv(options) {
|
|
|
9598
9698
|
});
|
|
9599
9699
|
}
|
|
9600
9700
|
} else if (options.shell) {
|
|
9601
|
-
child = (0,
|
|
9701
|
+
child = (0, import_node_child_process18.spawn)(options.command[0], {
|
|
9602
9702
|
cwd: deps.repoPath,
|
|
9603
9703
|
stdio: "inherit",
|
|
9604
9704
|
shell: true,
|
|
@@ -9606,7 +9706,7 @@ async function execWithAppEnv(options) {
|
|
|
9606
9706
|
});
|
|
9607
9707
|
} else {
|
|
9608
9708
|
const [command, ...args] = options.command;
|
|
9609
|
-
child = (0,
|
|
9709
|
+
child = (0, import_node_child_process18.spawn)(command, args, {
|
|
9610
9710
|
cwd: deps.repoPath,
|
|
9611
9711
|
stdio: "inherit",
|
|
9612
9712
|
shell: false,
|
|
@@ -9627,11 +9727,11 @@ async function execWithAppEnv(options) {
|
|
|
9627
9727
|
deps.stopDeps();
|
|
9628
9728
|
}
|
|
9629
9729
|
}
|
|
9630
|
-
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;
|
|
9631
9731
|
var init_app_run = __esm({
|
|
9632
9732
|
"src/core/app-run.ts"() {
|
|
9633
9733
|
"use strict";
|
|
9634
|
-
|
|
9734
|
+
import_node_child_process18 = require("child_process");
|
|
9635
9735
|
import_node_net = __toESM(require("net"));
|
|
9636
9736
|
import_node_process = require("process");
|
|
9637
9737
|
import_promises = require("readline/promises");
|
|
@@ -9892,7 +9992,7 @@ var init_version = __esm({
|
|
|
9892
9992
|
|
|
9893
9993
|
// src/cli.ts
|
|
9894
9994
|
var import_commander = require("commander");
|
|
9895
|
-
var CLI_VERSION = true ? "0.0.
|
|
9995
|
+
var CLI_VERSION = true ? "0.0.34" : "0.0.0-dev";
|
|
9896
9996
|
var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
|
|
9897
9997
|
function withErrorHandling(action2) {
|
|
9898
9998
|
return async (...args) => {
|
package/package.json
CHANGED
|
@@ -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>`
|