@zendero/runctl 0.1.9 → 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 +26 -6
- package/bin/runctl +20 -7
- package/examples/run.sh.example +6 -0
- package/lib/run-lib.sh +49 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -135,23 +135,37 @@ Add scripts to your `package.json`:
|
|
|
135
135
|
|
|
136
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.
|
|
137
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
|
+
|
|
138
152
|
---
|
|
139
153
|
|
|
140
154
|
## Commands
|
|
141
155
|
|
|
142
156
|
| Command | What it does |
|
|
143
157
|
|---------|-------------|
|
|
144
|
-
| `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) |
|
|
145
159
|
| `runctl stop [dir]` | Stop daemons & release ports |
|
|
146
160
|
| `runctl status [dir]` | Show `.run` state for this package |
|
|
147
161
|
| `runctl ps` | List running programs with PID, port, service, project |
|
|
148
|
-
| `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`) |
|
|
149
163
|
| `runctl ports` | List user-wide port registry (`~/.run`) |
|
|
150
164
|
| `runctl ports gc` | Clean up stale port claims |
|
|
151
165
|
| `runctl env expand <manifest> [--out file]` | Generate `.env.local` from manifest |
|
|
152
|
-
| `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`) |
|
|
153
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)) |
|
|
154
|
-
| `runctl version` | Print package version and install path |
|
|
168
|
+
| `runctl version` \| `runctl --version` \| `runctl -v` | Print package version and install path (supported interchangeably) |
|
|
155
169
|
|
|
156
170
|
**Monorepo:** `runctl start ./apps/web --script dev:server`
|
|
157
171
|
|
|
@@ -168,7 +182,7 @@ Add scripts to your `package.json`:
|
|
|
168
182
|
| **`predev`** + split `dev` / `dev:server` | **Supported** — see above. |
|
|
169
183
|
| Monorepo app in a subfolder | Use `runctl start ./apps/web`. |
|
|
170
184
|
| **No `package.json`** (Python, Go, etc.) | **Not a fit** — this tool is for Node package scripts. |
|
|
171
|
-
| 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`). |
|
|
172
186
|
|
|
173
187
|
---
|
|
174
188
|
|
|
@@ -176,7 +190,13 @@ Add scripts to your `package.json`:
|
|
|
176
190
|
|
|
177
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)
|
|
178
192
|
|
|
179
|
-
**
|
|
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))
|
|
180
200
|
|
|
181
201
|
**Publish (maintainers)** — workflow similar to elata’s release preflight, scaled for one package:
|
|
182
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() {
|
|
@@ -442,6 +450,7 @@ cmd_doctor() {
|
|
|
442
450
|
printf ' %-12s %s\n' "package.json:" "not found in $proj" >&2
|
|
443
451
|
ec=1
|
|
444
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)."
|
|
445
454
|
return "$ec"
|
|
446
455
|
}
|
|
447
456
|
|
|
@@ -456,7 +465,8 @@ cmd_logs() {
|
|
|
456
465
|
shift 2
|
|
457
466
|
;;
|
|
458
467
|
-h | --help)
|
|
459
|
-
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
|
|
460
470
|
echo " runctl logs [--lines N]" >&2
|
|
461
471
|
exit 0
|
|
462
472
|
;;
|
|
@@ -470,10 +480,13 @@ cmd_logs() {
|
|
|
470
480
|
proj="$(cd "${pos[0]}" && pwd)"
|
|
471
481
|
pos=("${pos[@]:1}")
|
|
472
482
|
fi
|
|
473
|
-
local svc="${pos[0]:-
|
|
483
|
+
local svc="${pos[0]:-}"
|
|
474
484
|
# shellcheck source=../lib/run-lib.sh
|
|
475
485
|
source "$RUNCTL_PKG_ROOT/lib/run-lib.sh"
|
|
476
486
|
run_project_init "$proj"
|
|
487
|
+
if [[ -z "$svc" ]]; then
|
|
488
|
+
svc="$(run_default_service_name)"
|
|
489
|
+
fi
|
|
477
490
|
local logf="$RUN_LOCAL_STATE/logs/${svc}.log"
|
|
478
491
|
if [[ ! -f "$logf" ]]; then
|
|
479
492
|
echo "runctl logs: no file at $logf" >&2
|
|
@@ -515,7 +528,7 @@ main() {
|
|
|
515
528
|
|
|
516
529
|
# Plumbing
|
|
517
530
|
lib-path) printf '%s\n' "$RUNCTL_PKG_ROOT/lib/run-lib.sh" ;;
|
|
518
|
-
version | -v) cmd_version_print ;;
|
|
531
|
+
version | -v | --version) cmd_version_print ;;
|
|
519
532
|
help | -h | --help) usage ;;
|
|
520
533
|
|
|
521
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",
|