-
Notifications
You must be signed in to change notification settings - Fork 1
Pipeline Design inline
Now I have everything I need to produce the ADR.
Shipwright is a bash-based CLI tool with 100+ commands dispatched through a central router (scripts/sw) via a case statement. Each command is a standalone bash script (scripts/sw-<name>.sh) with a paired test file (scripts/sw-<name>-test.sh). The sw-hello.sh command (67 lines) is the established template for simple diagnostic/utility commands and is the direct model for this implementation.
The ping command serves a concrete operational purpose: it provides the simplest possible liveness probe. shipwright ping can be called by health-check scripts, CI pipelines, and orchestration layers to verify the CLI binary is installed and executable without invoking any external dependencies (GitHub API, tmux, database, etc.).
Constraints from the codebase:
- All scripts must be Bash 3.2 compatible — no
declare -A, no${var,,}, noreadarray -
set -euo pipefailis mandatory in every script - Output must be plain
echoto stdout; no ANSI color in the primary response payload (color helpers are for diagnostics, not protocol output) - The router dispatches via
exec— the case entry must be placed before the*)default atscripts/sw:608 - Test suites must be registered in the
npm testchain inpackage.json
Standalone script pattern, structurally identical to sw-hello.sh.
The command is implemented as scripts/sw-ping.sh with:
-
echo "pong"as the sole output on the happy path (no color wrappers — the response is a protocol token, not a user message) -
exit 0(implicit fromechosucceeding underset -euo pipefail) -
--help/-hand--version/-vflags (consistent with every other command; omitting them would make ping the only command that doesn't respond to--help) - Unknown-argument guard with
exit 1
The router entry is inserted at scripts/sw:607 (after hello), before *)):
ping) exec "$SCRIPT_DIR/sw-ping.sh" "$@" ;;
┌─────────────────────────────────────────────────────────────────┐
│ shipwright CLI │
│ │
│ User / CI / Health-check script │
│ │ │
│ ▼ │
│ ┌──────────────┐ exec ┌─────────────────────────────┐ │
│ │ scripts/sw │──────────▶│ scripts/sw-ping.sh │ │
│ │ (router) │ │ ┌───────────────────────┐ │ │
│ │ │ │ │ arg parser (case) │ │ │
│ │ case "ping" │ │ │ - "" → echo "pong" │ │ │
│ │ │ │ │ - --help/-h → help │ │ │
│ └──────────────┘ │ │ - --version/-v → ver │ │ │
│ │ │ - * → error + exit 1 │ │ │
│ ┌──────────────────────┐ │ └───────────────────────┘ │ │
│ │ scripts/sw-ping- │ │ │ │
│ │ test.sh │ │ Deps: lib/helpers.sh │ │
│ │ (6 test cases) │ │ (optional, fallback inline) │ │
│ └──────────────────────┘ └─────────────────────────────┘ │
│ ▲さんかく │
│ npm test chain (package.json) │
└─────────────────────────────────────────────────────────────────┘
Component responsibilities — one reason to change each:
| Component | Single Responsibility | Changes When |
|---|---|---|
scripts/sw |
Route command tokens to scripts | New commands are added / removed |
scripts/sw-ping.sh |
Print pong and exit |
Ping behavior changes |
scripts/sw-ping-test.sh |
Verify ping contract | Test cases added or ping contract changes |
package.json test script |
Enumerate all test suites | Test suite files added / removed |
# ── sw-ping.sh public interface ───────────────────────────────────────────── # Invocation forms: # sw-ping.sh → stdout: "pong\n" exit: 0 # sw-ping.sh --help → stdout: <help text> exit: 0 # sw-ping.sh -h → stdout: <help text> exit: 0 # sw-ping.sh --version → stdout: "<semver>\n" exit: 0 # sw-ping.sh -v → stdout: "<semver>\n" exit: 0 # sw-ping.sh <unknown> → stderr: error msg exit: 1 # Preconditions: # - bash >= 3.2 available at /usr/bin/env bash # - lib/helpers.sh may or may not be present (graceful fallback required) # Postconditions (happy path): # - Exactly the string "pong" followed by a newline written to stdout # - Nothing written to stderr # - Exit status 0 # Error contract: # - Exit status 1 on unrecognized argument # - Error message to stderr (not stdout) # - No side effects (no files written, no network calls, no env mutations) # ── Router contract (scripts/sw lines 605-613) ────────────────────────────── # Input: 1ドル == "ping" # Effect: exec replaces the router process with sw-ping.sh # All remaining argv ($@) forwarded to sw-ping.sh # No return: exec never returns on success
stdin ──(not read)──▶ sw-ping.sh
argv ──────────────▶ arg parser (case statement)
│
┌──────────┴──────────┐
│ "" (no args) │ --help/-h │ --version/-v │ *
▼ ▼ ▼ ▼
echo "pong" cat <<EOF echo $VERSION error() >&2
to stdout to stdout to stdout + show_help
exit 0 exit 0 exit 0 exit 1
There is no state read or written. No files. No environment variables queried beyond BASH_SOURCE (for SCRIPT_DIR). No network. This is a pure function: deterministic, zero side effects, zero external dependencies.
-
Inline in router (
scripts/sw) — Pros: zero new files. Cons: violates every established convention; untestable in isolation; sets a precedent that degrades the architecture for every future simple command; inconsistent withhello, which is structurally identical in complexity. Rejected. -
Shared "simple command" library — Pros: DRY across
helloandping. Cons: premature abstraction — two commands don't justify a shared library; adds indirection with no benefit; forces future commands into a rigid template. Rejected. -
printfinstead ofecho— Pros: more portable across edge-case/bin/shinterpreters. Cons: the entire codebase usesecho; this file is#!/usr/bin/env bashso portability is not a concern;printfwith no format string is unconventional. Rejected — useecho "pong".
Files to create:
| File | Lines (est.) | Purpose |
|---|---|---|
scripts/sw-ping.sh |
~67 | Command implementation (mirrors sw-hello.sh structure) |
scripts/sw-ping-test.sh |
~108 | 6-case test suite (mirrors sw-hello-test.sh structure) |
Files to modify:
| File | Location | Change |
|---|---|---|
scripts/sw |
After line 607 (after hello) block, before *)) |
Add ping) exec "$SCRIPT_DIR/sw-ping.sh" "$@" ;;
|
package.json |
"test" script string |
Append && bash scripts/sw-ping-test.sh to the test chain |
Dependencies: None. No new packages, libraries, or external tools.
Risk areas:
| Risk | Severity | Mitigation |
|---|---|---|
((PASS++)) under set -euo pipefail evaluates to exit 1 when result is 0 |
High | Use ((PASS++)) || true OR restructure as PASS=$((PASS + 1)) — matches sw-hello-test.sh exactly which already handles this |
| ERR trap in test file fires on intentional non-zero exits | High |
No ERR trap in sw-ping-test.sh — capture exit codes with cmd || local ec=$? pattern (as done in sw-hello-test.sh:92) |
Router *) default catches ping if case order is wrong |
Medium | Insert ping) block at lines 607-609, confirmed before *) at line 608 |
ANSI color codes in pong output break CI assertions |
Medium |
echo "pong" — no color wrappers; color helpers are for diagnostics only |
lib/helpers.sh not found in test environments |
Low | Inline fallback functions already established in sw-hello.sh:18-21; copy verbatim |
| Component | Errors it owns | Propagation |
|---|---|---|
scripts/sw-ping.sh — arg parser |
Unrecognized flags → error() to stderr + exit 1
|
Terminal; caller observes exit code |
scripts/sw-ping.sh — ERR trap |
Unexpected bash errors (e.g., echo fails on broken stdout) |
Prints ERROR: file:line status to stderr; exits non-zero |
scripts/sw — router |
ping token not in case → *) handler, exit 1
|
Terminal; would only occur if case entry is missing — covered by integration test |
scripts/sw-ping-test.sh |
Assertion failures increment $FAIL; final exit 1 if FAIL > 0
|
CI sees non-zero exit; npm test fails |
Error non-propagation by design: sw-ping.sh has no network calls, no file I/O, no subprocess spawning. The only possible failures are (a) broken stdout (environment issue, not command bug) or (b) unexpected argv (user error). Both terminate immediately with a non-zero exit. There is no retry logic, no partial state, and no cleanup required.
-
bash scripts/sw-ping.shoutputs exactlypong(one line, no trailing spaces, no ANSI codes) and exits 0 -
bash scripts/sw-ping.sh --helpoutputs text containingUSAGEand exits 0 -
bash scripts/sw-ping.sh -hsame as--help -
bash scripts/sw-ping.sh --versionoutputs a string matching^[0-9]+\.[0-9]+\.[0-9]+and exits 0 -
bash scripts/sw-ping.sh --invalidexits 1 and writes to stderr (not stdout) -
bash scripts/sw-ping-test.shreportsPASS: 6 FAIL: 0and exits 0 -
bash scripts/sw pingoutputspongand exits 0 (router integration) -
npm testexits 0 with all suites passing (regression gate) - No new files other than
sw-ping.shandsw-ping-test.share created -
VERSIONinsw-ping.shmatchespackage.json"version"field (currently3.2.4)