-
Notifications
You must be signed in to change notification settings - Fork 1
Pipeline Design inline
Shipwright exposes 100+ CLI commands via a central router (scripts/sw) that dispatches to standalone bash scripts using exec. The project needs a ping command to serve as a reachability/liveness probe — callers invoke shipwright ping, expect exactly pong on stdout, and rely on exit code 0 to confirm the CLI is installed and on PATH.
Constraints from the codebase:
- All shell scripts must be Bash 3.2 compatible (
no declare -A,no readarray,no ${var,,}) - All scripts use
set -euo pipefailwith an ERR trap -
VERSIONmust matchpackage.json(currently3.2.4) - Output to stdout must be clean and undecorated — no ANSI codes, no prefix characters on the primary response line
- Every new command requires a parallel test file registered in
package.json's"test"script - Router (
scripts/sw) dispatches via acasestatement usingexec— inline command logic in the router is explicitly against project convention
┌─────────────────────────────────────────────────────────┐
│ CLI Layer │
│ │
│ scripts/sw ─── case "ping)" ──exec──► sw-ping.sh │
│ (router) (command) │
└─────────────────────────────────────────────────────────┘
│ │
│ ┌──────▼──────────┐
│ │ stdout: "pong" │
│ │ exit: 0 │
│ └─────────────────┘
│
┌───────▼────────────────────────────────────────────────┐
│ Test Layer │
│ │
│ sw-ping-test.sh ──invokes──► sw-ping.sh directly │
│ (6 test cases) (bypasses router) │
│ │ │
│ └── registered in package.json "test" script │
└─────────────────────────────────────────────────────────┘
Standalone script pattern — scripts/sw-ping.sh is a self-contained bash script registered in the router with a two-line case entry. It follows the identical structure as scripts/sw-hello.sh (the most recent precedent), which is the canonical model for simple commands.
Output contract: echo "pong" with no ANSI wrapping, no info()/success() helper decorators, followed by explicit exit 0. The helpers are sourced as fallbacks (required by the template) but never called on the happy path — the response is a bare printable string, not a status message.
Option parsing: A case "${1:-}" block handles --help|-h, --version|-v, "" (happy path), and * (unknown → exit 1). This matches sw-hello.sh:44-65 exactly.
# sw-ping.sh public interface
#
# Invocation:
# sw-ping.sh [OPTION]
#
# Arguments:
# (none) → prints "pong" to stdout, exits 0
# --help | -h → prints USAGE block to stdout, exits 0
# --version|-v → prints semver string (e.g. "3.2.4") to stdout, exits 0
# <unknown> → prints error to stderr, prints USAGE to stdout, exits 1
#
# Stdout contract (no-arg case):
# Exactly: "pong\n" (one line, no leading/trailing whitespace, no ANSI)
#
# Exit codes:
# 0 → success (all valid invocations)
# 1 → unknown option passed
#
# Error contract:
# Errors written to stderr via error() helper
# ERR trap fires on unexpected non-zero exits (bash error, not logic error)# Router registration contract (scripts/sw, line 607)
# ping)
# exec "$SCRIPT_DIR/sw-ping.sh" "$@"
# ;;
#
# Precondition: sw-ping.sh is executable and on the filesystem
# Postcondition: all args forwarded verbatim; process replaced via exec-
Inline in router (
scripts/swcase body) — Pros: zero new files, noexecoverhead. Cons: violates the single-responsibility boundary that keeps the router as pure dispatch; not independently testable; creates precedent for logic in the router. Rejected as architectural debt — the existing 100+ commands all follow the standalone pattern. -
Shared utility function in
lib/helpers.sh— Pros: reusable if other commands need a liveness probe. Cons:helpers.shis for output/event utilities, not command implementations; premature abstraction for a one-liner. Rejected — no other consumer exists. -
printf "pong\n"instead ofecho "pong"— Pros: portable across shells. Cons:echois used in all existing commands includingsw-hello.sh:56; both are POSIX;echois idiomatic in this codebase. Not chosen but not a risk — either works identically.
User invokes: shipwright ping
│
▼
scripts/sw main()
└── parse $1 → "ping"
└── case "ping)" → exec scripts/sw-ping.sh "$@"
│
▼
sw-ping.sh main()
case "${1:-}" → "" (no args)
echo "pong" ──────────────► stdout fd 1
exit 0 ──────────────► $? = 0
User invokes: shipwright ping --invalid
│
▼
... (same router path) ...
sw-ping.sh main()
case "${1:-}" → "--invalid"
error "Unknown option: --invalid" ──► stderr fd 2
show_help ──► stdout fd 1
exit 1 ──────────────────────► $? = 1
| Layer | Errors Handled | Propagation |
|---|---|---|
sw-ping.sh ERR trap |
Unexpected bash errors (command not found, arithmetic error) | Prints ERROR: $BASH_SOURCE:$LINENO to stderr, exits non-zero |
sw-ping.sh option parse |
Unknown CLI flags (* case) |
Writes to stderr via error(), exits 1 |
scripts/sw router |
Unknown command name |
error "Unknown command", exits non-zero (existing behavior, not modified) |
| Test runner | Script exit code ≠ expected |
assert_exit_code increments FAIL counter; final [[ $FAIL -eq 0 ]] propagates to npm test exit code |
Explicit non-error: set -euo pipefail means any unguarded non-zero exit within sw-ping.sh will fire the ERR trap. The only non-zero intentional exit is the exit 1 in the *) branch — all other code paths exit 0.
-
Files to create:
-
scripts/sw-ping.sh— command implementation (mirrorssw-hello.shstructure) -
scripts/sw-ping-test.sh— 6-case test suite (mirrorssw-hello-test.shstructure)
-
-
Files to modify:
-
scripts/sw— insert 2 lines at line 607 (afterhello)block, before*)catch-all) -
package.json— insertbash scripts/sw-ping-test.sh &&aftersw-hello-test.shin the"test"script (line 39)
-
-
Dependencies: None. No new packages, no new libraries.
-
Risk areas:
-
Router insertion point —
scripts/swis 617 lines. Thehello)entry is at line 605-607. The*)catch-all immediately follows. Inserting before*)is the critical edit; a misplaced entry would leavepingunreachable or break the router's fallthrough. -
package.json test chain — The
"test"script is a single long&&-chained string on line 39. Addingsw-ping-test.shaftersw-hello-test.shmust maintain valid shell syntax; a missing&&or trailing&&will break all subsequent tests. -
Exit code capture in test — The
test_ping_exit_codecase runs"$SCRIPT_DIR/sw-ping.sh" > /dev/null 2>&1and checks$?. Underset -euo pipefail, a non-zero exit here would abort the test script before the assertion fires. The|| trueguard is not needed because exit 0 is the contract; but theinvalid optiontest uses the|| local exit_code=$?pattern (copied fromsw-hello-test.sh:92) which is correct for capturing non-zero exits without aborting.
-
Router insertion point —
-
bash scripts/sw-ping.shoutputs exactlypong(one line, no trailing spaces, no ANSI) -
bash scripts/sw-ping.shexits with code 0 -
bash scripts/sw-ping.sh --helpoutput contains the stringUSAGE -
bash scripts/sw-ping.sh -houtput contains the stringUSAGE -
bash scripts/sw-ping.sh --versionoutput matches^[0-9]+\.[0-9]+\.[0-9]+ -
bash scripts/sw-ping.sh --invalidexits with code 1 -
bash scripts/sw pingoutputspong(router dispatch works end-to-end) -
bash scripts/sw-ping-test.shexits 0 withPASS: 6 FAIL: 0 -
package.json"test"script includessw-ping-test.shas a&&-chained entry -
scripts/sw-ping.shcontainsset -euo pipefail, ERR trap, andVERSION="3.2.4" - No Bash 4.x features present (no
declare -A, no${var,,}, noreadarray)