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

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

Context

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.


Decision

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 in scripts/sw after the hello) block (lines 605–607), before the *) wildcard
  • Test suite: scripts/sw-ping-test.sh with 6 tests mirroring sw-hello-test.sh
  • Package.json: bash scripts/sw-ping-test.sh && inserted in the "test" script immediately after sw-hello-test.sh

Component Diagram

┌─────────────────────────────────────────────────────────────────┐
│ 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 && ... │
└─────────────────────────────────────────────────────────────────┘

Interface Contracts

# 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 otherwise

Data Flow

shipwright 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

Error Boundaries

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.


Alternatives Considered

  1. Inline in scripts/sw router — 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/--version surface. Rejected: architectural debt with no benefit.

  2. Shared library function — Pros: reusable pattern for trivial commands. Cons: premature abstraction — one command doesn't justify a new library. sw-hello.sh already exists and isn't extracted to a library either. Rejected: over-engineering.


Implementation Plan

Files to create:

  • scripts/sw-ping.sh — command implementation (≈67 lines, mirroring sw-hello.sh)
  • scripts/sw-ping-test.sh — test suite (≈108 lines, mirroring sw-hello-test.sh)

Files to modify:

  • scripts/sw — insert ping) case after hello) at line 607, before *) wildcard
  • package.json — insert bash scripts/sw-ping-test.sh && in "test" string after sw-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

Validation Criteria

  • bash scripts/sw-ping.sh outputs exactly pong (one line, no extra whitespace) to stdout and exits 0
  • bash scripts/sw ping outputs exactly pong and exits 0 (router dispatch works)
  • bash scripts/sw-ping.sh --help and -h both output text containing USAGE and exit 0
  • bash scripts/sw-ping.sh --version outputs a semver string matching ^[0-9]+\.[0-9]+\.[0-9]+ and exits 0
  • bash scripts/sw-ping.sh --invalid exits 1 and writes error to stderr
  • bash scripts/sw-ping-test.sh reports PASS: 6 FAIL: 0
  • bash scripts/sw-hello-test.sh still reports PASS: 6 FAIL: 0 (no regression)
  • sw-ping-test.sh appears in package.json "test" script and npm test does not fail on it

Clone this wiki locally

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