-
Notifications
You must be signed in to change notification settings - Fork 1
Pipeline Design inline
Shipwright is a CLI tool with 100+ subcommands, each implemented as a standalone bash script (scripts/sw-<name>.sh) and dispatched via a central router (scripts/sw). The codebase enforces strict conventions: Bash 3.2 compatibility, set -euo pipefail, ERR trap, VERSION constant, fallback output helpers, and a parallel test file per command. Every command is registered in two places: the case statement in scripts/sw and the "test" script in package.json.
The goal is to add a shipwright ping command — a minimal connectivity check that prints pong to stdout and exits 0. The constraint is zero architectural novelty: this must be indistinguishable in structure from sw-hello.sh, which already serves as the canonical template for new commands.
Implement ping as a standalone script (scripts/sw-ping.sh) following the sw-hello.sh pattern exactly:
-
echo "pong"as the default action (no args) -
--help/-h→ heredoc help text, exit 0 -
--version/-v→$VERSION, exit 0 - Unknown args →
error()+ help, exit 1 - Router entry:
ping)case inserted inscripts/swafter thehello)block (lines 605–607), before the*)wildcard - Test suite:
scripts/sw-ping-test.shwith 6 tests mirroringsw-hello-test.sh - Package.json:
bash scripts/sw-ping-test.sh &&inserted in the"test"script immediately aftersw-hello-test.sh
┌─────────────────────────────────────────────────────────────────┐
│ User │
│ shipwright ping [args] │
└──────────────────┬──────────────────────────────────────────────┘
│ exec
▼
┌─────────────────────────────────────────────────────────────────┐
│ CLI Router (scripts/sw) │
│ case "$cmd" in │
│ hello) exec sw-hello.sh ← existing │
│ ping) exec sw-ping.sh ← NEW │
│ *) error + exit 1 │
│ esac │
└──────────────────┬──────────────────────────────────────────────┘
│ exec (replaces router process)
▼
┌─────────────────────────────────────────────────────────────────┐
│ sw-ping.sh │
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ show_help()│ │ main() │ │ lib/helpers.sh │ │
│ │ heredoc │ │ case $1 in │ │ (sourced if present)│ │
│ │ USAGE block│ │ "" → pong │ │ info/success/warn/ │ │
│ └─────────────┘ │ -h → help │ │ error fallbacks │ │
│ │ -v → ver │ └──────────────────────┘ │
│ │ * → err │ │
│ └──────────────┘ │
└─────────────────────────────────────────────────────────────────┘
▲ direct invocation (test isolation)
│
┌─────────────────────────────────────────────────────────────────┐
│ sw-ping-test.sh │
│ 6 tests: output, exit 0, --help, -h, --version, invalid exit 1│
│ No mocks needed — script is pure stdout/exit code │
└─────────────────────────────────────────────────────────────────┘
▲
┌─────────────────────────────────────────────────────────────────┐
│ package.json "test" script │
│ ...&& bash scripts/sw-hello-test.sh && │
│ bash scripts/sw-ping-test.sh && ← NEW │
│ bash scripts/sw-hygiene-test.sh && ... │
└─────────────────────────────────────────────────────────────────┘
# sw-ping.sh — public interface
#
# Inputs: $1 (optional) — one of: "", "--help", "-h", "--version", "-v", <unknown>
# Outputs: stdout — one of: "pong\n", help text, VERSION string, error message
# Exit: 0 on success (no args, --help, --version)
# 1 on unknown arg
#
# Error contract:
# - ERR trap fires on any unexpected non-zero status, prints BASH_SOURCE:LINENO to stderr
# - Unknown arg: error() writes to stderr, show_help() to stdout, exits 1
# - No network calls, no file writes — pure stdout/exit behavior
main() # entry point, called with "$@"
show_help() # prints USAGE block to stdout, no args, no return value
# scripts/sw router contract (ping case):
# Input: cmd="ping", remaining "$@" forwarded
# Effect: exec replaces router process with sw-ping.sh — no return
# Error: if sw-ping.sh missing, exec fails → bash error to stderr
# sw-ping-test.sh:
# assert_equals expected actual description → increments PASS or FAIL, prints result
# assert_exit_code expected actual description → same
# Exit: 0 if FAIL==0, 1 otherwiseshipwright ping
│
▼ scripts/sw parses $1 as cmd="ping"
│ exec scripts/sw-ping.sh (remaining args forwarded)
│
▼ sw-ping.sh main() evaluates ${1:-}
case "":
echo "pong" → stdout
exit 0
case "--help"|"-h":
show_help() → stdout (heredoc)
exit 0
case "--version"|"-v":
echo "$VERSION" → stdout
exit 0
case *:
error "Unknown option: $1" → stderr
show_help() → stdout
exit 1
| Component | Errors handled | Propagation |
|---|---|---|
sw-ping.sh main() |
Unknown args → stderr + exit 1 | Terminal — no caller |
sw-ping.sh ERR trap |
Unexpected non-zero (set -euo pipefail) | Prints $BASH_SOURCE:$LINENO to stderr, script exits non-zero |
scripts/sw router |
exec failure (missing script) |
Bash emits error to stderr, exits non-zero |
sw-ping-test.sh |
Test assertion failures | Accumulated in $FAIL, non-zero exit at end |
No error propagates silently — every failure path writes to stderr and exits non-zero.
-
Inline in
scripts/swrouter — Pros: zero new files, single-line change. Cons: untestable in isolation (no script path to invoke directly), breaks the architectural contract that every command is a separate file, no--help/--versionsurface. Rejected: architectural debt with no benefit. -
Shared library function — Pros: reusable pattern for trivial commands. Cons: premature abstraction — one command doesn't justify a new library.
sw-hello.shalready exists and isn't extracted to a library either. Rejected: over-engineering.
Files to create:
-
scripts/sw-ping.sh— command implementation (≈67 lines, mirroringsw-hello.sh) -
scripts/sw-ping-test.sh— test suite (≈108 lines, mirroringsw-hello-test.sh)
Files to modify:
-
scripts/sw— insertping)case afterhello)at line 607, before*)wildcard -
package.json— insertbash scripts/sw-ping-test.sh &&in"test"string aftersw-hello-test.sh
Dependencies: None. No new packages, no new libraries.
Risk areas:
| Risk | Mitigation |
|---|---|
Router wildcard *) catches ping if case is misplaced |
Insert strictly after hello) block, before *) — verified by bash scripts/sw ping smoke test |
package.json test string is a single long line — edit must be surgical |
Use Edit tool with exact old_string/new_string containing sw-hello-test.sh as anchor |
CLAUDE.md AUTO:core-scripts and AUTO:test-suites sections become stale |
Acceptable — pipeline patrol handles doc sync; not a build blocker |
echo "pong" vs printf "pong\n" — output capture with $() strips trailing newline |
Both produce identical result when captured; echo is consistent with sw-hello.sh
|
-
bash scripts/sw-ping.shoutputs exactlypong(one line, no extra whitespace) to stdout and exits 0 -
bash scripts/sw pingoutputs exactlypongand exits 0 (router dispatch works) -
bash scripts/sw-ping.sh --helpand-hboth output text containingUSAGEand exit 0 -
bash scripts/sw-ping.sh --versionoutputs a semver string matching^[0-9]+\.[0-9]+\.[0-9]+and exits 0 -
bash scripts/sw-ping.sh --invalidexits 1 and writes error to stderr -
bash scripts/sw-ping-test.shreportsPASS: 6 FAIL: 0 -
bash scripts/sw-hello-test.shstill reportsPASS: 6 FAIL: 0(no regression) -
sw-ping-test.shappears inpackage.json"test"script andnpm testdoes not fail on it