@devrouter/cli 0.0.29 → 0.0.30
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 -0
- package/bin/devrouter-process +274 -0
- package/dist/devrouter.js +31 -19
- package/package.json +6 -3
- package/upgrade-prompts/0.0.30.md +13 -0
package/README.md
CHANGED
|
@@ -113,6 +113,11 @@ vars. Use it when you are not (yet) on a devcontainer. Fully supported.
|
|
|
113
113
|
The current `devrouter repo devcontainer write` scaffold is intentionally narrow:
|
|
114
114
|
Node + pnpm + Postgres. Other package managers stop with a JSON diagnostic
|
|
115
115
|
instead of writing files that would need manual repair.
|
|
116
|
+
The generated image extracts `devrouter-process` from the exact Devrouter
|
|
117
|
+
package tarball without installing the CLI dependency tree. Its `post-start.sh`
|
|
118
|
+
uses that helper for locked, owned, idempotent background startup. Application
|
|
119
|
+
commands and environment setup remain repository-owned; route readiness remains
|
|
120
|
+
part of `devrouter workspace ensure`.
|
|
116
121
|
Use `devrouter repo devcontainer verify --json` for read-only PR evidence; add
|
|
117
122
|
`--live --yes` only after the devcontainer is running and route probes should
|
|
118
123
|
mutate local route state.
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
usage() {
|
|
5
|
+
cat <<'EOF'
|
|
6
|
+
Usage:
|
|
7
|
+
devrouter-process ensure --name <name> --match <regex> [options] -- <command> [args...]
|
|
8
|
+
|
|
9
|
+
Options:
|
|
10
|
+
--fingerprint <value> Runtime identity. Defaults to command plus workspace identity.
|
|
11
|
+
--log <path> Log file. Defaults to /tmp/devrouter-process-<name>.log.
|
|
12
|
+
EOF
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
die() {
|
|
16
|
+
echo "[devrouter-process] $*" >&2
|
|
17
|
+
exit 1
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
require_value() {
|
|
21
|
+
[ "$#" -ge 2 ] || die "$1 requires a value."
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
[ "${1:-}" = "ensure" ] || {
|
|
25
|
+
usage >&2
|
|
26
|
+
exit 1
|
|
27
|
+
}
|
|
28
|
+
shift
|
|
29
|
+
|
|
30
|
+
name=""
|
|
31
|
+
process_match=""
|
|
32
|
+
fingerprint=""
|
|
33
|
+
log_file=""
|
|
34
|
+
|
|
35
|
+
while [ "$#" -gt 0 ]; do
|
|
36
|
+
case "$1" in
|
|
37
|
+
--name)
|
|
38
|
+
require_value "$@"
|
|
39
|
+
name="$2"
|
|
40
|
+
shift 2
|
|
41
|
+
;;
|
|
42
|
+
--match)
|
|
43
|
+
require_value "$@"
|
|
44
|
+
process_match="$2"
|
|
45
|
+
shift 2
|
|
46
|
+
;;
|
|
47
|
+
--fingerprint)
|
|
48
|
+
require_value "$@"
|
|
49
|
+
fingerprint="$2"
|
|
50
|
+
shift 2
|
|
51
|
+
;;
|
|
52
|
+
--log)
|
|
53
|
+
require_value "$@"
|
|
54
|
+
log_file="$2"
|
|
55
|
+
shift 2
|
|
56
|
+
;;
|
|
57
|
+
--help|-h)
|
|
58
|
+
usage
|
|
59
|
+
exit 0
|
|
60
|
+
;;
|
|
61
|
+
--)
|
|
62
|
+
shift
|
|
63
|
+
break
|
|
64
|
+
;;
|
|
65
|
+
*)
|
|
66
|
+
die "Unknown option: $1"
|
|
67
|
+
;;
|
|
68
|
+
esac
|
|
69
|
+
done
|
|
70
|
+
|
|
71
|
+
[[ "$name" =~ ^[a-zA-Z0-9][a-zA-Z0-9._-]*$ ]] || die "--name must be a safe identifier."
|
|
72
|
+
[ -n "$process_match" ] || die "--match is required."
|
|
73
|
+
[ "$#" -gt 0 ] || die "A command is required after --."
|
|
74
|
+
[ -r "/proc/$$/environ" ] || die "Linux /proc process metadata is required."
|
|
75
|
+
|
|
76
|
+
for tool in awk cksum flock grep pgrep ps setsid tr; do
|
|
77
|
+
command -v "$tool" >/dev/null 2>&1 || die "Required command is unavailable: $tool"
|
|
78
|
+
done
|
|
79
|
+
|
|
80
|
+
match_status=0
|
|
81
|
+
pgrep -f -- "$process_match" >/dev/null 2>&1 || match_status=$?
|
|
82
|
+
[ "$match_status" -le 1 ] || die "--match is not a valid process regular expression."
|
|
83
|
+
|
|
84
|
+
if [ -z "$fingerprint" ]; then
|
|
85
|
+
fingerprint="$({
|
|
86
|
+
printf '%s\0' "$name" "${WORKSPACE:-}" "${DEVROUTER_WORKSPACE:-}"
|
|
87
|
+
printf '%s\0' "$@"
|
|
88
|
+
} | cksum | awk '{print $1 "-" $2}')"
|
|
89
|
+
fi
|
|
90
|
+
[[ "$fingerprint" =~ ^[a-zA-Z0-9][a-zA-Z0-9._:-]*$ ]] || die "--fingerprint must be a safe identifier."
|
|
91
|
+
|
|
92
|
+
state_dir="${DEVROUTER_PROCESS_STATE_DIR:-/tmp}"
|
|
93
|
+
state_file="$state_dir/devrouter-process-$name.state"
|
|
94
|
+
lock_file="$state_file.lock"
|
|
95
|
+
log_file="${log_file:-/tmp/devrouter-process-$name.log}"
|
|
96
|
+
lock_timeout="${DEVROUTER_PROCESS_LOCK_TIMEOUT_SECONDS:-30}"
|
|
97
|
+
term_timeout="${DEVROUTER_PROCESS_TERM_TIMEOUT_SECONDS:-15}"
|
|
98
|
+
kill_timeout="${DEVROUTER_PROCESS_KILL_TIMEOUT_SECONDS:-5}"
|
|
99
|
+
|
|
100
|
+
process_alive() {
|
|
101
|
+
local pid="$1"
|
|
102
|
+
local state
|
|
103
|
+
|
|
104
|
+
kill -0 "$pid" 2>/dev/null || return 1
|
|
105
|
+
state="$(ps -o stat= -p "$pid" 2>/dev/null | tr -d ' ')"
|
|
106
|
+
[[ "$state" != Z* ]]
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
process_owned() {
|
|
110
|
+
local pid="$1"
|
|
111
|
+
local pgid="$2"
|
|
112
|
+
local expected_fingerprint="$3"
|
|
113
|
+
local actual_pgid
|
|
114
|
+
|
|
115
|
+
[ -r "/proc/$pid/environ" ] || return 1
|
|
116
|
+
actual_pgid="$(ps -o pgid= -p "$pid" 2>/dev/null | tr -d ' ')"
|
|
117
|
+
[ "$actual_pgid" = "$pgid" ] || return 1
|
|
118
|
+
[ "$pgid" = "$pid" ] || return 1
|
|
119
|
+
tr '\0' '\n' <"/proc/$pid/environ" 2>/dev/null |
|
|
120
|
+
grep -Fqx "DEVROUTER_PROCESS_NAME=$name" || return 1
|
|
121
|
+
tr '\0' '\n' <"/proc/$pid/environ" 2>/dev/null |
|
|
122
|
+
grep -Fqx "DEVROUTER_PROCESS_FINGERPRINT=$expected_fingerprint"
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
process_group_alive() {
|
|
126
|
+
local pgid="$1"
|
|
127
|
+
|
|
128
|
+
ps -eo pgid=,stat= | awk -v expected="$pgid" '
|
|
129
|
+
$1 == expected && $2 !~ /^Z/ { found = 1 }
|
|
130
|
+
END { exit(found ? 0 : 1) }
|
|
131
|
+
'
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
is_self_or_ancestor() {
|
|
135
|
+
local candidate="$1"
|
|
136
|
+
local current="$$"
|
|
137
|
+
|
|
138
|
+
while [[ "$current" =~ ^[1-9][0-9]*$ ]]; do
|
|
139
|
+
[ "$candidate" = "$current" ] && return 0
|
|
140
|
+
[ "$current" -le 1 ] && break
|
|
141
|
+
current="$(ps -o ppid= -p "$current" 2>/dev/null | tr -d ' ')"
|
|
142
|
+
done
|
|
143
|
+
return 1
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
is_helper_process() {
|
|
147
|
+
local candidate="$1"
|
|
148
|
+
|
|
149
|
+
[ -r "/proc/$candidate/cmdline" ] || return 1
|
|
150
|
+
tr '\0' '\n' <"/proc/$candidate/cmdline" 2>/dev/null | grep -Fqx "$0"
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
marked_process_exists() {
|
|
154
|
+
local environ
|
|
155
|
+
local pid
|
|
156
|
+
|
|
157
|
+
for environ in /proc/[1-9]*/environ; do
|
|
158
|
+
[ -r "$environ" ] || continue
|
|
159
|
+
pid="${environ#/proc/}"
|
|
160
|
+
pid="${pid%/environ}"
|
|
161
|
+
process_alive "$pid" || continue
|
|
162
|
+
is_self_or_ancestor "$pid" && continue
|
|
163
|
+
if tr '\0' '\n' <"$environ" 2>/dev/null | grep -Fqx "DEVROUTER_PROCESS_NAME=$name"; then
|
|
164
|
+
return 0
|
|
165
|
+
fi
|
|
166
|
+
done
|
|
167
|
+
return 1
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
matching_process_exists() {
|
|
171
|
+
local pid
|
|
172
|
+
|
|
173
|
+
while IFS= read -r pid; do
|
|
174
|
+
[ -n "$pid" ] || continue
|
|
175
|
+
[ -d "/proc/$pid" ] || continue
|
|
176
|
+
process_alive "$pid" || continue
|
|
177
|
+
is_self_or_ancestor "$pid" && continue
|
|
178
|
+
is_helper_process "$pid" && continue
|
|
179
|
+
return 0
|
|
180
|
+
done < <(pgrep -f -- "$process_match" 2>/dev/null || true)
|
|
181
|
+
return 1
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
unknown_process_exists() {
|
|
185
|
+
marked_process_exists || matching_process_exists
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
stop_process_group() {
|
|
189
|
+
local pid="$1"
|
|
190
|
+
local pgid="$2"
|
|
191
|
+
local attempt
|
|
192
|
+
|
|
193
|
+
kill -TERM -- "-$pgid" 2>/dev/null || true
|
|
194
|
+
for ((attempt = 1; attempt <= term_timeout; attempt += 1)); do
|
|
195
|
+
if ! process_group_alive "$pgid"; then
|
|
196
|
+
wait "$pid" 2>/dev/null || true
|
|
197
|
+
return 0
|
|
198
|
+
fi
|
|
199
|
+
sleep 1
|
|
200
|
+
done
|
|
201
|
+
|
|
202
|
+
kill -KILL -- "-$pgid" 2>/dev/null || true
|
|
203
|
+
for ((attempt = 1; attempt <= kill_timeout; attempt += 1)); do
|
|
204
|
+
if ! process_group_alive "$pgid"; then
|
|
205
|
+
wait "$pid" 2>/dev/null || true
|
|
206
|
+
return 0
|
|
207
|
+
fi
|
|
208
|
+
sleep 1
|
|
209
|
+
done
|
|
210
|
+
|
|
211
|
+
echo "[devrouter-process] Could not stop owned process group $pgid." >&2
|
|
212
|
+
return 1
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
mkdir -p "$state_dir" "$(dirname "$log_file")"
|
|
216
|
+
exec 9>"$lock_file"
|
|
217
|
+
flock -w "$lock_timeout" 9 || die "Timed out waiting for the '$name' process lock."
|
|
218
|
+
|
|
219
|
+
pid=""
|
|
220
|
+
pgid=""
|
|
221
|
+
stored_fingerprint=""
|
|
222
|
+
extra=""
|
|
223
|
+
|
|
224
|
+
if [ -f "$state_file" ]; then
|
|
225
|
+
read -r pid pgid stored_fingerprint extra <"$state_file" || true
|
|
226
|
+
if [ -z "$stored_fingerprint" ] ||
|
|
227
|
+
! [[ "$pid" =~ ^[1-9][0-9]*$ ]] ||
|
|
228
|
+
! [[ "$pgid" =~ ^[1-9][0-9]*$ ]] ||
|
|
229
|
+
[ -n "$extra" ]; then
|
|
230
|
+
if unknown_process_exists; then
|
|
231
|
+
die "Invalid state while an unowned '$name' process is running; refusing to start or kill it."
|
|
232
|
+
fi
|
|
233
|
+
rm -f "$state_file"
|
|
234
|
+
elif process_alive "$pid"; then
|
|
235
|
+
process_owned "$pid" "$pgid" "$stored_fingerprint" ||
|
|
236
|
+
die "State points at an unowned '$name' process; refusing to kill it."
|
|
237
|
+
if [ "$stored_fingerprint" = "$fingerprint" ]; then
|
|
238
|
+
echo "[devrouter-process] '$name' already matches this runtime (PID $pid)."
|
|
239
|
+
exit 0
|
|
240
|
+
fi
|
|
241
|
+
|
|
242
|
+
echo "[devrouter-process] '$name' runtime changed; restarting owned process group $pgid."
|
|
243
|
+
stop_process_group "$pid" "$pgid"
|
|
244
|
+
rm -f "$state_file"
|
|
245
|
+
else
|
|
246
|
+
rm -f "$state_file"
|
|
247
|
+
fi
|
|
248
|
+
fi
|
|
249
|
+
|
|
250
|
+
unknown_process_exists &&
|
|
251
|
+
die "Found an unowned '$name' process; refusing to start a duplicate or kill it."
|
|
252
|
+
|
|
253
|
+
echo "[devrouter-process] Starting '$name' (logs: $log_file)..."
|
|
254
|
+
env DEVROUTER_PROCESS_NAME="$name" \
|
|
255
|
+
DEVROUTER_PROCESS_FINGERPRINT="$fingerprint" \
|
|
256
|
+
setsid "$@" 9>&- >"$log_file" 2>&1 </dev/null &
|
|
257
|
+
pid=$!
|
|
258
|
+
pgid="$pid"
|
|
259
|
+
|
|
260
|
+
for attempt in {1..10}; do
|
|
261
|
+
if process_owned "$pid" "$pgid" "$fingerprint"; then
|
|
262
|
+
printf '%s %s %s\n' "$pid" "$pgid" "$fingerprint" >"$state_file.tmp.$BASHPID"
|
|
263
|
+
mv "$state_file.tmp.$BASHPID" "$state_file"
|
|
264
|
+
echo "[devrouter-process] '$name' started (PID $pid)."
|
|
265
|
+
exit 0
|
|
266
|
+
fi
|
|
267
|
+
if ! process_alive "$pid"; then
|
|
268
|
+
die "'$name' exited during startup; see $log_file."
|
|
269
|
+
fi
|
|
270
|
+
sleep 1
|
|
271
|
+
done
|
|
272
|
+
|
|
273
|
+
stop_process_group "$pid" "$pgid" || true
|
|
274
|
+
die "Could not verify ownership of '$name'; stopped it."
|
package/dist/devrouter.js
CHANGED
|
@@ -1787,7 +1787,7 @@ function loadRepoConfig(repoPath) {
|
|
|
1787
1787
|
const config = parseConfig(parsed ?? {}, configPath);
|
|
1788
1788
|
const requiredVersion = config.devrouter?.version;
|
|
1789
1789
|
if (requiredVersion && !hasWarnedVersionMismatch) {
|
|
1790
|
-
const cliVersion = true ? "0.0.
|
|
1790
|
+
const cliVersion = true ? "0.0.30" : "0.0.0-dev";
|
|
1791
1791
|
if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
|
|
1792
1792
|
hasWarnedVersionMismatch = true;
|
|
1793
1793
|
process.stderr.write(
|
|
@@ -4390,7 +4390,7 @@ async function buildDoctorReport(options = {}) {
|
|
|
4390
4390
|
const config = runtimeConfig.config;
|
|
4391
4391
|
loadedConfig = config;
|
|
4392
4392
|
loadedWorkspace = runtimeConfig.workspace;
|
|
4393
|
-
const cliVersion = true ? "0.0.
|
|
4393
|
+
const cliVersion = true ? "0.0.30" : "0.0.0-dev";
|
|
4394
4394
|
const configVersion = config.devrouter?.version;
|
|
4395
4395
|
if (configVersion && cliVersion !== "0.0.0-dev" && compareSemver(configVersion, cliVersion) > 0) {
|
|
4396
4396
|
addCheck(checks, {
|
|
@@ -5984,27 +5984,39 @@ function majorVersion(value, fallback) {
|
|
|
5984
5984
|
function devrouterVersion(value) {
|
|
5985
5985
|
return value && /^\d+\.\d+\.\d+$/.test(value) ? value : DEFAULT_DEVROUTER_VERSION;
|
|
5986
5986
|
}
|
|
5987
|
-
function
|
|
5987
|
+
function escapeExtendedRegex(value) {
|
|
5988
|
+
return value.replace(/[\\.^$|?*+()[\]{}]/g, "\\$&");
|
|
5989
|
+
}
|
|
5990
|
+
function inferDevProcess(repo) {
|
|
5988
5991
|
const script = repo.scripts.find((entry) => entry.name === "dev") ?? repo.scripts.find((entry) => entry.name.endsWith(":dev"));
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5992
|
+
const scriptName = script?.name ?? "dev";
|
|
5993
|
+
return {
|
|
5994
|
+
command: scriptName === "dev" ? "pnpm dev" : `pnpm run -- ${shellSingleQuote(scriptName)}`,
|
|
5995
|
+
match: `pnpm(\\.cjs)? .*${escapeExtendedRegex(scriptName)}`
|
|
5996
|
+
};
|
|
5993
5997
|
}
|
|
5994
5998
|
function inferPort2(repo) {
|
|
5995
5999
|
return repo.apps.find((app) => app.port)?.port ?? 3e3;
|
|
5996
6000
|
}
|
|
5997
|
-
function renderDockerfile(nodeMajor, pnpmVersion) {
|
|
6001
|
+
function renderDockerfile(nodeMajor, pnpmVersion, version) {
|
|
5998
6002
|
const pnpmPackageSpec = `pnpm@${pnpmVersion}`;
|
|
6003
|
+
const devrouterPackageSpec = `@devrouter/cli@${version}`;
|
|
6004
|
+
const devrouterTarball = `devrouter-cli-${version}.tgz`;
|
|
5999
6005
|
return `# ${MANAGED_MARKER}
|
|
6000
6006
|
FROM node:${nodeMajor}-bookworm-slim
|
|
6001
6007
|
|
|
6002
6008
|
RUN apt-get update \\
|
|
6003
|
-
&& apt-get install -y --no-install-recommends git ca-certificates curl procps openssl \\
|
|
6009
|
+
&& apt-get install -y --no-install-recommends git ca-certificates curl procps openssl tar util-linux \\
|
|
6004
6010
|
&& rm -rf /var/lib/apt/lists/*
|
|
6005
6011
|
|
|
6006
6012
|
RUN npm install -g ${shellSingleQuote(pnpmPackageSpec)}
|
|
6007
6013
|
|
|
6014
|
+
RUN npm pack --silent ${shellSingleQuote(devrouterPackageSpec)} \\
|
|
6015
|
+
&& tar -xzf ${shellSingleQuote(devrouterTarball)} --strip-components=2 \\
|
|
6016
|
+
-C /usr/local/bin package/bin/devrouter-process \\
|
|
6017
|
+
&& chmod +x /usr/local/bin/devrouter-process \\
|
|
6018
|
+
&& rm ${shellSingleQuote(devrouterTarball)}
|
|
6019
|
+
|
|
6008
6020
|
WORKDIR /workspaces/app
|
|
6009
6021
|
`;
|
|
6010
6022
|
}
|
|
@@ -6140,7 +6152,7 @@ fi
|
|
|
6140
6152
|
function shellSingleQuote(value) {
|
|
6141
6153
|
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
6142
6154
|
}
|
|
6143
|
-
function renderPostStart(
|
|
6155
|
+
function renderPostStart(devProcess) {
|
|
6144
6156
|
return `#!/usr/bin/env bash
|
|
6145
6157
|
# ${MANAGED_MARKER}
|
|
6146
6158
|
set -euo pipefail
|
|
@@ -6151,11 +6163,11 @@ set -a
|
|
|
6151
6163
|
. .devcontainer/devcontainer.env
|
|
6152
6164
|
set +a
|
|
6153
6165
|
|
|
6154
|
-
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
|
|
6166
|
+
devrouter-process ensure \\
|
|
6167
|
+
--name app \\
|
|
6168
|
+
--match ${shellSingleQuote(devProcess.match)} \\
|
|
6169
|
+
--log /tmp/devrouter-app.log \\
|
|
6170
|
+
-- bash -lc ${shellSingleQuote(devProcess.command)}
|
|
6159
6171
|
`;
|
|
6160
6172
|
}
|
|
6161
6173
|
function renderDevrouter(projectName, port, version) {
|
|
@@ -6272,7 +6284,7 @@ function plannedFiles(repoPath, version) {
|
|
|
6272
6284
|
const nodeMajor = majorVersion(repo.node?.version, "24");
|
|
6273
6285
|
const pnpmVersion = repo.packageManager?.name === "pnpm" && repo.packageManager.version ? repo.packageManager.version : DEFAULT_PNPM_VERSION;
|
|
6274
6286
|
const port = inferPort2(repo);
|
|
6275
|
-
const
|
|
6287
|
+
const devProcess = inferDevProcess(repo);
|
|
6276
6288
|
const issues = packageManagerIssues(repo);
|
|
6277
6289
|
return {
|
|
6278
6290
|
projectName,
|
|
@@ -6280,7 +6292,7 @@ function plannedFiles(repoPath, version) {
|
|
|
6280
6292
|
files: [
|
|
6281
6293
|
{
|
|
6282
6294
|
relativePath: ".devcontainer/Dockerfile",
|
|
6283
|
-
content: renderDockerfile(nodeMajor, pnpmVersion)
|
|
6295
|
+
content: renderDockerfile(nodeMajor, pnpmVersion, version)
|
|
6284
6296
|
},
|
|
6285
6297
|
{ relativePath: ".devcontainer/docker-compose.yml", content: renderCompose(projectName) },
|
|
6286
6298
|
{
|
|
@@ -6304,7 +6316,7 @@ function plannedFiles(repoPath, version) {
|
|
|
6304
6316
|
},
|
|
6305
6317
|
{
|
|
6306
6318
|
relativePath: ".devcontainer/post-start.sh",
|
|
6307
|
-
content: renderPostStart(
|
|
6319
|
+
content: renderPostStart(devProcess),
|
|
6308
6320
|
executable: true
|
|
6309
6321
|
},
|
|
6310
6322
|
{ relativePath: ".devcontainer/README.md", content: renderReadme(projectName) },
|
|
@@ -8565,7 +8577,7 @@ var init_version = __esm({
|
|
|
8565
8577
|
|
|
8566
8578
|
// src/cli.ts
|
|
8567
8579
|
var import_commander = require("commander");
|
|
8568
|
-
var CLI_VERSION = true ? "0.0.
|
|
8580
|
+
var CLI_VERSION = true ? "0.0.30" : "0.0.0-dev";
|
|
8569
8581
|
var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
|
|
8570
8582
|
function withErrorHandling(action2) {
|
|
8571
8583
|
return async (...args) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devrouter/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.30",
|
|
4
4
|
"description": "Local dev routing CLI with shared Traefik reverse proxy",
|
|
5
5
|
"author": "Roland Schlaefli",
|
|
6
6
|
"homepage": "https://github.com/rschlaefli/devrouter#readme",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"url": "https://github.com/rschlaefli/devrouter.git"
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
|
+
"bin",
|
|
13
14
|
"dist",
|
|
14
15
|
"upgrade-prompts"
|
|
15
16
|
],
|
|
@@ -18,7 +19,8 @@
|
|
|
18
19
|
"node": ">=24"
|
|
19
20
|
},
|
|
20
21
|
"bin": {
|
|
21
|
-
"devrouter": "dist/devrouter.js"
|
|
22
|
+
"devrouter": "dist/devrouter.js",
|
|
23
|
+
"devrouter-process": "bin/devrouter-process"
|
|
22
24
|
},
|
|
23
25
|
"dependencies": {
|
|
24
26
|
"commander": "^12.1.0",
|
|
@@ -42,7 +44,8 @@
|
|
|
42
44
|
"scripts": {
|
|
43
45
|
"build": "tsup",
|
|
44
46
|
"dev": "tsx src/cli.ts",
|
|
45
|
-
"test": "vitest run",
|
|
47
|
+
"test": "vitest run && pnpm test:process",
|
|
48
|
+
"test:process": "bash scripts/test-devrouter-process.sh",
|
|
46
49
|
"check": "biome check .",
|
|
47
50
|
"check:fix": "biome check --write .",
|
|
48
51
|
"check:docs-policy": "./scripts/check-docs-policy.sh",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Upgrade to devrouter 0.0.30
|
|
2
|
+
|
|
3
|
+
## What changed
|
|
4
|
+
|
|
5
|
+
Devrouter now ships `devrouter-process ensure`, a Linux devcontainer helper for locked, fingerprinted, process-group-owned application startup. Generated devcontainers extract only this helper from the exact Devrouter package tarball instead of installing the CLI dependency tree or carrying their own PID, locking, and restart implementation. HTTP readiness and the single container-recreate budget remain in `devrouter workspace ensure`.
|
|
6
|
+
|
|
7
|
+
## Steps
|
|
8
|
+
|
|
9
|
+
1. Install `@devrouter/cli@0.0.30` on the host and bump `.devrouter.yml` to `devrouter.version: 0.0.30`.
|
|
10
|
+
2. If the repository uses the managed scaffold, run `devrouter repo devcontainer write --dry-run --json`, review the plan, then run `devrouter repo devcontainer write --yes`.
|
|
11
|
+
3. For a custom devcontainer, extract `bin/devrouter-process` from the exact package tarball into its image and replace repository-specific background-process lifecycle code with one `devrouter-process ensure` call. Keep only the application's command and environment setup in the repository.
|
|
12
|
+
4. Rebuild or recreate the devcontainer so the helper is installed in the image.
|
|
13
|
+
5. From a linked worktree, run `devrouter workspace ensure .` twice and confirm the second run reuses both the container and managed process.
|