@zendero/runctl 0.1.11 → 0.1.13
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 +3 -1
- package/bin/runctl +225 -29
- package/docs/vercel-and-env.md +2 -2
- package/examples/run.sh.example +76 -67
- package/lib/repo-env.sh +54 -0
- package/package.json +3 -2
- package/run.sh +91 -0
- package/scripts/maintain.sh +255 -0
package/README.md
CHANGED
|
@@ -196,11 +196,13 @@ Listen on `process.env.PORT` (runctl sets it). Optional: `runctl start --script
|
|
|
196
196
|
|
|
197
197
|
**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`**.
|
|
198
198
|
|
|
199
|
+
**Scaffold `run.sh`:** `runctl run-sh --write` writes that example to `./run.sh` (honors `RUNCTL_PROJECT_ROOT`; use `-C dir` for another directory). Use `--force` to replace an existing file. Equivalent to redirecting `runctl run-sh` to `run.sh`, but also sets the executable bit.
|
|
200
|
+
|
|
199
201
|
**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).
|
|
200
202
|
|
|
201
203
|
**Roadmap (ideas):** `runctl exec` (one-off commands with the same port / `.run` contract as `start`); optional HTTP health gate before “ready”.
|
|
202
204
|
|
|
203
|
-
**Develop this repo:** `pnpm install` → `./run.sh` (default **
|
|
205
|
+
**Develop this repo:** `pnpm install` → `./run.sh` (thin runner; default **help**) delegates to **`bin/runctl`**. Maintainer npm flows live in [`scripts/maintain.sh`](scripts/maintain.sh). **`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))
|
|
204
206
|
|
|
205
207
|
**Publish (maintainers)** — workflow similar to elata’s release preflight, scaled for one package:
|
|
206
208
|
|
package/bin/runctl
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
# Runctl CLI — installed via pnpm/npm (devDependency .bin or global).
|
|
3
3
|
set -euo pipefail
|
|
4
4
|
RUNCTL_PKG_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")/.." && pwd)"
|
|
5
|
+
# Load optional repo .env / npm token (consumer project: set RUNCTL_PROJECT_ROOT or run from repo root).
|
|
6
|
+
# shellcheck source=../lib/repo-env.sh
|
|
7
|
+
source "$RUNCTL_PKG_ROOT/lib/repo-env.sh"
|
|
8
|
+
runctl_load_repo_env "${RUNCTL_PROJECT_ROOT:-$PWD}"
|
|
9
|
+
|
|
5
10
|
|
|
6
11
|
# Volta resolves pnpm from the *current* project's packageManager for `pnpm -v`, but
|
|
7
12
|
# `pnpm add -g` uses the default global pnpm (often older). Run pnpm as this package
|
|
@@ -18,25 +23,17 @@ _runctl_read_pnpm_version_from_package_json() {
|
|
|
18
23
|
' "$RUNCTL_PKG_ROOT/package.json" 2>/dev/null || true
|
|
19
24
|
}
|
|
20
25
|
|
|
21
|
-
#
|
|
22
|
-
# updates a different notion of "current" than the pnpm used for global -g installs (Volta/Corepack).
|
|
26
|
+
# After a successful pnpm global update: short tip (global pnpm vs project pin).
|
|
23
27
|
_runctl_print_pnpm_version_fixup_hint() {
|
|
24
28
|
local ver
|
|
25
29
|
ver="$(_runctl_read_pnpm_version_from_package_json)"
|
|
26
30
|
[[ -n "$ver" ]] || ver="X.Y.Z"
|
|
27
31
|
cat >&2 <<EOF
|
|
28
32
|
|
|
29
|
-
runctl
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
volta install pnpm@${ver}
|
|
35
|
-
corepack enable && corepack prepare pnpm@${ver} --activate
|
|
36
|
-
RUNCTL_PNPM_VERSION=${ver} runctl update git
|
|
37
|
-
runctl update --pm npm
|
|
38
|
-
|
|
39
|
-
Hide this hint: RUNCTL_UPDATE_SKIP_PNPM_HINT=1 (also suppressed when CI is set.)
|
|
33
|
+
${_d}┌${_r} ${_g}runctl${_r} ${_b}tip${_r} ${_d}· global pnpm can differ from your repo's packageManager pin.${_r}
|
|
34
|
+
${_d}│${_r} ${_c}volta install pnpm@${ver}${_r} ${_d}·${_r} ${_c}corepack prepare pnpm@${ver} --activate${_r}
|
|
35
|
+
${_d}│${_r} ${_c}RUNCTL_PNPM_VERSION=${ver} runctl update git${_r} ${_d}·${_r} ${_c}runctl update --pm npm${_r}
|
|
36
|
+
${_d}└${_r} ${_d}hide:${_r} ${_c}RUNCTL_UPDATE_SKIP_PNPM_HINT=1${_r}
|
|
40
37
|
EOF
|
|
41
38
|
}
|
|
42
39
|
|
|
@@ -74,6 +71,19 @@ if [[ -t 1 ]]; then
|
|
|
74
71
|
_c=$'\033[36m' _y=$'\033[33m' _g=$'\033[32m'
|
|
75
72
|
fi
|
|
76
73
|
|
|
74
|
+
# Semver from this package's package.json (for --version and update banners).
|
|
75
|
+
_runctl_pkg_version() {
|
|
76
|
+
local ver="?"
|
|
77
|
+
if command -v node >/dev/null 2>&1; then
|
|
78
|
+
ver="$(
|
|
79
|
+
node -e 'const fs=require("fs"); console.log(JSON.parse(fs.readFileSync(process.argv[1],"utf8")).version);' \
|
|
80
|
+
"$RUNCTL_PKG_ROOT/package.json" 2>/dev/null || echo "?"
|
|
81
|
+
)"
|
|
82
|
+
fi
|
|
83
|
+
printf '%s' "$ver"
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
77
87
|
usage() {
|
|
78
88
|
cat <<EOF
|
|
79
89
|
${_b}runctl${_r} — dev-server orchestrator & port manager
|
|
@@ -91,12 +101,18 @@ ${_y}Commands${_r}
|
|
|
91
101
|
${_c}ports gc${_r} Clean up stale port claims
|
|
92
102
|
${_c}env expand${_r} <manifest> [opts] Generate .env.local from manifest
|
|
93
103
|
${_c}doctor${_r} Check Node, tooling, and project basics
|
|
104
|
+
${_c}run-sh${_r} [--print|--write] [opts] Emit example ${_d}run.sh${_r} ${_d}(see runctl run-sh --help)${_r}
|
|
94
105
|
${_c}update${_r} [npm|git|auto] [opts] Update global runctl ${_d}(default: auto; see runctl update --help)${_r}
|
|
106
|
+
${_c}install${_r} [args] Install dependencies ${_d}(pnpm preferred; npm fallback when PM=pnpm)${_r}
|
|
107
|
+
${_c}run${_r} <script> [args] ${_d}package manager run (PM env, default pnpm)${_r}
|
|
108
|
+
${_c}exec${_r} [args] ${_d}package manager exec${_r}
|
|
109
|
+
${_c}test${_r} [args] ${_d}package manager test${_r}
|
|
95
110
|
|
|
96
111
|
${_y}Options${_r}
|
|
97
112
|
${_c}--script${_r} <name> Package script to run ${_d}(default: dev)${_r}
|
|
98
113
|
${_c}--open${_r} After a successful start, open the app URL ${_d}(same as ${_c}runctl open${_r})${_r}
|
|
99
|
-
${_c}
|
|
114
|
+
${_c}RUNCTL_PROJECT_ROOT${_r} Repo root for .env + install/run/test ${_d}(default: cwd)${_r}
|
|
115
|
+
${_c}version${_r} | ${_c}--version${_r} | ${_c}-v${_r} Print version and package install path
|
|
100
116
|
${_c}--help${_r}, ${_c}-h${_r} Show this help
|
|
101
117
|
|
|
102
118
|
${_d}Quick start — add to package.json:
|
|
@@ -415,9 +431,10 @@ cmd_update() {
|
|
|
415
431
|
fi
|
|
416
432
|
[[ -n "$MODE" ]] || MODE="auto"
|
|
417
433
|
|
|
418
|
-
local pm git_target
|
|
434
|
+
local pm git_target ver_now
|
|
419
435
|
pm="$(cmd_update_pick_pm "$PM")" || exit 1
|
|
420
436
|
git_target="${GIT_BASE}#${GIT_REF}"
|
|
437
|
+
ver_now="$(_runctl_pkg_version)"
|
|
421
438
|
|
|
422
439
|
_remove_conflicting_global_runctl() {
|
|
423
440
|
# A legacy package named "runctl" can own the same binary name and shadow @zendero/runctl.
|
|
@@ -450,12 +467,12 @@ cmd_update() {
|
|
|
450
467
|
|
|
451
468
|
_update_registry() {
|
|
452
469
|
local spec="${PKG}@latest"
|
|
453
|
-
|
|
470
|
+
printf '%s\n' "${_c}▶${_r} ${_b}runctl update${_r} ${_d}·${_r} ${_c}v${ver_now}${_r} ${_d}·${_r} registry ${_g}${spec}${_r}" >&2
|
|
454
471
|
_remove_conflicting_global_runctl
|
|
455
472
|
if [[ "$pm" == "pnpm" ]]; then
|
|
456
|
-
_runctl_pnpm_exec add -g --force "$spec"
|
|
473
|
+
_runctl_pnpm_exec add -g --force --loglevel error "$spec"
|
|
457
474
|
else
|
|
458
|
-
npm install -g --force "$spec"
|
|
475
|
+
npm install -g --force --silent --no-fund --no-audit "$spec"
|
|
459
476
|
fi
|
|
460
477
|
}
|
|
461
478
|
|
|
@@ -463,17 +480,17 @@ cmd_update() {
|
|
|
463
480
|
local sha spec
|
|
464
481
|
if sha="$(_resolve_git_ref_sha "$GIT_BASE" "$GIT_REF")"; then
|
|
465
482
|
spec="${GIT_BASE}#${sha}"
|
|
466
|
-
|
|
483
|
+
printf '%s\n' "${_c}▶${_r} ${_b}runctl update${_r} ${_d}·${_r} ${_c}v${ver_now}${_r} ${_d}·${_r} git ${_g}${GIT_REF}${_r} ${_d}@${sha:0:12}${_r}" >&2
|
|
467
484
|
else
|
|
468
485
|
spec="$git_target"
|
|
469
|
-
|
|
486
|
+
printf '%s\n' "${_c}▶${_r} ${_b}runctl update${_r} ${_d}·${_r} ${_c}v${ver_now}${_r} ${_d}·${_r} git ${_g}${git_target}${_r}" >&2
|
|
470
487
|
echo "runctl update: could not resolve ref to SHA; proceeding with ref directly." >&2
|
|
471
488
|
fi
|
|
472
489
|
_remove_conflicting_global_runctl
|
|
473
490
|
if [[ "$pm" == "pnpm" ]]; then
|
|
474
|
-
_runctl_pnpm_exec add -g --force "$spec"
|
|
491
|
+
_runctl_pnpm_exec add -g --force --loglevel error "$spec"
|
|
475
492
|
else
|
|
476
|
-
npm install -g --force "$spec"
|
|
493
|
+
npm install -g --force --silent --no-fund --no-audit "$spec"
|
|
477
494
|
fi
|
|
478
495
|
}
|
|
479
496
|
|
|
@@ -496,6 +513,10 @@ cmd_update() {
|
|
|
496
513
|
;;
|
|
497
514
|
esac
|
|
498
515
|
|
|
516
|
+
local ver_installed
|
|
517
|
+
ver_installed="$(_runctl_pkg_version)"
|
|
518
|
+
printf '%s\n' "${_g}✓${_r} ${_b}runctl${_r} ${_c}v${ver_installed}${_r} ${_d}· global install refreshed${_r}" >&2
|
|
519
|
+
|
|
499
520
|
_runctl_maybe_print_pnpm_update_hint "$pm"
|
|
500
521
|
}
|
|
501
522
|
|
|
@@ -582,15 +603,185 @@ cmd_logs() {
|
|
|
582
603
|
tail -n "$lines" "$logf"
|
|
583
604
|
}
|
|
584
605
|
|
|
606
|
+
_runctl_pm() {
|
|
607
|
+
printf '%s' "${PM:-pnpm}"
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
cmd_install() {
|
|
611
|
+
local root="${RUNCTL_PROJECT_ROOT:-$PWD}"
|
|
612
|
+
if [[ "$(_runctl_pm)" == "pnpm" ]]; then
|
|
613
|
+
if command -v pnpm >/dev/null 2>&1; then
|
|
614
|
+
(cd "$root" && exec pnpm install "$@")
|
|
615
|
+
elif command -v npm >/dev/null 2>&1; then
|
|
616
|
+
echo "runctl install: pnpm not found; using npm" >&2
|
|
617
|
+
(cd "$root" && exec npm install "$@")
|
|
618
|
+
else
|
|
619
|
+
echo "runctl install: install pnpm (preferred) or npm" >&2
|
|
620
|
+
exit 1
|
|
621
|
+
fi
|
|
622
|
+
else
|
|
623
|
+
local pm="$(_runctl_pm)"
|
|
624
|
+
command -v "$pm" >/dev/null 2>&1 || {
|
|
625
|
+
echo "runctl install: $pm not on PATH" >&2
|
|
626
|
+
exit 1
|
|
627
|
+
}
|
|
628
|
+
(cd "$root" && exec "$pm" install "$@")
|
|
629
|
+
fi
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
cmd_run() {
|
|
633
|
+
local root="${RUNCTL_PROJECT_ROOT:-$PWD}"
|
|
634
|
+
local pm="$(_runctl_pm)"
|
|
635
|
+
command -v "$pm" >/dev/null 2>&1 || {
|
|
636
|
+
echo "runctl run: $pm not on PATH (set PM=…)" >&2
|
|
637
|
+
exit 1
|
|
638
|
+
}
|
|
639
|
+
(cd "$root" && exec "$pm" run "$@")
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
cmd_exec() {
|
|
643
|
+
local root="${RUNCTL_PROJECT_ROOT:-$PWD}"
|
|
644
|
+
local pm="$(_runctl_pm)"
|
|
645
|
+
command -v "$pm" >/dev/null 2>&1 || {
|
|
646
|
+
echo "runctl exec: $pm not on PATH (set PM=…)" >&2
|
|
647
|
+
exit 1
|
|
648
|
+
}
|
|
649
|
+
(cd "$root" && exec "$pm" exec "$@")
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
cmd_test() {
|
|
653
|
+
local root="${RUNCTL_PROJECT_ROOT:-$PWD}"
|
|
654
|
+
local pm="$(_runctl_pm)"
|
|
655
|
+
command -v "$pm" >/dev/null 2>&1 || {
|
|
656
|
+
echo "runctl test: $pm not on PATH (set PM=…)" >&2
|
|
657
|
+
exit 1
|
|
658
|
+
}
|
|
659
|
+
(cd "$root" && exec "$pm" test "$@")
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
cmd_run_sh_usage() {
|
|
663
|
+
cat <<EOF
|
|
664
|
+
${_b}runctl run-sh${_r} — emit the canonical example ${_c}run.sh${_r} (thin wrapper → runctl)
|
|
665
|
+
|
|
666
|
+
${_y}Usage:${_r}
|
|
667
|
+
runctl run-sh [--print]
|
|
668
|
+
runctl run-sh --write [--force] [-C dir]
|
|
669
|
+
|
|
670
|
+
${_y}Description${_r}
|
|
671
|
+
Reads ${_c}examples/run.sh.example${_r} from this package (falls back to ${_c}run.sh${_r}).
|
|
672
|
+
|
|
673
|
+
${_c}--print${_r} ${_d}(default)${_r} prints to stdout — redirect into your repo:
|
|
674
|
+
${_d}runctl run-sh > run.sh && chmod +x run.sh${_r}
|
|
675
|
+
|
|
676
|
+
${_c}--write${_r} writes ${_c}run.sh${_r} under the target directory (default: ${_c}\$PWD${_r} or ${_c}RUNCTL_PROJECT_ROOT${_r} when set).
|
|
677
|
+
Refuses to overwrite an existing file unless ${_c}--force${_r}.
|
|
678
|
+
|
|
679
|
+
${_y}Options${_r}
|
|
680
|
+
${_c}--print${_r} Print to stdout ${_d}(default when --write not used)${_r}
|
|
681
|
+
${_c}--write${_r} | ${_c}-w${_r} Write ${_c}run.sh${_r} in the target directory
|
|
682
|
+
${_c}--force${_r} | ${_c}-f${_r} Overwrite ${_c}run.sh${_r} if it already exists
|
|
683
|
+
${_c}-C${_r} ${_c}dir${_r} | ${_c}--dir${_r} ${_c}dir${_r} Target directory (default: ${_c}RUNCTL_PROJECT_ROOT${_r} or cwd)
|
|
684
|
+
${_c}-h${_r} | ${_c}--help${_r} Show this help
|
|
685
|
+
EOF
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
_runctl_run_sh_example_path() {
|
|
689
|
+
local ex="$RUNCTL_PKG_ROOT/examples/run.sh.example"
|
|
690
|
+
local fb="$RUNCTL_PKG_ROOT/run.sh"
|
|
691
|
+
if [[ -f "$ex" && -r "$ex" ]]; then
|
|
692
|
+
printf '%s' "$ex"
|
|
693
|
+
return 0
|
|
694
|
+
fi
|
|
695
|
+
if [[ -f "$fb" && -r "$fb" ]]; then
|
|
696
|
+
printf '%s' "$fb"
|
|
697
|
+
return 0
|
|
698
|
+
fi
|
|
699
|
+
return 1
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
cmd_run_sh() {
|
|
703
|
+
local action=print
|
|
704
|
+
local force=0
|
|
705
|
+
local target_dir=""
|
|
706
|
+
|
|
707
|
+
while [[ $# -gt 0 ]]; do
|
|
708
|
+
case "$1" in
|
|
709
|
+
--print)
|
|
710
|
+
action=print
|
|
711
|
+
shift
|
|
712
|
+
;;
|
|
713
|
+
--write | -w)
|
|
714
|
+
action=write
|
|
715
|
+
shift
|
|
716
|
+
;;
|
|
717
|
+
--force | -f)
|
|
718
|
+
force=1
|
|
719
|
+
shift
|
|
720
|
+
;;
|
|
721
|
+
-C | --dir)
|
|
722
|
+
if [[ $# -lt 2 ]]; then
|
|
723
|
+
echo "runctl run-sh: $1 requires a directory" >&2
|
|
724
|
+
exit 1
|
|
725
|
+
fi
|
|
726
|
+
target_dir="$(cd "$2" && pwd)" || exit 1
|
|
727
|
+
shift 2
|
|
728
|
+
;;
|
|
729
|
+
-h | --help)
|
|
730
|
+
cmd_run_sh_usage
|
|
731
|
+
return 0
|
|
732
|
+
;;
|
|
733
|
+
-*)
|
|
734
|
+
echo "runctl run-sh: unknown option: $1" >&2
|
|
735
|
+
cmd_run_sh_usage >&2
|
|
736
|
+
exit 1
|
|
737
|
+
;;
|
|
738
|
+
*)
|
|
739
|
+
echo "runctl run-sh: unexpected argument: $1" >&2
|
|
740
|
+
cmd_run_sh_usage >&2
|
|
741
|
+
exit 1
|
|
742
|
+
;;
|
|
743
|
+
esac
|
|
744
|
+
done
|
|
745
|
+
|
|
746
|
+
if [[ -z "$target_dir" ]]; then
|
|
747
|
+
if [[ -n "${RUNCTL_PROJECT_ROOT:-}" && -d "$RUNCTL_PROJECT_ROOT" ]]; then
|
|
748
|
+
target_dir="$(cd "$RUNCTL_PROJECT_ROOT" && pwd)"
|
|
749
|
+
else
|
|
750
|
+
target_dir="$(pwd)"
|
|
751
|
+
fi
|
|
752
|
+
fi
|
|
753
|
+
|
|
754
|
+
local path
|
|
755
|
+
path="$(_runctl_run_sh_example_path)" || {
|
|
756
|
+
echo "runctl run-sh: example not found under $RUNCTL_PKG_ROOT" >&2
|
|
757
|
+
exit 1
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
if [[ "$action" == "write" ]]; then
|
|
761
|
+
local out="$target_dir/run.sh"
|
|
762
|
+
if [[ -e "$out" && "$force" -ne 1 ]]; then
|
|
763
|
+
echo "runctl run-sh: $out already exists (use --force to overwrite)" >&2
|
|
764
|
+
exit 1
|
|
765
|
+
fi
|
|
766
|
+
cat "$path" >"$out"
|
|
767
|
+
chmod +x "$out"
|
|
768
|
+
echo "runctl run-sh: wrote $out" >&2
|
|
769
|
+
return 0
|
|
770
|
+
fi
|
|
771
|
+
|
|
772
|
+
cat "$path"
|
|
773
|
+
}
|
|
774
|
+
|
|
585
775
|
cmd_version_print() {
|
|
586
|
-
local ver
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
776
|
+
local ver
|
|
777
|
+
ver="$(_runctl_pkg_version)"
|
|
778
|
+
if [[ -t 1 ]]; then
|
|
779
|
+
printf '%s\n\n' "${_b}runctl${_r} ${_c}v${ver}${_r}"
|
|
780
|
+
else
|
|
781
|
+
printf 'runctl %s\n\n' "$ver"
|
|
592
782
|
fi
|
|
593
|
-
printf '%s\n' "
|
|
783
|
+
printf '%s\n' "${_d}Install:${_r}"
|
|
784
|
+
printf '%s\n' " $RUNCTL_PKG_ROOT"
|
|
594
785
|
}
|
|
595
786
|
|
|
596
787
|
main() {
|
|
@@ -607,6 +798,11 @@ main() {
|
|
|
607
798
|
env) cmd_env "$@" ;;
|
|
608
799
|
doctor) cmd_doctor "$@" ;;
|
|
609
800
|
update) cmd_update "$@" ;;
|
|
801
|
+
install) cmd_install "$@" ;;
|
|
802
|
+
run) cmd_run "$@" ;;
|
|
803
|
+
exec) cmd_exec "$@" ;;
|
|
804
|
+
test) cmd_test "$@" ;;
|
|
805
|
+
run-sh) cmd_run_sh "$@" ;;
|
|
610
806
|
|
|
611
807
|
# Plumbing
|
|
612
808
|
lib-path) printf '%s\n' "$RUNCTL_PKG_ROOT/lib/run-lib.sh" ;;
|
package/docs/vercel-and-env.md
CHANGED
|
@@ -13,7 +13,7 @@ Keep a **manifest** (not loaded directly by Next/Vite) that lists each real valu
|
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
15
|
pnpm exec runctl env expand env.manifest --out .env.local
|
|
16
|
-
# or:
|
|
16
|
+
# or: runctl env expand env.manifest --out .env.local
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
Add **`.env.local`** (and optionally `.env.manifest` if it contains no secrets—usually it *does*, so keep the manifest **gitignored** or use a **`.env.manifest.example`** without real values).
|
|
@@ -81,6 +81,6 @@ Sometimes two env vars exist for historical reasons. If both client and server c
|
|
|
81
81
|
|
|
82
82
|
| Goal | Approach |
|
|
83
83
|
|------|-----------|
|
|
84
|
-
| Same value, many names **locally** | `env.manifest` + `expand
|
|
84
|
+
| Same value, many names **locally** | `env.manifest` + `runctl env expand` → `.env.local` |
|
|
85
85
|
| Match Vercel → laptop | `vercel env pull` + merge with generated `.env.local` |
|
|
86
86
|
| Same value, many names **on Vercel** | Duplicate in UI, or scripted `vercel env add`, or a secrets manager with Vercel sync |
|
package/examples/run.sh.example
CHANGED
|
@@ -1,82 +1,91 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
#
|
|
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`).
|
|
10
|
-
|
|
2
|
+
# Thin project runner: sets RUNCTL_PROJECT_ROOT + PM/PORT, then delegates to bin/runctl.
|
|
3
|
+
# Maintainer npm flows: scripts/maintain.sh
|
|
11
4
|
set -euo pipefail
|
|
12
5
|
|
|
13
|
-
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
14
|
-
|
|
6
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
7
|
+
export RUNCTL_PROJECT_ROOT="$ROOT"
|
|
8
|
+
export RUNCTL_PKG_ROOT="$ROOT"
|
|
9
|
+
export RUN_SH_TASK="${RUN_SH_TASK:-}"
|
|
10
|
+
|
|
11
|
+
PM="${PM:-pnpm}"
|
|
12
|
+
export PM
|
|
13
|
+
RUNCTL_DEV_SCRIPT="${RUNCTL_DEV_SCRIPT:-dev}"
|
|
14
|
+
PORT="${PORT:-3000}"
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
RUN_LIB="$(
|
|
19
|
-
cd "$ROOT" && RUNCTL_PACKAGE="$_rp" node -e "
|
|
20
|
-
const p = require('path');
|
|
21
|
-
const n = process.env.RUNCTL_PACKAGE;
|
|
22
|
-
console.log(p.join(p.dirname(require.resolve(n + '/package.json')), 'lib', 'run-lib.sh'));
|
|
23
|
-
"
|
|
24
|
-
)" || {
|
|
25
|
-
echo "run.sh: add runctl — pnpm add -D ${_rp}" >&2
|
|
26
|
-
exit 1
|
|
27
|
-
}
|
|
28
|
-
fi
|
|
29
|
-
[[ -f "$RUN_LIB" ]] || {
|
|
30
|
-
echo "run.sh: RUN_LIB missing: $RUN_LIB" >&2
|
|
16
|
+
die() {
|
|
17
|
+
echo "run.sh${RUN_SH_TASK:+ ($RUN_SH_TASK)}: $*" >&2
|
|
31
18
|
exit 1
|
|
32
19
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
20
|
+
|
|
21
|
+
runctl_cmd() {
|
|
22
|
+
if [[ -x "$ROOT/bin/runctl" ]]; then
|
|
23
|
+
exec "$ROOT/bin/runctl" "$@"
|
|
24
|
+
elif [[ -x "$ROOT/node_modules/.bin/runctl" ]]; then
|
|
25
|
+
exec "$ROOT/node_modules/.bin/runctl" "$@"
|
|
26
|
+
elif command -v pnpm >/dev/null 2>&1 && pnpm exec runctl version >/dev/null 2>&1; then
|
|
27
|
+
exec pnpm exec runctl "$@"
|
|
28
|
+
elif command -v runctl >/dev/null 2>&1; then
|
|
29
|
+
exec runctl "$@"
|
|
30
|
+
else
|
|
31
|
+
die "runctl not found — install deps or add @zendero/runctl"
|
|
32
|
+
fi
|
|
33
|
+
}
|
|
36
34
|
|
|
37
35
|
usage() {
|
|
38
36
|
cat <<'EOF'
|
|
39
|
-
Usage: ./run.sh <command>
|
|
37
|
+
Usage: ./run.sh <command> [args]
|
|
40
38
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
status
|
|
44
|
-
|
|
39
|
+
install | run | exec | test → runctl (same as: runctl install|run|exec|test)
|
|
40
|
+
dev | start → runctl start this repo (--script RUNCTL_DEV_SCRIPT)
|
|
41
|
+
open | stop | status | doctor | ports | ps | logs | env | update | …
|
|
42
|
+
env-expand | lib-path
|
|
43
|
+
release-check | publish | release | promote | npm-whoami → scripts/maintain.sh
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
Environment:
|
|
46
|
+
RUNCTL_PROJECT_ROOT Set automatically to this script’s directory
|
|
47
|
+
RUNCTL_DEV_SCRIPT npm script for dev/start (default: dev)
|
|
48
|
+
PORT Passed to dev/start
|
|
49
|
+
PM package manager (default: pnpm)
|
|
49
50
|
EOF
|
|
51
|
+
echo ""
|
|
52
|
+
runctl_cmd help
|
|
50
53
|
}
|
|
51
54
|
|
|
52
|
-
|
|
53
|
-
|
|
55
|
+
main() {
|
|
56
|
+
[[ $# -eq 0 ]] && set -- help
|
|
57
|
+
local cmd="${1:-}"
|
|
58
|
+
shift || true
|
|
59
|
+
|
|
60
|
+
case "$cmd" in
|
|
61
|
+
install | run | exec | test)
|
|
62
|
+
RUN_SH_TASK="$cmd"
|
|
63
|
+
export RUN_SH_TASK
|
|
64
|
+
runctl_cmd "$cmd" "$@"
|
|
65
|
+
;;
|
|
66
|
+
dev | start)
|
|
67
|
+
export PORT
|
|
68
|
+
runctl_cmd start "$ROOT" --script "$RUNCTL_DEV_SCRIPT" "$@"
|
|
69
|
+
;;
|
|
70
|
+
release-check | publish | release | promote | npm-whoami)
|
|
71
|
+
exec "$ROOT/scripts/maintain.sh" "$cmd" "$@"
|
|
72
|
+
;;
|
|
73
|
+
ports-gc | gc)
|
|
74
|
+
runctl_cmd ports gc "$@"
|
|
75
|
+
;;
|
|
76
|
+
lib-path)
|
|
77
|
+
runctl_cmd lib-path
|
|
78
|
+
;;
|
|
79
|
+
env-expand | expand-env)
|
|
80
|
+
runctl_cmd env expand "$@"
|
|
81
|
+
;;
|
|
82
|
+
help | -h | --help)
|
|
83
|
+
usage
|
|
84
|
+
;;
|
|
85
|
+
*)
|
|
86
|
+
runctl_cmd "$cmd" "$@"
|
|
87
|
+
;;
|
|
88
|
+
esac
|
|
89
|
+
}
|
|
54
90
|
|
|
55
|
-
|
|
56
|
-
dev)
|
|
57
|
-
run_with_lock run_start_package_dev "$@"
|
|
58
|
-
;;
|
|
59
|
-
stop)
|
|
60
|
-
run_stop_all
|
|
61
|
-
;;
|
|
62
|
-
status)
|
|
63
|
-
run_local_status
|
|
64
|
-
;;
|
|
65
|
-
install)
|
|
66
|
-
if command -v pnpm >/dev/null 2>&1; then
|
|
67
|
-
(cd "$ROOT" && pnpm install)
|
|
68
|
-
elif command -v npm >/dev/null 2>&1; then
|
|
69
|
-
(cd "$ROOT" && npm install)
|
|
70
|
-
else
|
|
71
|
-
echo "Install pnpm or npm" >&2
|
|
72
|
-
exit 1
|
|
73
|
-
fi
|
|
74
|
-
;;
|
|
75
|
-
help | -h | --help)
|
|
76
|
-
usage
|
|
77
|
-
;;
|
|
78
|
-
*)
|
|
79
|
-
usage
|
|
80
|
-
exit 1
|
|
81
|
-
;;
|
|
82
|
-
esac
|
|
91
|
+
main "$@"
|
package/lib/repo-env.sh
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Optional repo-root env for npm auth — sourced by bin/runctl and scripts/maintain.sh.
|
|
3
|
+
# Resolves root as: $1, else RUNCTL_PROJECT_ROOT, else PWD.
|
|
4
|
+
# Idempotent per shell (RUNCTL_REPO_ENV_LOADED).
|
|
5
|
+
|
|
6
|
+
runctl_load_repo_env() {
|
|
7
|
+
[[ -n "${RUNCTL_REPO_ENV_LOADED:-}" ]] && return 0
|
|
8
|
+
local root="${1:-${RUNCTL_PROJECT_ROOT:-$PWD}}"
|
|
9
|
+
|
|
10
|
+
if [[ -z "${NPM_TOKEN:-}" ]] && [[ -f "$root/.env" ]]; then
|
|
11
|
+
local line key val
|
|
12
|
+
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
13
|
+
line="${line#"${line%%[![:space:]]*}"}"
|
|
14
|
+
line="${line%"${line##*[![:space:]]}"}"
|
|
15
|
+
[[ -z "$line" || "$line" == \#* ]] && continue
|
|
16
|
+
if [[ "$line" == export\ * ]]; then
|
|
17
|
+
line="${line#export }"
|
|
18
|
+
fi
|
|
19
|
+
[[ "$line" == *"="* ]] || continue
|
|
20
|
+
key="${line%%=*}"
|
|
21
|
+
val="${line#*=}"
|
|
22
|
+
if [[ "$key" != "NPM_TOKEN" && "$key" != "npm_token" ]]; then
|
|
23
|
+
continue
|
|
24
|
+
fi
|
|
25
|
+
val="${val%$'\r'}"
|
|
26
|
+
if [[ "$val" == \"*\" ]]; then
|
|
27
|
+
val="${val#\"}"
|
|
28
|
+
val="${val%\"}"
|
|
29
|
+
elif [[ "$val" == \'*\' ]]; then
|
|
30
|
+
val="${val#\'}"
|
|
31
|
+
val="${val%\'}"
|
|
32
|
+
fi
|
|
33
|
+
export NPM_TOKEN="$val"
|
|
34
|
+
break
|
|
35
|
+
done <"$root/.env"
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
if [[ -f "$root/.env" ]]; then
|
|
39
|
+
set -a
|
|
40
|
+
# shellcheck source=/dev/null
|
|
41
|
+
source "$root/.env"
|
|
42
|
+
set +a
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
if [[ -z "${NODE_AUTH_TOKEN:-}" ]]; then
|
|
46
|
+
if [[ -n "${NPM_TOKEN:-}" ]]; then
|
|
47
|
+
export NODE_AUTH_TOKEN="$NPM_TOKEN"
|
|
48
|
+
elif [[ -n "${npm_token:-}" ]]; then
|
|
49
|
+
export NODE_AUTH_TOKEN="$npm_token"
|
|
50
|
+
fi
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
export RUNCTL_REPO_ENV_LOADED=1
|
|
54
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zendero/runctl",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
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",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"files": [
|
|
31
31
|
"bin",
|
|
32
|
+
"run.sh",
|
|
32
33
|
"lib",
|
|
33
34
|
"scripts",
|
|
34
35
|
"examples",
|
|
@@ -42,7 +43,7 @@
|
|
|
42
43
|
"release-check": "bash ./run.sh release-check",
|
|
43
44
|
"promote": "bash ./run.sh promote",
|
|
44
45
|
"release": "bash ./run.sh release latest",
|
|
45
|
-
"env:expand": "
|
|
46
|
+
"env:expand": "bash ./run.sh env-expand",
|
|
46
47
|
"link-global": "pnpm link --global"
|
|
47
48
|
}
|
|
48
49
|
}
|
package/run.sh
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Thin project runner: sets RUNCTL_PROJECT_ROOT + PM/PORT, then delegates to bin/runctl.
|
|
3
|
+
# Maintainer npm flows: scripts/maintain.sh
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
7
|
+
export RUNCTL_PROJECT_ROOT="$ROOT"
|
|
8
|
+
export RUNCTL_PKG_ROOT="$ROOT"
|
|
9
|
+
export RUN_SH_TASK="${RUN_SH_TASK:-}"
|
|
10
|
+
|
|
11
|
+
PM="${PM:-pnpm}"
|
|
12
|
+
export PM
|
|
13
|
+
RUNCTL_DEV_SCRIPT="${RUNCTL_DEV_SCRIPT:-dev}"
|
|
14
|
+
PORT="${PORT:-3000}"
|
|
15
|
+
|
|
16
|
+
die() {
|
|
17
|
+
echo "run.sh${RUN_SH_TASK:+ ($RUN_SH_TASK)}: $*" >&2
|
|
18
|
+
exit 1
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
runctl_cmd() {
|
|
22
|
+
if [[ -x "$ROOT/bin/runctl" ]]; then
|
|
23
|
+
exec "$ROOT/bin/runctl" "$@"
|
|
24
|
+
elif [[ -x "$ROOT/node_modules/.bin/runctl" ]]; then
|
|
25
|
+
exec "$ROOT/node_modules/.bin/runctl" "$@"
|
|
26
|
+
elif command -v pnpm >/dev/null 2>&1 && pnpm exec runctl version >/dev/null 2>&1; then
|
|
27
|
+
exec pnpm exec runctl "$@"
|
|
28
|
+
elif command -v runctl >/dev/null 2>&1; then
|
|
29
|
+
exec runctl "$@"
|
|
30
|
+
else
|
|
31
|
+
die "runctl not found — install deps or add @zendero/runctl"
|
|
32
|
+
fi
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
usage() {
|
|
36
|
+
cat <<'EOF'
|
|
37
|
+
Usage: ./run.sh <command> [args]
|
|
38
|
+
|
|
39
|
+
install | run | exec | test → runctl (same as: runctl install|run|exec|test)
|
|
40
|
+
dev | start → runctl start this repo (--script RUNCTL_DEV_SCRIPT)
|
|
41
|
+
open | stop | status | doctor | ports | ps | logs | env | update | …
|
|
42
|
+
env-expand | lib-path
|
|
43
|
+
release-check | publish | release | promote | npm-whoami → scripts/maintain.sh
|
|
44
|
+
|
|
45
|
+
Environment:
|
|
46
|
+
RUNCTL_PROJECT_ROOT Set automatically to this script’s directory
|
|
47
|
+
RUNCTL_DEV_SCRIPT npm script for dev/start (default: dev)
|
|
48
|
+
PORT Passed to dev/start
|
|
49
|
+
PM package manager (default: pnpm)
|
|
50
|
+
EOF
|
|
51
|
+
echo ""
|
|
52
|
+
runctl_cmd help
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
main() {
|
|
56
|
+
[[ $# -eq 0 ]] && set -- help
|
|
57
|
+
local cmd="${1:-}"
|
|
58
|
+
shift || true
|
|
59
|
+
|
|
60
|
+
case "$cmd" in
|
|
61
|
+
install | run | exec | test)
|
|
62
|
+
RUN_SH_TASK="$cmd"
|
|
63
|
+
export RUN_SH_TASK
|
|
64
|
+
runctl_cmd "$cmd" "$@"
|
|
65
|
+
;;
|
|
66
|
+
dev | start)
|
|
67
|
+
export PORT
|
|
68
|
+
runctl_cmd start "$ROOT" --script "$RUNCTL_DEV_SCRIPT" "$@"
|
|
69
|
+
;;
|
|
70
|
+
release-check | publish | release | promote | npm-whoami)
|
|
71
|
+
exec "$ROOT/scripts/maintain.sh" "$cmd" "$@"
|
|
72
|
+
;;
|
|
73
|
+
ports-gc | gc)
|
|
74
|
+
runctl_cmd ports gc "$@"
|
|
75
|
+
;;
|
|
76
|
+
lib-path)
|
|
77
|
+
runctl_cmd lib-path
|
|
78
|
+
;;
|
|
79
|
+
env-expand | expand-env)
|
|
80
|
+
runctl_cmd env expand "$@"
|
|
81
|
+
;;
|
|
82
|
+
help | -h | --help)
|
|
83
|
+
usage
|
|
84
|
+
;;
|
|
85
|
+
*)
|
|
86
|
+
runctl_cmd "$cmd" "$@"
|
|
87
|
+
;;
|
|
88
|
+
esac
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
main "$@"
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Maintainer-only: npm publish, release, promote, release-check, whoami.
|
|
3
|
+
# Invoked via ./run.sh <command> or directly: scripts/maintain.sh <command>
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
|
7
|
+
|
|
8
|
+
die() {
|
|
9
|
+
echo "maintain: $*" >&2
|
|
10
|
+
exit 1
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export RUNCTL_PROJECT_ROOT="$ROOT"
|
|
14
|
+
# shellcheck source=../lib/repo-env.sh
|
|
15
|
+
source "$ROOT/lib/repo-env.sh"
|
|
16
|
+
runctl_load_repo_env "$ROOT"
|
|
17
|
+
|
|
18
|
+
in_repo() {
|
|
19
|
+
(cd "$ROOT" && "$@")
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
npm_publish_token() {
|
|
23
|
+
printf '%s' "${NPM_TOKEN:-${NODE_AUTH_TOKEN:-${npm_token:-}}}"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
runctl_npm_userconfig_from_token() {
|
|
27
|
+
local tok="$1"
|
|
28
|
+
local f
|
|
29
|
+
f="$(mktemp "${TMPDIR:-/tmp}/runctl-npm-auth.XXXXXX")" || return 1
|
|
30
|
+
chmod 600 "$f" || true
|
|
31
|
+
printf '//registry.npmjs.org/:_authToken=%s\n' "$tok" >"$f" || {
|
|
32
|
+
rm -f "$f"
|
|
33
|
+
return 1
|
|
34
|
+
}
|
|
35
|
+
printf '%s' "$f"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
validate_dist_tag() {
|
|
39
|
+
case "${1:-latest}" in
|
|
40
|
+
latest | next) return 0 ;;
|
|
41
|
+
*)
|
|
42
|
+
echo "maintain publish: unsupported dist-tag '$1'. Use: latest or next." >&2
|
|
43
|
+
return 1
|
|
44
|
+
;;
|
|
45
|
+
esac
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
release_commit_and_push_if_needed() {
|
|
49
|
+
local dist_tag="$1"
|
|
50
|
+
local pkg_name="$2"
|
|
51
|
+
local pkg_version="$3"
|
|
52
|
+
if ! command -v git >/dev/null 2>&1; then
|
|
53
|
+
echo "maintain release: git not found; skipping commit/push step" >&2
|
|
54
|
+
return 0
|
|
55
|
+
fi
|
|
56
|
+
if ! git -C "$ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
57
|
+
echo "maintain release: not in a git worktree; skipping commit/push step" >&2
|
|
58
|
+
return 0
|
|
59
|
+
fi
|
|
60
|
+
if [[ -n "$(git -C "$ROOT" status --porcelain)" ]]; then
|
|
61
|
+
local branch
|
|
62
|
+
branch="$(git -C "$ROOT" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
|
|
63
|
+
[[ -n "$branch" && "$branch" != "HEAD" ]] || die "release commit/push requires a branch checkout (not detached HEAD)"
|
|
64
|
+
git -C "$ROOT" add -A
|
|
65
|
+
git -C "$ROOT" commit -m "release: publish ${pkg_name}@${pkg_version} (${dist_tag})"
|
|
66
|
+
git -C "$ROOT" push -u origin "$branch"
|
|
67
|
+
echo "maintain release: committed and pushed release changes on $branch"
|
|
68
|
+
else
|
|
69
|
+
echo "maintain release: no local git changes; skipping commit/push"
|
|
70
|
+
fi
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
package_field() {
|
|
74
|
+
local field="$1"
|
|
75
|
+
node -e '
|
|
76
|
+
const fs = require("fs");
|
|
77
|
+
const path = require("path");
|
|
78
|
+
const f = path.join(process.argv[1], "package.json");
|
|
79
|
+
const pkg = JSON.parse(fs.readFileSync(f, "utf8"));
|
|
80
|
+
const key = process.argv[2];
|
|
81
|
+
if (!pkg[key]) process.exit(1);
|
|
82
|
+
process.stdout.write(String(pkg[key]));
|
|
83
|
+
' "$ROOT" "$field"
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
publish_failed_hint() {
|
|
87
|
+
local op="$1"
|
|
88
|
+
echo "maintain ${op}: publish failed." >&2
|
|
89
|
+
echo " If ./run.sh npm-whoami works: check scoped write / maintainer on npm." >&2
|
|
90
|
+
echo " Run ./run.sh release-check before publish." >&2
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
maint_release_check() {
|
|
94
|
+
echo "maintain release-check: runctl doctor..."
|
|
95
|
+
"$ROOT/bin/runctl" doctor "$ROOT" || die "doctor failed"
|
|
96
|
+
echo "maintain release-check: pack --dry-run..."
|
|
97
|
+
if command -v pnpm >/dev/null 2>&1; then
|
|
98
|
+
in_repo pnpm pack --dry-run >/dev/null || die "pnpm pack --dry-run failed"
|
|
99
|
+
elif command -v npm >/dev/null 2>&1; then
|
|
100
|
+
in_repo npm pack --dry-run >/dev/null || die "npm pack --dry-run failed"
|
|
101
|
+
else
|
|
102
|
+
die "install pnpm or npm for release-check"
|
|
103
|
+
fi
|
|
104
|
+
local tok _rc _ec
|
|
105
|
+
tok="$(npm_publish_token)"
|
|
106
|
+
if [[ -n "$tok" ]]; then
|
|
107
|
+
echo "maintain release-check: npm whoami..."
|
|
108
|
+
_rc="$(runctl_npm_userconfig_from_token "$tok")" || die "could not write temp npmrc"
|
|
109
|
+
NPM_CONFIG_USERCONFIG="$_rc" npm whoami || {
|
|
110
|
+
_ec=$?
|
|
111
|
+
rm -f "$_rc"
|
|
112
|
+
die "npm whoami failed (exit $_ec)"
|
|
113
|
+
}
|
|
114
|
+
rm -f "$_rc"
|
|
115
|
+
else
|
|
116
|
+
echo "maintain release-check: skip npm whoami (no NPM_TOKEN / npm_token in .env)" >&2
|
|
117
|
+
fi
|
|
118
|
+
echo "maintain release-check: ok"
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
maint_publish_or_release() {
|
|
122
|
+
local cmd="$1"
|
|
123
|
+
shift
|
|
124
|
+
if [[ "${1:-}" == "all" ]]; then
|
|
125
|
+
shift
|
|
126
|
+
fi
|
|
127
|
+
local dist_tag="${1:-latest}"
|
|
128
|
+
validate_dist_tag "$dist_tag" || exit 1
|
|
129
|
+
if [[ $# -gt 0 ]]; then
|
|
130
|
+
shift
|
|
131
|
+
fi
|
|
132
|
+
local token pkg_name pkg_version remote_version
|
|
133
|
+
token="$(npm_publish_token)"
|
|
134
|
+
[[ -n "$token" ]] || die "missing npm token — set NPM_TOKEN in .env (or NODE_AUTH_TOKEN / npm_token)"
|
|
135
|
+
export NODE_AUTH_TOKEN="$token"
|
|
136
|
+
export PUBLISH_OK=1
|
|
137
|
+
pkg_name="$(package_field name)"
|
|
138
|
+
pkg_version="$(package_field version)"
|
|
139
|
+
remote_version="$(npm view "${pkg_name}@${pkg_version}" version 2>/dev/null || true)"
|
|
140
|
+
|
|
141
|
+
local is_dry_run=0 arg
|
|
142
|
+
for arg in "$@"; do
|
|
143
|
+
if [[ "$arg" == "--dry-run" ]]; then
|
|
144
|
+
is_dry_run=1
|
|
145
|
+
break
|
|
146
|
+
fi
|
|
147
|
+
done
|
|
148
|
+
|
|
149
|
+
if [[ -n "$remote_version" && "$is_dry_run" -eq 0 ]]; then
|
|
150
|
+
echo "maintain ${cmd}: ${pkg_name}@${pkg_version} already exists; bumping patch version..."
|
|
151
|
+
if command -v pnpm >/dev/null 2>&1; then
|
|
152
|
+
in_repo pnpm version patch --no-git-tag-version || die "pnpm version patch failed"
|
|
153
|
+
else
|
|
154
|
+
in_repo npm version patch --no-git-tag-version || die "npm version patch failed"
|
|
155
|
+
fi
|
|
156
|
+
pkg_version="$(package_field version)"
|
|
157
|
+
echo "maintain ${cmd}: publishing ${pkg_name}@${pkg_version} with dist-tag '${dist_tag}'"
|
|
158
|
+
elif [[ -n "$remote_version" ]]; then
|
|
159
|
+
echo "maintain ${cmd}: ${pkg_name}@${pkg_version} already exists; not bumping during --dry-run"
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
local npmrc _pub_ok=0
|
|
163
|
+
npmrc="$(runctl_npm_userconfig_from_token "$token")" || exit 1
|
|
164
|
+
|
|
165
|
+
if command -v pnpm >/dev/null 2>&1; then
|
|
166
|
+
(cd "$ROOT" && NPM_CONFIG_USERCONFIG="$npmrc" pnpm publish --access public --tag "$dist_tag" --no-git-checks "$@") && _pub_ok=1
|
|
167
|
+
elif command -v npm >/dev/null 2>&1; then
|
|
168
|
+
(cd "$ROOT" && NPM_CONFIG_USERCONFIG="$npmrc" npm publish --access public --tag "$dist_tag" --no-git-checks "$@") && _pub_ok=1
|
|
169
|
+
else
|
|
170
|
+
rm -f "$npmrc"
|
|
171
|
+
die "install pnpm (preferred) or npm"
|
|
172
|
+
fi
|
|
173
|
+
rm -f "$npmrc"
|
|
174
|
+
if [[ "$_pub_ok" -ne 1 ]]; then
|
|
175
|
+
publish_failed_hint "$cmd"
|
|
176
|
+
exit 1
|
|
177
|
+
fi
|
|
178
|
+
if [[ "$cmd" == "release" && "$is_dry_run" -eq 0 ]]; then
|
|
179
|
+
release_commit_and_push_if_needed "$dist_tag" "$pkg_name" "$pkg_version"
|
|
180
|
+
fi
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
maint_promote() {
|
|
184
|
+
local tok npmrc name ver
|
|
185
|
+
tok="$(npm_publish_token)"
|
|
186
|
+
[[ -n "$tok" ]] || die "set NPM_TOKEN (or npm_token) in .env for promote"
|
|
187
|
+
name="$(package_field name)"
|
|
188
|
+
ver="$(package_field version)"
|
|
189
|
+
npmrc="$(runctl_npm_userconfig_from_token "$tok")" || exit 1
|
|
190
|
+
echo "maintain promote: npm dist-tag add ${name}@${ver} latest"
|
|
191
|
+
if NPM_CONFIG_USERCONFIG="$npmrc" npm dist-tag add "${name}@${ver}" latest; then
|
|
192
|
+
rm -f "$npmrc"
|
|
193
|
+
echo "maintain promote: ok"
|
|
194
|
+
else
|
|
195
|
+
rm -f "$npmrc"
|
|
196
|
+
die "dist-tag add failed (is ${name}@${ver} published?)"
|
|
197
|
+
fi
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
maint_npm_whoami() {
|
|
201
|
+
local _tok _rc _ec
|
|
202
|
+
command -v npm >/dev/null 2>&1 || die "npm not found"
|
|
203
|
+
_tok="$(npm_publish_token)"
|
|
204
|
+
[[ -n "$_tok" ]] || die "set NPM_TOKEN or NODE_AUTH_TOKEN or npm_token in .env"
|
|
205
|
+
_rc="$(runctl_npm_userconfig_from_token "$_tok")" || exit 1
|
|
206
|
+
NPM_CONFIG_USERCONFIG="$_rc" npm whoami
|
|
207
|
+
_ec=$?
|
|
208
|
+
rm -f "$_rc"
|
|
209
|
+
exit "$_ec"
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
maint_usage() {
|
|
213
|
+
cat <<'EOF'
|
|
214
|
+
Maintainer commands (scripts/maintain.sh):
|
|
215
|
+
|
|
216
|
+
release-check Preflight: doctor + pack --dry-run + optional npm whoami
|
|
217
|
+
publish [all] [tag] Publish package to npm
|
|
218
|
+
release [all] [tag] Publish, then commit+push if needed
|
|
219
|
+
promote npm dist-tag add <pkg>@<version> latest
|
|
220
|
+
npm-whoami npm whoami using token from .env
|
|
221
|
+
|
|
222
|
+
Also available via: ./run.sh <same command>
|
|
223
|
+
EOF
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
main() {
|
|
227
|
+
[[ $# -eq 0 ]] && set -- help
|
|
228
|
+
local cmd="${1:-}"
|
|
229
|
+
shift || true
|
|
230
|
+
case "$cmd" in
|
|
231
|
+
release-check)
|
|
232
|
+
maint_release_check
|
|
233
|
+
;;
|
|
234
|
+
publish | release)
|
|
235
|
+
maint_publish_or_release "$cmd" "$@"
|
|
236
|
+
;;
|
|
237
|
+
promote)
|
|
238
|
+
maint_promote
|
|
239
|
+
;;
|
|
240
|
+
npm-whoami)
|
|
241
|
+
maint_npm_whoami
|
|
242
|
+
;;
|
|
243
|
+
help | -h | --help)
|
|
244
|
+
maint_usage
|
|
245
|
+
;;
|
|
246
|
+
*)
|
|
247
|
+
echo "maintain: unknown command: $cmd" >&2
|
|
248
|
+
echo "" >&2
|
|
249
|
+
maint_usage >&2
|
|
250
|
+
exit 1
|
|
251
|
+
;;
|
|
252
|
+
esac
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
main "$@"
|