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 to produce the ADR.


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

Context

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,,}, no readarray
  • set -euo pipefail is mandatory in every script
  • Output must be plain echo to 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 at scripts/sw:608
  • Test suites must be registered in the npm test chain in package.json

Decision

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 from echo succeeding under set -euo pipefail)
  • --help/-h and --version/-v flags (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" "$@"
 ;;

Component Diagram

┌─────────────────────────────────────────────────────────────────┐
│ 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

Interface Contracts

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

Data Flow

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.


Alternatives Considered

  1. 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 with hello, which is structurally identical in complexity. Rejected.

  2. Shared "simple command" library — Pros: DRY across hello and ping. Cons: premature abstraction — two commands don't justify a shared library; adds indirection with no benefit; forces future commands into a rigid template. Rejected.

  3. printf instead of echo — Pros: more portable across edge-case /bin/sh interpreters. Cons: the entire codebase uses echo; this file is #!/usr/bin/env bash so portability is not a concern; printf with no format string is unconventional. Rejected — use echo "pong".


Implementation Plan

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

Error Boundaries

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.


Validation Criteria

  • bash scripts/sw-ping.sh outputs exactly pong (one line, no trailing spaces, no ANSI codes) and exits 0
  • bash scripts/sw-ping.sh --help outputs text containing USAGE and exits 0
  • bash scripts/sw-ping.sh -h same as --help
  • bash scripts/sw-ping.sh --version outputs a string matching ^[0-9]+\.[0-9]+\.[0-9]+ and exits 0
  • bash scripts/sw-ping.sh --invalid exits 1 and writes to stderr (not stdout)
  • bash scripts/sw-ping-test.sh reports PASS: 6 FAIL: 0 and exits 0
  • bash scripts/sw ping outputs pong and exits 0 (router integration)
  • npm test exits 0 with all suites passing (regression gate)
  • No new files other than sw-ping.sh and sw-ping-test.sh are created
  • VERSION in sw-ping.sh matches package.json "version" field (currently 3.2.4)

Clone this wiki locally

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