-
Notifications
You must be signed in to change notification settings - Fork 1
Pipeline Design inline
Shipwright needed a lightweight, zero-dependency health-check primitive — a command that a human or machine can run to confirm the CLI is installed, on PATH, and executable without triggering any external API calls, GitHub auth, or tmux sessions. The existing 100+ commands all interact with external systems; none served as a pure local smoke-test.
Codebase constraints that shaped the design:
- All commands are standalone Bash scripts dispatched by
scripts/swvia acasestatement usingexec - Scripts must be Bash 3.2 compatible (macOS system shell) — no
declare -A,readarray,${var,,}, etc. - Every script must carry
set -euo pipefail, an ERR trap, and aVERSIONconstant in sync withpackage.json - The test harness (
npm test) discovers test suites registered inpackage.json; each command has a parallelsw-<name>-test.shfile - Output helpers (
info,success,warn,error) are sourced fromlib/helpers.shwith inline fallbacks for test environments whereSCRIPT_DIRis overridden
Implement shipwright ping as a standalone Bash script (scripts/sw-ping.sh) following the established Command Script pattern, registered in the router at scripts/sw lines 605–606.
The command's single responsibility is: receive zero arguments → print pong\n to stdout → exit 0. All flag parsing (--help, -h, --version, -v, unknown flags) is handled inside main() via a single case on ${1:-}.
-
Inline the response in the router (
scripts/sw) — Pros: Zero new files. Cons: The router becomes a mixed-concern object (dispatch + implementation); untestable in isolation; inconsistent with every other command; modifying the router for each new command violates open/closed. Rejected. -
Shared library function (e.g.,
lib/helpers.sh: ping_cmd()) — Pros: Reusable if multiple callers need pong-like output. Cons: No second caller exists or is anticipated; premature abstraction adds indirection; the library is for cross-cutting concerns (colors, output, events), not command implementations. Rejected. -
Alias or symlink to
sw-hello.shwith env var control — Pros: Near-zero code. Cons: The two commands have different semantics (hello = greeting, ping = health probe); conflating them produces an unreadable interface; symlink resolution underset -euo pipefailhas historically caused issues in this codebase. Rejected.
Files created:
| Path | Purpose |
|---|---|
scripts/sw-ping.sh |
Command implementation (68 lines) |
scripts/sw-ping-test.sh |
Test suite — 6 cases, self-contained (109 lines) |
Files modified:
| Path | Change |
|---|---|
scripts/sw |
Lines 605–606: ping) case dispatching to sw-ping.sh
|
package.json |
test script: bash scripts/sw-ping-test.sh appended to suite |
Dependencies: None. No new packages, no new libraries, no external calls.
Risk areas:
-
set -euo pipefail+ the ERR trap can produce spuriousERROR:output in test environments when the test intentionally provokes exit code 1 (invalid option). The test at line 92–93 ofsw-ping-test.shdefensively captures the exit code with|| local exit_code=$?rather than relying on$?after a command that might exit non-zero inside a function. Consistent with the failure patterns documented inmemory/failures.json. - The
((PASS++))/((FAIL++))arithmetic in the test file will causeset -eto abort if the counter starts at 0 — mitigated by initializing both counters to0before the first increment and by the fact that arithmetic((expr))returns exit code 1 when the result is 0. This is a known Bash pitfall; the existing test files in the repo share the same pattern.
┌─────────────────────────────────────────────────────┐
│ User / CI / Health-check script │
└──────────────────────────┬──────────────────────────┘
│ shipwright ping
▼
┌─────────────────────────────────────────────────────┐
│ scripts/sw (Command Router) │
│ ───────────────────────────── │
│ case "$cmd" in │
│ ping) exec sw-ping.sh "$@" ;; │
│ esac │
└──────────────────────────┬──────────────────────────┘
│ exec (replaces process)
▼
┌─────────────────────────────────────────────────────┐
│ scripts/sw-ping.sh (Ping Command) │
│ ───────────────────────────── │
│ main() │
│ case ${1:-} │
│ "" → echo "pong"; exit 0 │
│ --help/-h → show_help; exit 0 │
│ --ver/-v → echo $VERSION; exit 0 │
│ * → error + show_help; exit 1 │
└──────────────────────────┬──────────────────────────┘
│ stdout: "pong"
▼
Caller process
Test boundary (separate process):
┌─────────────────────────────────────────────────────┐
│ scripts/sw-ping-test.sh (Test Suite) │
│ ───────────────────────────── │
│ Invokes sw-ping.sh as subprocess for each case │
│ assert_equals / assert_exit_code accumulate │
│ PASS/FAIL counts, exits 1 if FAIL > 0 │
└─────────────────────────────────────────────────────┘
// Conceptual interface (implemented in Bash)
/**
* @param args Process argv after "ping" is stripped by router
* @stdout "pong\n" when args is empty
* @stdout help text when args[0] is --help or -h
* @stdout VERSION string when args[0] is --version or -v
* @stderr error message when args[0] is unknown flag
* @exit 0 on success (pong, help, version)
* @exit 1 on unknown flag
*/
function ping(args: string[]): never;
// Router contract (scripts/sw)
// Input: argv[0] === "ping"
// Action: exec(sw-ping.sh, argv.slice(1))
// Effect: current process is replaced; no return
// Test contract (sw-ping-test.sh)
// Precondition: sw-ping.sh is executable and on SCRIPT_DIR path
// Postcondition: exit 0 iff FAIL === 0
// Side effects: writes PASS/FAIL counts to stdoutRequest: shipwright ping [args]
│
▼
Router: parse $1 → match "ping" → exec sw-ping.sh [args]
│ (process replaced, no fork overhead)
▼
Command: parse ${1:-}
├── empty → write "pong\n" to fd1 → _exit(0)
├── help → write help heredoc to fd1 → _exit(0)
├── version→ write VERSION to fd1 → _exit(0)
└── other → write error to fd2, help to fd1 → _exit(1)
│
▼
Response: caller receives stdout stream + exit code
(no JSON, no structured output, no side effects)
| Layer | Error type | Handling |
|---|---|---|
Router (scripts/sw) |
Unknown top-level subcommand | Router's own *) case — prints usage, exits 1. Never reaches ping. |
Ping command (sw-ping.sh) |
Unknown flag passed to ping |
error() to stderr + show_help to stdout + exit 1
|
| Ping command | ERR trap fires (unexpected) | ERR trap prints ERROR: <file>:<line> exited with status <N> to stderr; process exits non-zero |
| Test suite | Subprocess exits non-zero unexpectedly |
assert_exit_code captures it; FAIL incremented; suite continues; exits 1 at end |
| Test suite | ERR trap fires inside test function | Would abort the test process — mitigated by the ` |
-
shipwright pingprints exactlypong(no trailing whitespace, no ANSI codes) and exits 0 -
shipwright ping --helpandshipwright ping -hprint aUSAGEblock and exit 0 -
shipwright ping --versionandshipwright ping -vprint a semver string matching^[0-9]+\.[0-9]+\.[0-9]+and exit 0 -
shipwright ping --invalidexits 1 and writes an error to stderr -
scripts/sw-ping-test.shruns to completion with PASS: 6 / FAIL: 0 -
npm testincludessw-ping-test.shand the suite passes end-to-end - No external calls, no tmux, no GitHub API, no Claude CLI invoked at any point
- Script is Bash 3.2 compatible (verified: no bash 4+ constructs used)
-
VERSIONconstant insw-ping.shmatchespackage.json→"version"