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 204

ezigus edited this page Mar 19, 2026 · 2 revisions

ADR written to .claude/pipeline-artifacts/design.md.

Summary of the architecture decision:

  • Chosen approach: Add quickstart subcommand to the existing sw-ci.sh router, with shipwright init --ci as a 3-line delegation in sw-init.sh
  • New file: scripts/lib/ci-quickstart.sh — pure functions for detection, YAML generation, and README section output
  • 5 modified files: sw-ci.sh (routing + orchestrator), sw-init.sh (--ci flag), sw-doctor.sh (--ci validation), plus test files
  • Key decisions: library/orchestrator separation for testability, fast+standard as defaults, skip-existing-files safety, atomic writes, and post-write validation via existing cmd_validate()
  • Zero new dependencies — reuses pipeline-detection.sh, helpers.sh, and templates/pipelines/*.json
  • 15 validation criteria covering happy path, error cases, edge cases, Bash 3.2 compat, and air-gapped mode ine-detection.shdetect_repo_environments_json()returns a JSON array of{id, marker}objects anddefault_test_cmd_for_environment()` maps env IDs to test commands
  • Pipeline templates live in templates/pipelines/*.json with a stages[] array of {id, enabled, gate, config} objects
  • The existing scripts/sw-ci.sh already handles workflow generation (cmd_generate), validation (cmd_validate), and nine other CI subcommands via a case-statement router (line 566)
  • scripts/sw-init.sh uses a while/case flag-parsing loop and delegates to other scripts (e.g., doctor at the end)
  • scripts/sw-doctor.sh uses check_pass/check_warn/check_fail counters with sectioned validation
  • Air-gapped mode (NO_GITHUB=1) must be respected — no gh API calls when set

Decision

Approach B: Add quickstart subcommand to sw-ci.sh, with shipwright init --ci as a thin delegation.

Why this approach

  1. CI logic stays in the CI module. sw-ci.sh already owns workflow generation, validation, badges, and secrets management. Quickstart is a natural extension — it composes existing primitives (detect_repo_environments_json, template JSON reading, cmd_validate) into a guided flow.

  2. Minimal blast radius. Only 3 lines change in sw-init.sh (flag parse + delegation). sw-ci.sh gains one new case entry and one new orchestrator function. The core generation logic lives in a new sourced library (scripts/lib/ci-quickstart.sh) that is independently testable.

  3. Consistent with existing patterns. Other commands delegate similarly — init calls doctor at the end, pipeline start delegates to loop. Two entry points (init --ci and ci quickstart) is the established pattern.

Component Diagram

┌─────────────────────────────────────────────────────────┐
│ Entry Points │
│ sw-init.sh --ci sw-ci.sh quickstart │
│ (3 lines: parse + exec) (direct invocation) │
└─────────┬─────────────────────────────┬─────────────────┘
 │ exec │ call
 └──────────┐ ┌──────────────┘
 ▼ ▼
┌─────────────────────────────────────────────────────────┐
│ sw-ci.sh :: cmd_quickstart() │
│ Orchestrator: parse flags → detect → generate → validate │
└────┬──────────┬──────────┬──────────────┬───────────────┘
 │ │ │ │
 ▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌────────────┐ ┌──────────────┐
│pipeline-│ │templates/│ │ci-quick- │ │cmd_validate()│
│detection│ │pipelines/│ │start.sh │ │(existing in │
│.sh │ │*.json │ │(new lib) │ │sw-ci.sh) │
│(existing)│ │(existing)│ │ │ │ │
└─────────┘ └─────────┘ └────────────┘ └──────────────┘

Data Flow

1. User invokes: `shipwright init --ci` or `shipwright ci quickstart`
 ├── init --ci: sw-init.sh parses --ci, execs sw-ci.sh quickstart "$@"
 └── ci quickstart: sw-ci.sh router dispatches to cmd_quickstart()
2. cmd_quickstart() parses flags:
 --template <list> (default: "fast,standard")
 --test-cmd <cmd> (override auto-detection)
 --force (overwrite existing files)
3. detect_project_for_ci() → { env_id, test_cmd }
 └── calls detect_repo_environments_json() → picks first env
 └── calls default_test_cmd_for_environment(env_id) → test command
 └── --test-cmd flag overrides detected command
4. For each template in the comma-separated list:
 a. Validate template exists: templates/pipelines/${template}.json
 b. Read template JSON → extract enabled stages for workflow comment
 c. generate_quickstart_workflow(template, test_cmd, env_id) → YAML string
 d. Atomic write: tmp file → mv to .github/workflows/shipwright-${template}.yml
 e. Validate with cmd_validate() on the written file
 f. emit_event "ci_quickstart_generated" "template=$template"
5. generate_readme_section(templates[], owner, repo) → stdout
 └── Badges + usage instructions printed for user to paste
6. Print summary: files created, next steps

Interface Contracts

# ── scripts/lib/ci-quickstart.sh ──
# detect_project_for_ci()
# Stdout: two lines — env_id on line 1, test_cmd on line 2
# Exit 0: detection succeeded (may return "unknown" + empty cmd)
# Side effects: none
# Dependencies: pipeline-detection.sh must be sourced
# generate_quickstart_workflow(template_name, test_cmd, env_id)
# 1ドル: template name (e.g., "fast", "standard")
# 2ドル: test command (e.g., "npm test")
# 3ドル: detected environment ID (e.g., "node", "python")
# Stdout: complete GitHub Actions YAML
# Exit 0: success
# Exit 1: template JSON not found
# Side effects: none (pure function)
# Dependencies: reads templates/pipelines/${template_name}.json
# generate_readme_section(templates_csv, owner, repo)
# 1ドル: comma-separated template names (e.g., "fast,standard")
# 2ドル: GitHub owner (e.g., "ezigus")
# 3ドル: GitHub repo name (e.g., "shipwright")
# Stdout: markdown snippet with badges and usage
# Exit 0: always
# Side effects: none
# ── scripts/sw-ci.sh ──
# cmd_quickstart([--template <list>] [--test-cmd <cmd>] [--force])
# Orchestrates the full quickstart flow
# Exit 0: all workflows generated successfully
# Exit 1: template not found, write failure, or validation failure
# Side effects: writes .github/workflows/shipwright-*.yml, emits events
# Dependencies: sources lib/ci-quickstart.sh, lib/pipeline-detection.sh
# ── scripts/sw-doctor.sh ──
# doctor_check_ci()
# Checks: workflow files exist, valid YAML structure (name/on/jobs),
# issue-label trigger present, secrets references valid
# Side effects: increments PASS/WARN/FAIL counters
# Exit: does not exit (caller checks FAIL count)

Error Boundaries

Error Component Handling
No project type detected detect_project_for_ci() Returns env_id="unknown", test_cmd=""cmd_quickstart() warns and inserts echo "TODO: add test command" placeholder
Template JSON not found generate_quickstart_workflow() Exits 1 with message listing available templates from templates/pipelines/
.github/workflows/ dir doesn't exist cmd_quickstart() Creates it with mkdir -p before writing
Existing workflow file, no --force cmd_quickstart() Skips file, prints warning with exact path. Continues with remaining templates
Generated YAML fails validation cmd_quickstart() Prints error, removes the invalid file, exits 1
jq not available detect_project_for_ci() Checked at function entry; exits 1 with install instructions
Write permission denied atomic_write via set -euo pipefail ERR trap fires, prints $BASH_SOURCE:$LINENO

Generated Workflow Structure

Each shipwright-{template}.yml contains:

name: "Shipwright Pipeline ({Template})"
on:
 issues:
 types: [labeled]
 push:
 branches: [main]
 pull_request:
 branches: [main]
 workflow_dispatch:
 inputs:
 issue_number:
 description: "Issue number to process"
 required: false
env:
 SHIPWRIGHT_PIPELINE: "{template}"
jobs:
 pipeline:
 runs-on: ubuntu-latest
 if: >-
 github.event_name \!= 'issues' ||
 github.event.label.name == 'ready-to-build'
 steps:
 - uses: actions/checkout@v4
 - name: Setup
 run: |
 # Install shipwright, detect project, install deps
 - name: Secrets check
 run: |
 # Verify GITHUB_TOKEN exists; warn for optional secrets
 - name: Run pipeline
 env:
 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 run: |
 shipwright pipeline start \
 --issue "${{ github.event.issue.number || inputs.issue_number }}" \
 --template {template}
 - name: Upload artifacts
 if: always()
 uses: actions/upload-artifact@v4
 with:
 name: pipeline-artifacts
 path: .claude/pipeline-artifacts/
 - name: Report status
 if: always() && env.NO_GITHUB \!= '1'
 run: |
 # Comment on issue with result

Key Design Decisions

D1: Library + Orchestrator separation

  • Context: Generation logic needs to be independently testable without invoking the full sw-ci.sh router.
  • Decision: Pure functions in scripts/lib/ci-quickstart.sh, orchestration in cmd_quickstart() within sw-ci.sh.
  • Consequence: Tests can source the library directly and call individual functions with controlled inputs.

D2: Default to fast + standard templates

  • Context: New users need sensible defaults. fast covers hotfixes and simple PRs; standard covers feature work with review gates.
  • Decision: --template defaults to "fast,standard". Users can specify any combination.
  • Consequence: Two workflow files by default. Users who want full or autonomous must opt in.

D3: Skip-by-default for existing files

  • Context: Users may have customized their workflow files. Overwriting silently would destroy work.
  • Decision: Check for existing files before writing. Skip with warning unless --force is passed.
  • Consequence: Safe for re-running, but users must explicitly opt in to regenerate.

D4: Atomic writes via tmp + mv

  • Context: set -euo pipefail means a partial write leaves a corrupted file.
  • Decision: Use the atomic_write pattern from helpers.sh (write to tmp, then mv).
  • Consequence: Either the full file exists or it doesn't — no partial states.

D5: Post-write validation

  • Context: Generated YAML could have syntax errors from string interpolation bugs.
  • Decision: Run cmd_validate() on each generated file immediately after writing. Remove file if validation fails.
  • Consequence: Users never end up with broken workflows committed to their repo.

Alternatives Considered

  1. Approach A: Extend sw-init.sh directly — Pros: Single entry point, reuses existing flag infrastructure. Cons: sw-init.sh is already ~500 lines handling local setup (CLAUDE.md, deploy platforms, doctor delegation). Adding workflow generation would violate single responsibility and make the file harder to maintain. CI generation is conceptually different from local init.

  2. Approach C: Standalone sw-ci-quickstart.sh script — Pros: Complete isolation, zero risk to existing files. Cons: Duplicates the helper-sourcing boilerplate, adds another top-level script to maintain, can't reuse cmd_validate() without sourcing sw-ci.sh, inconsistent with the subcommand pattern used everywhere else.

Implementation Plan

  • Files to create:

    • scripts/lib/ci-quickstart.sh — Core library (~200 lines): detect_project_for_ci(), generate_quickstart_workflow(), generate_readme_section()
  • Files to modify:

    • scripts/sw-ci.sh — Add quickstart) case entry (line ~574) + cmd_quickstart() orchestrator function (~40 lines)
    • scripts/sw-init.sh — Add --ci flag parse + exec "$SCRIPT_DIR/sw-ci.sh" quickstart "$@" delegation (~10 lines)
    • scripts/sw-doctor.sh — Add --ci flag + doctor_check_ci() validation section (~30 lines)
    • scripts/sw-ci-test.sh — Add 9 quickstart test cases (~80 lines)
    • scripts/sw-doctor-test.sh — Add 2 doctor --ci test cases (~20 lines)
  • Dependencies: None new. All dependencies are existing internal libraries:

    • scripts/lib/pipeline-detection.sh (detection)
    • scripts/lib/helpers.sh (output, events, atomic writes)
    • templates/pipelines/*.json (template definitions)
    • jq (already a documented prerequisite)
  • Risk areas:

    • Bash 3.2 case conversion: The existing cmd_generate() in sw-ci.sh uses ${var^}. New code must use tr '[:lower:]' '[:upper:]' instead. Grep for ${var^} and ${var,,} in new code during review.
    • YAML generation via heredoc: Multi-line YAML built from bash variables is fragile. Indentation errors produce invalid YAML. Mitigated by post-write cmd_validate().
    • Template schema drift: If templates/pipelines/*.json schema changes (e.g., stages renamed), generated workflow comments become stale. Mitigated by reading template dynamically rather than hardcoding stage lists.

Validation Criteria

  • shipwright ci quickstart in a Node.js repo (with package.json) generates shipwright-fast.yml and shipwright-standard.yml in .github/workflows/ with npm test as the test command
  • shipwright init --ci produces identical output to shipwright ci quickstart (delegation works)
  • Generated YAML passes shipwright ci validate without errors
  • Generated YAML includes on.issues.types: [labeled] trigger with ready-to-build filter
  • Generated YAML includes actions/upload-artifact@v4 step
  • Generated YAML includes NO_GITHUB conditional on status reporting step
  • --template full generates only shipwright-full.yml
  • --test-cmd "pytest -x" overrides detected test command in generated YAML
  • --force overwrites existing files; without --force, existing files are skipped with warning
  • Running in a directory with no detectable project type produces workflows with placeholder test command and a warning
  • shipwright doctor --ci passes when valid workflows exist
  • shipwright doctor --ci reports failure when no workflows exist
  • All functions work with NO_GITHUB=1 — no gh API calls
  • All new code passes bash -n syntax check on Bash 3.2 (no ${var^}, no declare -A, no readarray)
  • npm test passes with no regressions

Clone this wiki locally

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