@zendero/runctl 0.1.8 → 0.1.10
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 +34 -6
- package/bin/runctl +35 -7
- package/examples/run.sh.example +6 -0
- package/lib/run-lib.sh +49 -2
- package/package.json +2 -2
- package/scripts/install-global.sh +12 -2
package/README.md
CHANGED
|
@@ -102,6 +102,14 @@ Use npm explicitly (e.g. no pnpm on the machine):
|
|
|
102
102
|
curl -fsSL "https://raw.githubusercontent.com/DoctorKhan/runctl/main/scripts/install-global.sh" | bash -s -- --pm npm --registry
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
+
If `runctl --help` still looks old after install/update, remove the legacy package that can shadow this CLI and reinstall:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
pnpm remove -g runctl
|
|
109
|
+
pnpm add -g @zendero/runctl@latest
|
|
110
|
+
hash -r
|
|
111
|
+
```
|
|
112
|
+
|
|
105
113
|
`--help` on the script prints the same usage summary.
|
|
106
114
|
|
|
107
115
|
---
|
|
@@ -127,23 +135,37 @@ Add scripts to your `package.json`:
|
|
|
127
135
|
|
|
128
136
|
**`predev`:** If you define `predev` next to `dev` (e.g. a doctor step) and your script name is `dev:*` or `dev_*` without its own `pre<script>`, runctl runs `predev` once before starting. Set `RUNCTL_SKIP_PREDEV=1` to skip.
|
|
129
137
|
|
|
138
|
+
**Dashboard / API-only (split names):** Avoid `runctl start --script dev` when `dev` is the runctl wrapper — that loops. Use a dedicated script for the real server:
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
{
|
|
142
|
+
"scripts": {
|
|
143
|
+
"dev": "runctl start --script dev:workbench",
|
|
144
|
+
"dev:workbench": "node --env-file=.env src/dashboard/server.js",
|
|
145
|
+
"dev:stop": "runctl stop"
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Listen on `process.env.PORT` (runctl sets it). Optional: `runctl start --script dev:workbench --open` to open the browser after start, or `runctl start … && runctl open .`.
|
|
151
|
+
|
|
130
152
|
---
|
|
131
153
|
|
|
132
154
|
## Commands
|
|
133
155
|
|
|
134
156
|
| Command | What it does |
|
|
135
157
|
|---------|-------------|
|
|
136
|
-
| `runctl start` \| `runctl dev` | Start dev server (same command; picks free port, backgrounds) |
|
|
158
|
+
| `runctl start` \| `runctl dev` | Start dev server (same command; picks free port, backgrounds). Flags: `--script`, `--open` (open browser after a successful start) |
|
|
137
159
|
| `runctl stop [dir]` | Stop daemons & release ports |
|
|
138
160
|
| `runctl status [dir]` | Show `.run` state for this package |
|
|
139
161
|
| `runctl ps` | List running programs with PID, port, service, project |
|
|
140
|
-
| `runctl logs [dir] [service]` | Tail `.run/logs/<service>.log` (default service: `web`) |
|
|
162
|
+
| `runctl logs [dir] [service]` | Tail `.run/logs/<service>.log` (default service: **`RUNCTL_SERVICE`**, else `package.json` `name` basename, else `web`) |
|
|
141
163
|
| `runctl ports` | List user-wide port registry (`~/.run`) |
|
|
142
164
|
| `runctl ports gc` | Clean up stale port claims |
|
|
143
165
|
| `runctl env expand <manifest> [--out file]` | Generate `.env.local` from manifest |
|
|
144
|
-
| `runctl doctor [dir]` | Check Node 18+, `lsof`, package manager, `package.json` |
|
|
166
|
+
| `runctl doctor [dir]` | Check Node 18+, `lsof`, package manager, `package.json`; reminds that child scripts get **`PORT`** / **`HOST`** (custom servers should `listen` on `process.env.PORT`) |
|
|
145
167
|
| `runctl update` | Refresh global CLI: default **`auto`** (npm `@latest`, then Git). **`runctl update npm`** / **`git`** / **`auto`** or flags **`--registry`** / **`--git`** / **`--auto`**; **`runctl update --help`**; env `RUNCTL_PACKAGE`, `RUNCTL_GIT_BASE`, `RUNCTL_GIT_REF` (aligned with [`install-global.sh`](scripts/install-global.sh)) |
|
|
146
|
-
| `runctl version` | Print package version and install path |
|
|
168
|
+
| `runctl version` \| `runctl --version` \| `runctl -v` | Print package version and install path (supported interchangeably) |
|
|
147
169
|
|
|
148
170
|
**Monorepo:** `runctl start ./apps/web --script dev:server`
|
|
149
171
|
|
|
@@ -160,7 +182,7 @@ Add scripts to your `package.json`:
|
|
|
160
182
|
| **`predev`** + split `dev` / `dev:server` | **Supported** — see above. |
|
|
161
183
|
| Monorepo app in a subfolder | Use `runctl start ./apps/web`. |
|
|
162
184
|
| **No `package.json`** (Python, Go, etc.) | **Not a fit** — this tool is for Node package scripts. |
|
|
163
|
-
| Custom Node entry (gateways, CLIs) | **Weak fit** — `PORT`/`HOST` are
|
|
185
|
+
| Custom Node entry (gateways, CLIs) | **Weak fit** — `PORT`/`HOST` are injected; bind with `server.listen(process.env.PORT)` (see `runctl doctor`). |
|
|
164
186
|
|
|
165
187
|
---
|
|
166
188
|
|
|
@@ -168,7 +190,13 @@ Add scripts to your `package.json`:
|
|
|
168
190
|
|
|
169
191
|
[`examples/consumer-package.json`](examples/consumer-package.json) · [`docs/vercel-and-env.md`](docs/vercel-and-env.md) · [`examples/env.manifest.example`](examples/env.manifest.example)
|
|
170
192
|
|
|
171
|
-
**
|
|
193
|
+
**CLI vs `run-lib.sh`:** Most apps only need the **`runctl`** binary and `package.json` scripts. For shell-heavy repos, [`examples/run.sh.example`](examples/run.sh.example) shows sourcing **`lib/run-lib.sh`** (same library the CLI uses). Resolve the installed path with **`runctl lib-path`**.
|
|
194
|
+
|
|
195
|
+
**CI:** Prefer **`pnpm add -D @zendero/runctl`** (or a global install) so `runctl` is on `PATH` with a stable version. **`pnpm dlx @zendero/runctl`** is fine for one-off recovery; avoid relying on it for every CI job (cold cache / latency).
|
|
196
|
+
|
|
197
|
+
**Roadmap (ideas):** `runctl exec` (one-off commands with the same port / `.run` contract as `start`); optional HTTP health gate before “ready”.
|
|
198
|
+
|
|
199
|
+
**Develop this repo:** `pnpm install` → `./run.sh` (default **doctor**, like `elata-bio-sdk/run.sh`) → `./run.sh ports` · **`pnpm test`** runs [`tests/run-all.sh`](tests/run-all.sh) (Jest-style output: suites, ✓/✗, `PASS`/`FAIL` per file, shared helpers in [`tests/lib/test-runner.sh`](tests/lib/test-runner.sh))
|
|
172
200
|
|
|
173
201
|
**Publish (maintainers)** — workflow similar to elata’s release preflight, scaled for one package:
|
|
174
202
|
|
package/bin/runctl
CHANGED
|
@@ -16,12 +16,12 @@ ${_b}runctl${_r} — dev-server orchestrator & port manager
|
|
|
16
16
|
${_y}Usage:${_r} runctl <command> [options]
|
|
17
17
|
|
|
18
18
|
${_y}Commands${_r}
|
|
19
|
-
${_c}start${_r} | ${_c}dev${_r} [dir] [--script name] Start dev server ${_d}(alias: dev = start)${_r}
|
|
19
|
+
${_c}start${_r} | ${_c}dev${_r} [dir] [--script name] [--open] Start dev server ${_d}(alias: dev = start; ${_c}--open${_r} = browser after start)${_r}
|
|
20
20
|
${_c}open${_r} [dir] Open running dev server in browser
|
|
21
21
|
${_c}stop${_r} [dir] Stop daemons & release ports
|
|
22
22
|
${_c}status${_r} [dir] Show running state
|
|
23
23
|
${_c}ps${_r} List running programs with ports/projects
|
|
24
|
-
${_c}logs${_r} [dir] [service] Tail .run/logs/<service>.log ${_d}(default
|
|
24
|
+
${_c}logs${_r} [dir] [service] Tail .run/logs/<service>.log ${_d}(default: ${_c}RUNCTL_SERVICE${_r} or package name, else web)${_r}
|
|
25
25
|
${_c}ports${_r} List user-wide port registry (~/.run)
|
|
26
26
|
${_c}ports gc${_r} Clean up stale port claims
|
|
27
27
|
${_c}env expand${_r} <manifest> [opts] Generate .env.local from manifest
|
|
@@ -30,6 +30,8 @@ ${_y}Commands${_r}
|
|
|
30
30
|
|
|
31
31
|
${_y}Options${_r}
|
|
32
32
|
${_c}--script${_r} <name> Package script to run ${_d}(default: dev)${_r}
|
|
33
|
+
${_c}--open${_r} After a successful start, open the app URL ${_d}(same as ${_c}runctl open${_r})${_r}
|
|
34
|
+
${_c}version${_r} | ${_c}--version${_r} | ${_c}-v${_r} Print version and install path
|
|
33
35
|
${_c}--help${_r}, ${_c}-h${_r} Show this help
|
|
34
36
|
|
|
35
37
|
${_d}Quick start — add to package.json:
|
|
@@ -48,12 +50,13 @@ _resolve_proj() {
|
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
cmd_start() {
|
|
51
|
-
local proj script="" args=()
|
|
53
|
+
local proj script="" args=() open_after=0
|
|
52
54
|
proj="$(pwd)"
|
|
53
55
|
|
|
54
56
|
while [[ $# -gt 0 ]]; do
|
|
55
57
|
case "$1" in
|
|
56
58
|
--script) script="${2:-}"; shift 2 ;;
|
|
59
|
+
--open) open_after=1; shift ;;
|
|
57
60
|
--) shift; args+=("$@"); break ;;
|
|
58
61
|
-h | --help) usage; exit 0 ;;
|
|
59
62
|
-*)
|
|
@@ -77,7 +80,12 @@ cmd_start() {
|
|
|
77
80
|
# shellcheck source=../lib/run-lib.sh
|
|
78
81
|
source "$RUNCTL_PKG_ROOT/lib/run-lib.sh"
|
|
79
82
|
run_project_init "$proj"
|
|
80
|
-
run_with_lock run_start_package_dev "${args[@]+"${args[@]}"}"
|
|
83
|
+
if ! run_with_lock run_start_package_dev "${args[@]+"${args[@]}"}"; then
|
|
84
|
+
return 1
|
|
85
|
+
fi
|
|
86
|
+
if [[ "$open_after" -eq 1 ]]; then
|
|
87
|
+
cmd_open "$proj"
|
|
88
|
+
fi
|
|
81
89
|
}
|
|
82
90
|
|
|
83
91
|
cmd_open() {
|
|
@@ -330,6 +338,19 @@ cmd_update() {
|
|
|
330
338
|
pm="$(cmd_update_pick_pm "$PM")" || exit 1
|
|
331
339
|
git_target="${GIT_BASE}#${GIT_REF}"
|
|
332
340
|
|
|
341
|
+
_remove_conflicting_global_runctl() {
|
|
342
|
+
# A legacy package named "runctl" can own the same binary name and shadow @zendero/runctl.
|
|
343
|
+
# Best-effort remove before reinstalling the intended package.
|
|
344
|
+
if [[ "$PKG" != "@zendero/runctl" ]]; then
|
|
345
|
+
return 0
|
|
346
|
+
fi
|
|
347
|
+
if [[ "$pm" == "pnpm" ]]; then
|
|
348
|
+
pnpm remove -g runctl >/dev/null 2>&1 || true
|
|
349
|
+
else
|
|
350
|
+
npm uninstall -g runctl >/dev/null 2>&1 || true
|
|
351
|
+
fi
|
|
352
|
+
}
|
|
353
|
+
|
|
333
354
|
_git_base_for_ls_remote() {
|
|
334
355
|
local base="$1"
|
|
335
356
|
printf '%s' "${base#git+}"
|
|
@@ -349,6 +370,7 @@ cmd_update() {
|
|
|
349
370
|
_update_registry() {
|
|
350
371
|
local spec="${PKG}@latest"
|
|
351
372
|
echo "runctl update: installing latest from registry ($spec)..."
|
|
373
|
+
_remove_conflicting_global_runctl
|
|
352
374
|
if [[ "$pm" == "pnpm" ]]; then
|
|
353
375
|
pnpm add -g --force "$spec"
|
|
354
376
|
else
|
|
@@ -366,6 +388,7 @@ cmd_update() {
|
|
|
366
388
|
echo "runctl update: installing from Git ($git_target)..."
|
|
367
389
|
echo "runctl update: could not resolve ref to SHA; proceeding with ref directly." >&2
|
|
368
390
|
fi
|
|
391
|
+
_remove_conflicting_global_runctl
|
|
369
392
|
if [[ "$pm" == "pnpm" ]]; then
|
|
370
393
|
pnpm add -g --force "$spec"
|
|
371
394
|
else
|
|
@@ -427,6 +450,7 @@ cmd_doctor() {
|
|
|
427
450
|
printf ' %-12s %s\n' "package.json:" "not found in $proj" >&2
|
|
428
451
|
ec=1
|
|
429
452
|
fi
|
|
453
|
+
printf ' %-12s %s\n' "child env:" "PORT and HOST are set for package scripts; custom Node servers should listen on process.env.PORT (and bind HOST if needed)."
|
|
430
454
|
return "$ec"
|
|
431
455
|
}
|
|
432
456
|
|
|
@@ -441,7 +465,8 @@ cmd_logs() {
|
|
|
441
465
|
shift 2
|
|
442
466
|
;;
|
|
443
467
|
-h | --help)
|
|
444
|
-
echo "Usage: runctl logs [dir] [service]
|
|
468
|
+
echo "Usage: runctl logs [dir] [service]" >&2
|
|
469
|
+
echo " Default service: RUNCTL_SERVICE, else package.json name, else web." >&2
|
|
445
470
|
echo " runctl logs [--lines N]" >&2
|
|
446
471
|
exit 0
|
|
447
472
|
;;
|
|
@@ -455,10 +480,13 @@ cmd_logs() {
|
|
|
455
480
|
proj="$(cd "${pos[0]}" && pwd)"
|
|
456
481
|
pos=("${pos[@]:1}")
|
|
457
482
|
fi
|
|
458
|
-
local svc="${pos[0]:-
|
|
483
|
+
local svc="${pos[0]:-}"
|
|
459
484
|
# shellcheck source=../lib/run-lib.sh
|
|
460
485
|
source "$RUNCTL_PKG_ROOT/lib/run-lib.sh"
|
|
461
486
|
run_project_init "$proj"
|
|
487
|
+
if [[ -z "$svc" ]]; then
|
|
488
|
+
svc="$(run_default_service_name)"
|
|
489
|
+
fi
|
|
462
490
|
local logf="$RUN_LOCAL_STATE/logs/${svc}.log"
|
|
463
491
|
if [[ ! -f "$logf" ]]; then
|
|
464
492
|
echo "runctl logs: no file at $logf" >&2
|
|
@@ -500,7 +528,7 @@ main() {
|
|
|
500
528
|
|
|
501
529
|
# Plumbing
|
|
502
530
|
lib-path) printf '%s\n' "$RUNCTL_PKG_ROOT/lib/run-lib.sh" ;;
|
|
503
|
-
version | -v) cmd_version_print ;;
|
|
531
|
+
version | -v | --version) cmd_version_print ;;
|
|
504
532
|
help | -h | --help) usage ;;
|
|
505
533
|
|
|
506
534
|
# Hidden backward-compat aliases
|
package/examples/run.sh.example
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Optional shell entrypoint — most projects only need package.json scripts.
|
|
3
3
|
# Prerequisite: pnpm add -D @zendero/runctl (Node >= 18)
|
|
4
|
+
#
|
|
5
|
+
# When to use this vs the runctl CLI:
|
|
6
|
+
# - Prefer `runctl start` / package.json scripts (see README) for normal dev.
|
|
7
|
+
# - Source lib/run-lib.sh (this file) for advanced shell integration: custom
|
|
8
|
+
# wrappers, CI, or repos that already drive everything through ./run.sh.
|
|
9
|
+
# Shipped path: package `lib/run-lib.sh` (see `runctl lib-path`).
|
|
4
10
|
|
|
5
11
|
set -euo pipefail
|
|
6
12
|
|
package/lib/run-lib.sh
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# run-lib.sh — npm package `@zendero/runctl`: project .run/ + user ~/.run registry.
|
|
3
3
|
# Requires bash. Port/dev automation requires Node.js >= 18 (see package.json engines).
|
|
4
4
|
# Intentionally no global `set -e` — this file is usually sourced.
|
|
5
|
+
# Consumer shell example: examples/run.sh.example (most projects use the `runctl` CLI instead).
|
|
5
6
|
|
|
6
7
|
if [[ -z "${BASH_VERSION:-}" ]]; then
|
|
7
8
|
echo "run-lib.sh: must be sourced or executed with bash (not zsh)" >&2
|
|
@@ -146,6 +147,50 @@ run_require_node() {
|
|
|
146
147
|
}
|
|
147
148
|
}
|
|
148
149
|
|
|
150
|
+
# Basename for .run/logs/<name>.log and RUN_DEV_SERVICE — override with RUNCTL_SERVICE.
|
|
151
|
+
run_sanitize_service_name() {
|
|
152
|
+
local s="${1:-web}"
|
|
153
|
+
s="${s//[^a-zA-Z0-9._-]/-}"
|
|
154
|
+
s="$(printf '%s' "$s" | sed 's/^-*//;s/-*$//')"
|
|
155
|
+
[[ -z "$s" ]] && s="web"
|
|
156
|
+
printf '%s\n' "$s"
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
run_default_service_name() {
|
|
160
|
+
if [[ -n "${RUNCTL_SERVICE:-}" ]]; then
|
|
161
|
+
run_sanitize_service_name "${RUNCTL_SERVICE}"
|
|
162
|
+
return 0
|
|
163
|
+
fi
|
|
164
|
+
[[ -f "$RUN_PROJECT_ROOT/package.json" ]] || {
|
|
165
|
+
printf '%s\n' web
|
|
166
|
+
return 0
|
|
167
|
+
}
|
|
168
|
+
command -v node >/dev/null 2>&1 || {
|
|
169
|
+
printf '%s\n' web
|
|
170
|
+
return 0
|
|
171
|
+
}
|
|
172
|
+
node -e '
|
|
173
|
+
const fs = require("fs");
|
|
174
|
+
const path = require("path");
|
|
175
|
+
const root = process.argv[1];
|
|
176
|
+
let pkg = {};
|
|
177
|
+
try {
|
|
178
|
+
pkg = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
|
|
179
|
+
} catch {
|
|
180
|
+
process.stdout.write("web" + "\n");
|
|
181
|
+
process.exit(0);
|
|
182
|
+
}
|
|
183
|
+
const n = pkg.name;
|
|
184
|
+
if (typeof n !== "string" || !n) {
|
|
185
|
+
process.stdout.write("web" + "\n");
|
|
186
|
+
process.exit(0);
|
|
187
|
+
}
|
|
188
|
+
const base = n.includes("/") ? n.split("/").pop() : n;
|
|
189
|
+
const safe = String(base).replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "web";
|
|
190
|
+
process.stdout.write(safe + "\n");
|
|
191
|
+
' "$RUN_PROJECT_ROOT" 2>/dev/null || printf '%s\n' web
|
|
192
|
+
}
|
|
193
|
+
|
|
149
194
|
# True if package.json defines scripts.<name> (Node required).
|
|
150
195
|
run_package_has_script() {
|
|
151
196
|
local name="$1"
|
|
@@ -242,14 +287,16 @@ EOF
|
|
|
242
287
|
|
|
243
288
|
# Start \`pnpm|yarn|npm run <script>\` in the background with a free port and register it.
|
|
244
289
|
# Script name defaults to \`dev\`; set RUNCTL_PM_RUN_SCRIPT (e.g. dev:server) when \`dev\` is the runctl wrapper.
|
|
245
|
-
#
|
|
290
|
+
# Default service name: RUNCTL_SERVICE, else package.json name (basename), else web — see run_default_service_name.
|
|
291
|
+
# Usage: run_start_package_dev [service_name] [base_port|auto=auto] [extra args after script --]
|
|
246
292
|
run_start_package_dev() {
|
|
247
293
|
run_require_node || return 1
|
|
248
294
|
[[ -f "$RUN_PROJECT_ROOT/package.json" ]] || {
|
|
249
295
|
echo "run_start_package_dev: no package.json in $RUN_PROJECT_ROOT" >&2
|
|
250
296
|
return 1
|
|
251
297
|
}
|
|
252
|
-
local svc
|
|
298
|
+
local svc
|
|
299
|
+
svc="$(run_default_service_name)"
|
|
253
300
|
local base_raw="auto"
|
|
254
301
|
if [[ $# -ge 1 ]]; then svc="$1"; shift; fi
|
|
255
302
|
if [[ $# -ge 1 ]]; then base_raw="$1"; shift; fi
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zendero/runctl",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Picks a free port, runs your dev server in the background, and keeps PID + port state in .run/ so projects don't collide.",
|
|
5
5
|
"author": "DoctorKhan",
|
|
6
6
|
"homepage": "https://github.com/DoctorKhan/runctl#readme",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
],
|
|
38
38
|
"scripts": {
|
|
39
39
|
"run": "bash ./run.sh",
|
|
40
|
-
"test": "bash ./tests/
|
|
40
|
+
"test": "bash ./tests/run-all.sh",
|
|
41
41
|
"doctor": "bash ./run.sh doctor",
|
|
42
42
|
"release-check": "bash ./run.sh release-check",
|
|
43
43
|
"promote": "bash ./run.sh promote",
|
|
@@ -132,10 +132,20 @@ run_install() {
|
|
|
132
132
|
local source_kind="$2"
|
|
133
133
|
local target="$3"
|
|
134
134
|
|
|
135
|
+
# A legacy package named "runctl" can shadow the same CLI binary.
|
|
136
|
+
# Remove it before installing @zendero/runctl.
|
|
137
|
+
if [[ "$PKG" == "@zendero/runctl" ]]; then
|
|
138
|
+
if [[ "$manager" == "pnpm" ]]; then
|
|
139
|
+
pnpm remove -g runctl >/dev/null 2>&1 || true
|
|
140
|
+
else
|
|
141
|
+
npm uninstall -g runctl >/dev/null 2>&1 || true
|
|
142
|
+
fi
|
|
143
|
+
fi
|
|
144
|
+
|
|
135
145
|
if [[ "$manager" == "pnpm" ]]; then
|
|
136
|
-
pnpm add -g "$target"
|
|
146
|
+
pnpm add -g --force "$target"
|
|
137
147
|
else
|
|
138
|
-
npm install -g "$target"
|
|
148
|
+
npm install -g --force "$target"
|
|
139
149
|
fi
|
|
140
150
|
|
|
141
151
|
say
|