Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Pipeline Design inline

Seth Ford edited this page Mar 2, 2026 · 8 revisions

Now I have everything I need. Here is the ADR:


Design: Add a shipwright ping command that prints pong to stdout and exits 0

Context

Shipwright exposes 100+ subcommands through a central CLI router (scripts/sw). Each command is a standalone bash script (scripts/sw-<name>.sh) dispatched via a case statement using exec. A hello command already exists as the canonical reference implementation of this pattern.

The ping command is a minimal health-check primitive: shipwright ping → prints pong to stdout, exits 0. It has no external dependencies, no side effects, and no state. The only constraints are:

  1. Must conform to the established Standalone Script Pattern (identical structure to sw-hello.sh)
  2. Must be Bash 3.2 compatible (no associative arrays, no ${var,,}, no readarray)
  3. Must use set -euo pipefail + ERR trap
  4. Must carry VERSION="3.2.4" to stay consistent with the rest of the codebase
  5. Output must be exactly pong — no color prefix, no ANSI escape codes — because tests do an exact-string comparison

Decision

Standalone Script Pattern — create scripts/sw-ping.sh as a structural clone of scripts/sw-hello.sh with a single behavioral change: the no-argument case prints echo "pong" instead of echo "hello world".

The router entry (ping) case in scripts/sw) is inserted before hello) at line 605. The *)catch-all at line 608 is the unreachable-command guard; any new entry must be placed before it.

The test file (scripts/sw-ping-test.sh) is a structural clone of scripts/sw-hello-test.sh — inline assert_equals/assert_exit_code helpers, 6 test functions, PASS/FAIL counters, exits 1 on any failure.

Note on package.json insertion point: The plan states insertion between sw-pipeline-vitals-test.sh and sw-pm-test.sh. This is incorrect. Alphabetically, ping (p-i-n) sorts before pipeline (p-i-p) because n < p. The correct insertion is between sw-patrol-meta-test.sh and sw-pipeline-composer-test.sh.


Alternatives Considered

  1. Inline the ping handler in the router (scripts/sw) — Pros: zero new files, no exec overhead. Cons: violates Single Responsibility — the router is a dispatcher, not an implementer; breaks the test boundary (no independent bash scripts/sw-ping-test.sh path); against established architecture for all 100+ commands. Rejected.

  2. Add ping as a shared library function — Pros: reusable if multiple scripts need a health check. Cons: premature abstraction for a one-line output; introduces a dependency where none exists; over-engineered for the requirement. Rejected.


Implementation Plan

Files to create

File Lines Purpose
scripts/sw-ping.sh ~67 Command implementation — mirrors sw-hello.sh exactly, echo "pong" in no-arg case
scripts/sw-ping-test.sh ~108 6-test suite — mirrors sw-hello-test.sh exactly, asserts output pong

Files to modify

File Change
scripts/sw Insert ping) exec "$SCRIPT_DIR/sw-ping.sh" "$@" ;; before hello) at line 605
package.json Insert bash scripts/sw-ping-test.sh && between sw-patrol-meta-test.sh and sw-pipeline-composer-test.sh (alphabetical order)

Dependencies

None. No new packages, no new shared libraries.

Risk areas

Risk Affected location Mitigation
Router entry placed after *) scripts/sw line 608 Insert at line 605 (before hello)), verified with grep -n 'hello|^\s*\*)' scripts/sw
pong output has trailing newline stripped test assert_equals "pong" "$output" echo "pong" adds one \n; command substitution $() strips it — both sides are stripped equally, assertion holds
ERR trap fires during intentional exit 1 test_ping_invalid_option Use `
VERSION drift sw-ping.sh header Hardcode VERSION="3.2.4" — same value as sw-hello.sh:8 and package.json
chmod not applied scripts/sw-ping.sh chmod +x immediately after creation, before any test run

Component Diagram

 ┌─────────────────────────────────────┐
 │ scripts/sw │
 │ (CLI Router) │
 │ │
 │ case "$cmd" in │
 │ ping) exec sw-ping.sh "$@" ;; │◄── NEW (line 605, before hello)
 │ hello) exec sw-hello.sh "$@" ;; │
 │ *) error "Unknown cmd" ;; │
 │ esac │
 └──────────────┬──────────────────────┘
 │ exec (replaces process)
 ▼
 ┌─────────────────────────────────────┐
 │ scripts/sw-ping.sh │
 │ (Command Handler) │
 │ │
 │ main() │
 │ case "${1:-}" in │
 │ --help|-h) show_help; exit 0 │
 │ --version) echo $VERSION; │
 │ exit 0 │
 │ "") echo "pong"; │◄── CORE BEHAVIOR
 │ exit 0 │
 │ *) error; exit 1 │
 │ esac │
 └─────────────────────────────────────┘
 さんかく
 │ bash invocation
 ┌─────────────────────────────────────┐
 │ scripts/sw-ping-test.sh │
 │ (Test Harness) │
 │ │
 │ assert_equals "pong" "$output" │
 │ assert_exit_code 0 $? │
 │ [[ output =~ "USAGE" ]] │
 │ [[ output =~ semver ]] │
 │ || local exit_code=$? (exit 1) │
 └─────────────────────────────────────┘

Interface Contracts

# sw-ping.sh — public interface (all args optional)
#
# Input: 1ドル — optional flag: --help | -h | --version | -v | (empty)
# Output: stdout — "pong" (no-arg), help text (--help/-h), semver (--version/-v)
# Stderr: error message on unknown option
# Exit: 0 — success (pong, help, version)
# 1 — unknown option
sw_ping() {
 # "" → echo "pong" to stdout, exit 0
 # --help/-h → echo help text to stdout, exit 0
 # --version → echo "$VERSION" to stdout, exit 0
 # * → echo error to stderr, echo help, exit 1
}
# Router dispatch (scripts/sw)
# Precondition: $cmd == "ping"
# Postcondition: process replaced by sw-ping.sh via exec (no return)
ping) exec "$SCRIPT_DIR/sw-ping.sh" "$@" ;;

Data Flow

User invokes: shipwright ping
 │
 ▼
scripts/sw main()
 → parses 1ドル as $cmd
 → case "ping" → exec sw-ping.sh (stdin/stdout/stderr inherited)
 │
 ▼
sw-ping.sh main()
 → case "${1:-}" — no arg: ""
 → echo "pong" ← single call, no color prefix
 → exit 0
 │
 ▼
stdout: "pong\n"
exit code: 0

For the test path:

bash scripts/sw-ping-test.sh
 → output=$("$SCRIPT_DIR/sw-ping.sh") ← subshell, stdout captured
 → assert_equals "pong" "$output" ← $() strips trailing newline
 → PASS++

Error Boundaries

Component Errors it handles Propagation
sw-ping.sh Unknown CLI flag → error() to stderr + exit 1 Caller receives exit code 1; no trap interference
sw-ping.sh ERR trap Unexpected bash errors (e.g., broken pipe) Prints ERROR: $BASH_SOURCE:$LINENO to stderr; exits non-zero
scripts/sw router ping command not found on PATH Would fall to *) — prevented by correct case placement
sw-ping-test.sh negative test Intentional exit 1 triggers ERR trap in test Mitigated by `
package.json test runner Any suite exits non-zero && chain halts; later suites do not run

Validation Criteria

  • bash scripts/sw-ping.sh prints exactly pong (no color codes, no prefix characters)
  • bash scripts/sw-ping.sh exits with code 0
  • bash scripts/sw-ping.sh --help contains the string USAGE
  • bash scripts/sw-ping.sh -h contains the string USAGE
  • bash scripts/sw-ping.sh --version matches ^[0-9]+\.[0-9]+\.[0-9]+
  • bash scripts/sw-ping.sh --invalid exits with code 1
  • bash scripts/sw-ping-test.sh reports PASS: 6 FAIL: 0
  • bash scripts/sw ping prints pong (router integration verified)
  • npm test passes with sw-ping-test.sh included and no regressions in existing suites
  • sw-ping.sh is executable (ls -l scripts/sw-ping.sh | grep ^-rwx)
  • scripts/sw ping) case appears before hello) and before *)

Clone this wiki locally

AltStyle によって変換されたページ (->オリジナル) /