Skip to content

Pipeline Design 6

Seth Ford edited this page Feb 11, 2026 · 2 revisions

Design: shipwright doctor should validate dashboard dependencies and port availability

Context

shipwright doctor (scripts/sw-doctor.sh) is the setup validation command — it runs 13 sections checking prerequisites (tmux, jq, claude, node, git), configuration, permissions, and GitHub integration. The dashboard (dashboard/server.ts, a Bun WebSocket server on port 8767) is a core feature but has zero doctor coverage. Users hit confusing failures when Bun is missing, dashboard files aren't present (e.g., shallow clone), or port 8767 is already occupied by another process.

Constraints from the codebase:

  • Bash 3.2 compatible — no associative arrays, no readarray, no ${var,,}
  • set -euo pipefail with ERR trap in all scripts
  • Doctor uses check_pass/check_warn/check_fail helpers with PASS/WARN/FAIL counters
  • $_DOCTOR_SCRIPT_DIR resolves the repo root via $(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
  • The dashboard port is 8767 (confirmed in dashboard/server.ts and scripts/sw-dashboard.sh), not 3000 as the issue title assumed
  • SHIPWRIGHT_DASHBOARD_PORT env var overrides the default port
  • Bun is an optional dependency (dashboard-only), so missing Bun should warn, not fail — matching how gh is treated in Section 12

Decision

Add Section 14: Dashboard to sw-doctor.sh between the GitHub Integration section (Section 13, ending ~line 886) and the Summary block (~line 888). Three subsections with warn-level severity:

14a. Bun Runtime Check

  • command -v bun to detect presence
  • On success: check_pass "bun $(bun --version)"
  • On failure: check_warn "bun not found — required for dashboard (curl -fsSL https://bun.sh/install | bash)"
  • Warn-level because Bun is only needed for the dashboard feature, not core pipeline functionality

14b. Dashboard File Existence

  • Resolve repo root as $_DOCTOR_SCRIPT_DIR/..
  • Check dashboard/server.ts exists (-f test)
  • Check dashboard/public/ exists (-d test)
  • On missing: check_warn with path shown — handles shallow clones or partial installs
  • On present: single check_pass "dashboard files present"

14c. Port 8767 Availability

  • Read port from ${SHIPWRIGHT_DASHBOARD_PORT:-8767}
  • If lsof is not available (! command -v lsof): check_pass "port $port check skipped (lsof not available)" — graceful degradation on minimal systems
  • If lsof -ti :"$port" exits non-zero (no listener): check_pass "port $port available"
  • If port is in use: get PID from lsof, get process name via ps -p "$pid" -o comm=
    • If process name contains "bun": check_pass "port $port in use by dashboard (pid $pid)" — our dashboard is already running, that's fine
    • Otherwise: check_warn "port $port in use by $proc_name (pid $pid) — dashboard may fail to start"

Test Suite: scripts/sw-doctor-test.sh

  • Follows the established test harness pattern from sw-tmux-test.sh exactly: same color constants, PASS/FAIL/TOTAL counters, FAILURES array, ERR trap, run_test wrapper
  • setup_env() creates sandboxed temp directory with mock scripts/, bin/, home/, dashboard/ structure
  • run_doctor() helper executes sw-doctor.sh with sandboxed HOME and PATH pointing to mock binaries
  • Mock binaries are simple shell scripts in $TEMP_DIR/bin that echo expected output and exit with controlled codes
  • 7 tests covering all branches of the three new checks

Registration

  • Append && bash scripts/sw-doctor-test.sh to package.json scripts.test chain as test #23

Alternatives Considered

  1. Make Bun a hard requirement (check_fail) — Pros: simpler logic, forces dashboard readiness. Cons: breaks doctor for users who don't use the dashboard; inconsistent with how gh (also optional) uses check_warn. Rejected because doctor should validate the full setup without forcing optional features.

  2. Use ss or netstat instead of lsof for port checking — Pros: ss is more modern on Linux. Cons: macOS doesn't ship ss; netstat output format varies across platforms; lsof is present on macOS by default and common on Linux. Rejected because lsof provides the best cross-platform coverage for the primary macOS target, with graceful skip when unavailable.

  3. Check port by attempting a TCP connect (/dev/tcp or nc) — Pros: no dependency on lsof. Cons: /dev/tcp requires bash (not POSIX), nc flags differ across platforms, and neither tells us which process holds the port (losing the dashboard-vs-other distinction). Rejected because process identification is a key UX differentiator.

  4. Embed dashboard checks into the existing Section 12 (Optional Tools) — Pros: fewer section headers, simpler diff. Cons: dashboard validation is a logical grouping of 3 related checks (runtime + files + port) that deserves its own section for clarity, matching how GitHub got its own Section 13. Rejected to maintain section-per-concern consistency.

Implementation Plan

  • Files to create: scripts/sw-doctor-test.sh (new test suite, ~250 lines)
  • Files to modify: scripts/sw-doctor.sh (add ~50 lines for Section 14 before Summary), package.json (append test #23 to chain)
  • Dependencies: None new. Uses lsof (already present on macOS, gracefully skipped elsewhere)
  • Risk areas:
    • lsof output parsing — PID extraction must handle multi-line output (multiple listeners); take only the first PID via head -1
    • ps -p PID -o comm= — the comm= (no header) format is POSIX but verify it works on macOS Bash 3.2
    • Section numbering — inserting Section 14 before Summary must not break the existing section counter or summary totals (they use PASS/WARN/FAIL variables that accumulate, so insertion point is safe)
    • pipefail interaction with lsoflsof exits 1 when no matching port; must guard with || true to avoid pipeline abort

Validation Criteria

  • shipwright doctor output includes 14. Dashboard section with three subsections
  • With Bun installed: shows PASS with version string
  • Without Bun: shows WARN with install instructions (not FAIL)
  • With dashboard/server.ts and dashboard/public/ present: shows PASS
  • With either missing: shows WARN naming the missing path
  • With port 8767 free: shows PASS
  • With port 8767 held by a bun process: shows PASS identifying it as dashboard
  • With port 8767 held by another process: shows WARN with process name and PID
  • Without lsof available: shows PASS with "skipped" note (not WARN or FAIL)
  • SHIPWRIGHT_DASHBOARD_PORT override is respected in port check
  • All 7 tests in sw-doctor-test.sh pass
  • sw-doctor-test.sh registered as test #23 in package.json
  • Existing test suites pass without modification
  • No Bash 3.2 incompatibilities (no associative arrays, no readarray, no ${var,,})
  • All new code uses set -euo pipefail with ERR trap

Clone this wiki locally