@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 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 set, but no framework CLI flags. |
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
- **Develop this repo:** `pnpm install` `./run.sh` (default **doctor**, like `elata-bio-sdk/run.sh`) `./run.sh ports`
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 service: web)${_r}
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] (default service: web)" >&2
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]:-web}"
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
@@ -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
- # Usage: run_start_package_dev [service_name=web] [base_port|auto=auto] [extra args after script --]
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="web"
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.9",
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/runctl-ps.test.sh",
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",