Implement preflight checks and step orchestration in scripts/local-install.mjs #91

Closed
opened 2026-08-27 23:59:07 +00:00 by david · 1 comment
Owner

Summary

Create scripts/local-install.mjs (Node ESM, no third-party deps) with the run-in-order step orchestration and the complete fail-fast Step 0 preflight: every check aborts with a non-zero exit and an actionable remediation message telling the user exactly what is missing and how to fix it.

Background

The install:local script updates and registers this repo's pi package from any git checkout. The full flow is: 0) preflight → 1) git pull → 2) npm install → 3) global pi registration with URL-entry dedupe → 4) /reload reminder. This issue creates the file and implements the orchestration plus Step 0; Steps 1–4 are implemented by later steps of this plan — leave them as temporary stubs that print a "not yet implemented" message and exit non-zero, so the script fails predictably rather than silently.

Depends on: #95 (Update package.json: add install:local script and register forgejo extension) — so npm run install:local is wired; the file can also be run directly via node scripts/local-install.mjs.

Implementation Details

  • Node ESM; use child_process.spawnSync and fs from Node builtins only.
  • Structure the script as small exported helper functions — one per check (e.g. checkPiCli, checkGitWorkTree, checkManagedClone, checkBranchMain, checkCleanTree, checkRipgrep, checkForgejoToken, checkFj) plus a main() that runs the steps in order and exits non-zero on the first failure. Helpers must be importable by the unit tests without running main() — guard the main() invocation (e.g. only call it when import.meta.url matches the entry-point URL).
  • Step 0 checks, in this order, each with the exact remediation message:
    1. pi CLI on PATH: spawnSync('pi', ['--version']) succeeds → else abort: "pi CLI not found — install pi first (see https://github.com/earendil-works/pi-coding-agent)".
    2. Cwd is inside a git work tree whose origin matches this repo: origin matches git.excelera.net/david/pi-extensions-and-skills, accepting https://, git:, and ssh:// URL forms → else abort: "not in a checkout of this repo — git clone https://git.excelera.net/david/pi-extensions-and-skills first".
    3. Not inside pi's managed clone dirs (~/.pi/agent/git/, .pi/git/) → else abort: "you're inside pi's managed clone — run from your own checkout".
    4. Current branch is main → else abort: "switch to main first: git checkout main".
    5. Working tree clean (git status --porcelain output empty) → else abort: "commit or stash your changes: git commit / git stash".
    6. rg binary on PATH (rg extension prereq) → else abort: "install ripgrep, e.g. sudo apt install ripgrep".
    7. FORGEJO_TOKEN in env (forgejo extension prereq) → missing is warn-only: print the full remediation (generate one in Forgejo → Settings → Applications, then export FORGEJO_TOKEN) on stderr with a [install:local] Warning: prefix and continue — the token lives in the per-machine environment, not the checkout.
    8. fj CLI on PATH and authenticated (pr-comments extension prereq) → else abort: "install/authenticate fj: fj auth login".
  • Steps 1–4: temporary stubs printing e.g. [install:local] Step <N> not implemented yet and exiting non-zero (replaced by later steps).

Acceptance Criteria

  • From a non-main branch of this repo, the script exits non-zero and prints "switch to main first: git checkout main".
  • From a dirty working tree on main, it exits non-zero and prints "commit or stash your changes: git commit / git stash".
  • From a directory that is not a checkout of this repo (e.g. /tmp), it exits non-zero and prints the clone remediation ("not in a checkout of this repo — git clone https://git.excelera.net/david/pi-extensions-and-skills first").
  • From inside a pi managed clone path, it exits non-zero and prints "you're inside pi's managed clone — run from your own checkout".
  • With rg missing from PATH, it exits non-zero and prints the ripgrep install message.
  • With FORGEJO_TOKEN unset, it prints a [install:local] Warning: line on stderr naming the fix, and does NOT abort for that reason.
  • With fj missing, it exits non-zero and prints the fj auth login message.
  • Every abort path exits non-zero; every message names both what is missing and how to fix it.
  • With all preflight checks passing, the script reaches the Step-1 stub and exits non-zero with a "not yet implemented" message (expected intermediate state).

Test Plan

  • Not a checkout: cd /tmp && node <repo>/scripts/local-install.mjs → clone remediation message.
  • Wrong branch: git checkout -b test-branch && node scripts/local-install.mjs → "switch to main first"; then git checkout main.
  • Dirty tree: touch dirty-file && node scripts/local-install.mjs → "commit or stash"; then rm dirty-file.
  • Missing tools: run with a stripped PATH (e.g. PATH=/usr/bin:/bin node scripts/local-install.mjs) → first failing tool check's message.
  • Warn-only path: env -u FORGEJO_TOKEN node scripts/local-install.mjs on clean main → warning on stderr, then reaches the Step-1 stub.
  • Assert the exit code is non-zero in each failing case.
## Summary Create `scripts/local-install.mjs` (Node ESM, no third-party deps) with the run-in-order step orchestration and the complete fail-fast Step 0 preflight: every check aborts with a non-zero exit and an actionable remediation message telling the user exactly what is missing and how to fix it. ## Background The `install:local` script updates and registers this repo's pi package from any git checkout. The full flow is: 0) preflight → 1) `git pull` → 2) `npm install` → 3) global pi registration with URL-entry dedupe → 4) `/reload` reminder. This issue creates the file and implements the orchestration plus Step 0; Steps 1–4 are implemented by later steps of this plan — leave them as temporary stubs that print a "not yet implemented" message and exit non-zero, so the script fails predictably rather than silently. **Depends on:** #95 (Update package.json: add install:local script and register forgejo extension) — so `npm run install:local` is wired; the file can also be run directly via `node scripts/local-install.mjs`. ## Implementation Details - Node ESM; use `child_process.spawnSync` and `fs` from Node builtins only. - Structure the script as small exported helper functions — one per check (e.g. `checkPiCli`, `checkGitWorkTree`, `checkManagedClone`, `checkBranchMain`, `checkCleanTree`, `checkRipgrep`, `checkForgejoToken`, `checkFj`) plus a `main()` that runs the steps in order and exits non-zero on the first failure. Helpers must be importable by the unit tests without running `main()` — guard the `main()` invocation (e.g. only call it when `import.meta.url` matches the entry-point URL). - Step 0 checks, in this order, each with the exact remediation message: 1. **pi CLI on PATH**: `spawnSync('pi', ['--version'])` succeeds → else abort: "pi CLI not found — install pi first (see https://github.com/earendil-works/pi-coding-agent)". 2. **Cwd is inside a git work tree whose origin matches this repo**: origin matches `git.excelera.net/david/pi-extensions-and-skills`, accepting `https://`, `git:`, and `ssh://` URL forms → else abort: "not in a checkout of this repo — `git clone https://git.excelera.net/david/pi-extensions-and-skills` first". 3. **Not inside pi's managed clone dirs** (`~/.pi/agent/git/`, `.pi/git/`) → else abort: "you're inside pi's managed clone — run from your own checkout". 4. **Current branch is `main`** → else abort: "switch to main first: `git checkout main`". 5. **Working tree clean** (`git status --porcelain` output empty) → else abort: "commit or stash your changes: `git commit` / `git stash`". 6. **`rg` binary on PATH** (rg extension prereq) → else abort: "install ripgrep, e.g. `sudo apt install ripgrep`". 7. **`FORGEJO_TOKEN` in env** (forgejo extension prereq) → missing is **warn-only**: print the full remediation (generate one in Forgejo → Settings → Applications, then `export FORGEJO_TOKEN`) on stderr with a `[install:local] Warning:` prefix and continue — the token lives in the per-machine environment, not the checkout. 8. **`fj` CLI on PATH and authenticated** (pr-comments extension prereq) → else abort: "install/authenticate fj: `fj auth login`". - Steps 1–4: temporary stubs printing e.g. `[install:local] Step <N> not implemented yet` and exiting non-zero (replaced by later steps). ## Acceptance Criteria - [ ] From a non-`main` branch of this repo, the script exits non-zero and prints "switch to main first: `git checkout main`". - [ ] From a dirty working tree on `main`, it exits non-zero and prints "commit or stash your changes: `git commit` / `git stash`". - [ ] From a directory that is not a checkout of this repo (e.g. `/tmp`), it exits non-zero and prints the clone remediation ("not in a checkout of this repo — `git clone https://git.excelera.net/david/pi-extensions-and-skills` first"). - [ ] From inside a pi managed clone path, it exits non-zero and prints "you're inside pi's managed clone — run from your own checkout". - [ ] With `rg` missing from PATH, it exits non-zero and prints the ripgrep install message. - [ ] With `FORGEJO_TOKEN` unset, it prints a `[install:local] Warning:` line on stderr naming the fix, and does NOT abort for that reason. - [ ] With `fj` missing, it exits non-zero and prints the `fj auth login` message. - [ ] Every abort path exits non-zero; every message names both what is missing and how to fix it. - [ ] With all preflight checks passing, the script reaches the Step-1 stub and exits non-zero with a "not yet implemented" message (expected intermediate state). ## Test Plan - Not a checkout: `cd /tmp && node <repo>/scripts/local-install.mjs` → clone remediation message. - Wrong branch: `git checkout -b test-branch && node scripts/local-install.mjs` → "switch to main first"; then `git checkout main`. - Dirty tree: `touch dirty-file && node scripts/local-install.mjs` → "commit or stash"; then `rm dirty-file`. - Missing tools: run with a stripped PATH (e.g. `PATH=/usr/bin:/bin node scripts/local-install.mjs`) → first failing tool check's message. - Warn-only path: `env -u FORGEJO_TOKEN node scripts/local-install.mjs` on clean `main` → warning on stderr, then reaches the Step-1 stub. - Assert the exit code is non-zero in each failing case.
Author
Owner

Work for this issue is complete — closing.

  • scripts/local-install.mjs exists (Node ESM, builtins only) with the fail-fast Step 0 preflight and in-order step orchestration: main() runs preflight → git pull --ff-onlynpm install → pi registration w/ URL dedupe → /reload reminder, aborting non-zero on first failure. Helpers are importable without running main() (entry-point guard).
  • Steps 1–4 are fully implemented rather than stubs, as the plan intended ("replaced by later steps"): #88 (git pull + npm install) and #90 (pi registration/dedupe/reminder) landed.
  • All 8 preflight checks present; node --test scripts/local-install.test.mjs passes 99/99. Live-checked: run from a non-checkout dir → clone remediation + exit 1; dirty tree on main → commit/stash remediation + exit 1.
  • Note: the "abort on missing rg/fj" acceptance criteria were deliberately superseded by #168 (skip/warn instead of hard-fail), documented in the file header and asserted by the current tests.
Work for this issue is complete — closing. - `scripts/local-install.mjs` exists (Node ESM, builtins only) with the fail-fast Step 0 preflight and in-order step orchestration: `main()` runs preflight → `git pull --ff-only` → `npm install` → pi registration w/ URL dedupe → `/reload` reminder, aborting non-zero on first failure. Helpers are importable without running `main()` (entry-point guard). - Steps 1–4 are fully implemented rather than stubs, as the plan intended ("replaced by later steps"): #88 (git pull + npm install) and #90 (pi registration/dedupe/reminder) landed. - All 8 preflight checks present; `node --test scripts/local-install.test.mjs` passes 99/99. Live-checked: run from a non-checkout dir → clone remediation + exit 1; dirty tree on `main` → commit/stash remediation + exit 1. - Note: the "abort on missing `rg`/`fj`" acceptance criteria were deliberately superseded by #168 (skip/warn instead of hard-fail), documented in the file header and asserted by the current tests.
david closed this issue 2026-09-18 05:42:12 +00:00
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
david/pi-extensions-and-skills#91
No description provided.