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 orchestration tool with 100+ subcommands, each implemented as a standalone bash script (scripts/sw-<name>.sh) dispatched by a central router (scripts/sw). The ping command is a liveness probe — a minimal diagnostic that confirms the CLI is on $PATH, executable, and returns a known exit code. This pattern is well-established in networked systems and developer tooling (Docker, kubectl, etc.) as the simplest possible health check.

Constraints from the codebase:

  • All scripts must be Bash 3.2 compatible (no associative arrays, no ${var,,}, no readarray)
  • set -euo pipefail and an ERR trap are mandatory on every script
  • VERSION="3.2.4" must appear at the top of every script and stay in sync with package.json
  • The router (scripts/sw) dispatches via a case statement using exec — new commands get one case entry immediately before the *) wildcard at line ~608
  • Test files mirror implementation files 1:1 (sw-ping-test.shsw-ping.sh)
  • Test suites are registered in package.json's "test" script as a &&-chained bash invocation sequence

Component Diagram

┌──────────────────────────────────────────────────────────────┐
│ User Shell │
│ $ shipwright ping │
└─────────────────────┬────────────────────────────────────────┘
 │ exec via PATH
 ▼
┌──────────────────────────────────────────────────────────────┐
│ Router scripts/sw │
│ main() → case "$cmd" in │
│ ping) exec "$SCRIPT_DIR/sw-ping.sh" "$@" ←── NEW │
│ hello) exec "$SCRIPT_DIR/sw-hello.sh" "$@" │
│ *) error + exit 1 │
│ esac │
└─────────────────────┬────────────────────────────────────────┘
 │ exec (replaces router process)
 ▼
┌──────────────────────────────────────────────────────────────┐
│ Command scripts/sw-ping.sh │
│ main() → case "${1:-}" in │
│ "") echo "pong" && exit 0 ← happy path │
│ --help|-h) show_help && exit 0 │
│ --version) echo "$VERSION" && exit 0 │
│ *) error + show_help && exit 1 │
│ esac │
└──────────────────────────────────────────────────────────────┘
 │ independent test execution
 ▼
┌──────────────────────────────────────────────────────────────┐
│ Test Suite scripts/sw-ping-test.sh │
│ Calls sw-ping.sh directly (not via router) │
│ 6 assertions: output, exit code, --help, -h, --version, │
│ invalid option │
│ Reports PASS/FAIL counts; exits 1 if any FAIL > 0 │
└──────────────────────────────────────────────────────────────┘

Decision

Implement ping as a standalone script (scripts/sw-ping.sh) registered in the central router, following the identical pattern as sw-hello.sh. The command has a single responsibility: print pong\n to stdout and exit 0 when called with no arguments.

Specific patterns applied:

  • main() uses a case "${1:-}" switch — the :- default prevents set -u from aborting when no argument is passed
  • exec in the router replaces the router process with the command process — no subprocess overhead, no double-ERR-trap noise
  • Helper fallbacks (info(), success(), etc.) are inlined as one-liners, sourced conditionally from lib/helpers.sh, so the script works both standalone and within the full CLI
  • ((PASS++)) arithmetic in the test file is safe under pipefail on Bash 3.2 — confirmed by the existing sw-hello-test.sh which uses this exact pattern at lines 15 and 29
  • Test file uses inline assert_equals/assert_exit_code helpers (not lib/test-helpers.sh) — matching the sw-hello-test.sh self-contained pattern

Interface Contracts

# sw-ping.sh — Public interface
# Invocation (no args): happy path
sw-ping.sh
# stdout: "pong\n"
# stderr: (empty)
# exit: 0
# Invocation (--help or -h): help text
sw-ping.sh --help | -h
# stdout: help text containing "USAGE"
# stderr: (empty)
# exit: 0
# Invocation (--version): version string
sw-ping.sh --version
# stdout: semver string matching /^[0-9]+\.[0-9]+\.[0-9]+/
# stderr: (empty)
# exit: 0
# Invocation (unknown flag): error path
sw-ping.sh --anything-else
# stdout: help text
# stderr: error message "Unknown option: ..."
# exit: 1
# Router contract
# scripts/sw dispatches: exec "$SCRIPT_DIR/sw-ping.sh" "$@"
# The router passes ALL remaining args verbatim — sw-ping.sh owns its own arg parsing
# sw-ping-test.sh — Test harness interface
# Outputs to stdout: colored PASS/FAIL lines + "PASS: N" + "FAIL: N"
# Exit code: 0 if FAIL==0, else 1
# No external dependencies — self-contained (no lib/test-helpers.sh)
# Direct invocation: bash scripts/sw-ping-test.sh

Data Flow

Request path (no args):
 User → shell → scripts/sw [cmd=ping] → exec sw-ping.sh → main("") → echo "pong" → stdout → exit 0
Request path (--help):
 User → shell → scripts/sw [cmd=ping, args=--help] → exec sw-ping.sh --help
 → main("--help") → show_help() → stdout → exit 0
Error path (unknown flag):
 User → shell → scripts/sw [cmd=ping, args=--bogus] → exec sw-ping.sh --bogus
 → main("--bogus") → error() [stderr] + show_help() [stdout] → exit 1
Test execution path (isolated):
 npm test → bash sw-ping-test.sh
 → sw-ping.sh (direct, no router)
 → capture stdout + exit code
 → assert_equals / assert_exit_code → PASS/FAIL counters → exit 0|1

Error Boundaries

Component Errors It Handles Errors It Propagates
scripts/sw (router) Unknown command name → error + exit 1 Passes all args verbatim to sw-ping.sh; does not catch ping errors
sw-ping.sh Unknown flag → error + exit 1; missing helpers (test env) → fallback helpers ERR trap catches unexpected failures; exits with bash error code
sw-ping-test.sh Assertion failures → FAIL++ + diagnostic message; never aborts mid-suite set -euo pipefail still active — unexpected script errors exit immediately with line/status

Key boundary: The router uses exec (not a subshell), so the ping script's exit code becomes the router's exit code directly. There is no error translation layer between them.


Alternatives Considered

  1. Inline in router (scripts/sw) — Pros: No new file, zero dispatch overhead. Cons: Violates the single responsibility of the router (it routes, not executes); breaks testability (can't call sw-ping.sh directly); deviates from every other command; makes the router grow without bound. Rejected.

  2. Shared ping function in lib/helpers.sh — Pros: Reusable if other scripts wanted a liveness check. Cons: Premature abstraction — nothing else needs ping; lib/helpers.sh is for output/event utilities, not commands; adds coupling to a shared library that has no other command logic. Rejected.

  3. shipwright ping as a router alias for shipwright hello — Pros: Near-zero implementation. Cons: Wrong output (hello worldpong), wrong semantics, confusing to users, not independently testable. Rejected.


Implementation Plan

Files to create:

File Purpose
scripts/sw-ping.sh The ping command implementation (~67 lines, mirrors sw-hello.sh)
scripts/sw-ping-test.sh 6-test suite (~108 lines, mirrors sw-hello-test.sh)

Files to modify:

File Change Location
scripts/sw Add ping) case before *) wildcard Lines 605–607 (insert before line 608)
package.json Append && bash scripts/sw-ping-test.sh to "test" script Line 39

Dependencies: None. No new packages, no new libraries.

Risk areas:

  1. Router insertion pointscripts/sw is 617 lines. The *) wildcard is at line 608. A mis-placed insertion (e.g., inside a nested case block like the version sub-case at lines 591–601) would silently mis-route ping. Mitigation: insert immediately after the hello) block at line 607, matching the existing pattern exactly.

  2. set -euo pipefail + arithmetic((PASS++)) returns exit code 1 when the result is 0 (i.e., on the very first increment from 0). This is not an issue here because PASS starts at 0 and ((PASS++)) only runs on the success branch of an if block — the outer if already exited the block. Confirmed safe in sw-hello-test.sh. However, a bare ((PASS++)) as a standalone statement at the top level would abort under pipefail if PASS were 0 — never do this outside a conditional.

  3. package.json test chain orderingnpm test is a single && chain. If sw-ping-test.sh is appended after the last existing test, a failure in any prior test will skip it; conversely, a failure in sw-ping-test.sh stops subsequent tests. This is acceptable and consistent with how all other tests are registered.

  4. Output exactness — The test asserts output == "pong" with assert_equals. If lib/helpers.sh is on the path and info() or other helpers emit prefix characters before echo "pong", the test will fail. Mitigation: echo "pong" must be a bare echo, not wrapped in any helper function.


Validation Criteria

  • bash scripts/sw-ping-test.sh exits 0 with output PASS: 6 / FAIL: 0
  • scripts/sw-ping.sh (direct) prints exactly pong to stdout (no ANSI codes, no prefix characters)
  • scripts/sw-ping.sh exits 0 on no-arg invocation; exits 1 on unknown flag
  • shipwright ping (via router) prints pong and exits 0 — confirms router dispatch is wired correctly
  • shipwright ping --help prints text containing USAGE and exits 0
  • shipwright ping --version prints a semver string matching /^[0-9]+\.[0-9]+\.[0-9]+/ and exits 0
  • shipwright ping --bogus exits 1 and emits an error to stderr
  • npm test passes in full (no regressions in the 100+ existing test suites)
  • shipwright version check exits 0 — confirms VERSION="3.2.4" in sw-ping.sh matches package.json
  • shellcheck scripts/sw-ping.sh produces no errors (disable SC2034 for VERSION if needed, matching sw-hello.sh line 7)

Clone this wiki locally

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