Skip to content

Pipeline Design 204

ezigus edited this page Mar 20, 2026 · 2 revisions

Now I have the full picture. Let me write the ADR.

Design: GitHub Actions quick-start generator with zero-config workflow templates

Context

Shipwright needs a way for users to bootstrap GitHub Actions CI/CD workflows without manual YAML authoring. The tool should auto-detect project type, select appropriate test commands, and generate production-ready workflow files that integrate with Shipwright's label-driven pipeline model. The codebase is entirely shell-based (Bash 3.2 compatible), uses set -euo pipefail, and follows established patterns for output helpers, event logging, and atomic file writes.

Two iterations have already landed significant infrastructure: scripts/sw-ci.sh (725 lines, 11 subcommands), scripts/lib/ci-quickstart.sh (257 lines, 3 pure functions), and comprehensive test suites. The old 13 hardcoded workflow files (2,927 lines) have been removed in favor of this generator approach.

Constraints:

  • Bash 3.2 compatibility (macOS default shell) — no associative arrays, readarray, or ${var^}
  • Must work air-gapped with NO_GITHUB=1
  • Generated YAML must include: issue label trigger (ready-to-build), secrets check, artifact upload, status reporting
  • No new runtime dependencies beyond jq (already required)

Decision

Bash-based generator with heredoc YAML templates in ci-quickstart.sh, invoked via shipwright ci quickstart (or shipwright init --ci).

Architecture

sw-init.sh --ci ──delegates──► sw-ci.sh :: cmd_quickstart()
                                     │
                                     ├─ sources lib/ci-quickstart.sh
                                     │    ├─ detect_project_for_ci()    → env_id + test_cmd
                                     │    ├─ generate_quickstart_workflow() → YAML string
                                     │    └─ generate_readme_section()  → markdown string
                                     │
                                     ├─ validates template exists in templates/pipelines/*.json
                                     ├─ atomic write: .tmp + mv
                                     └─ emits ci_quickstart_generated event

sw-doctor.sh --ci ──► validates generated .github/workflows/shipwright-*.yml
                       ├─ YAML structure (name/on/jobs)
                       ├─ label trigger (ready-to-build)
                       └─ artifact upload step

Data Flow

  1. Detection: detect_project_for_ci() delegates to pipeline-detection.shdetect_repo_environments_json(), returns first-match env ID + test command
  2. Generation: For each template in CSV list, reads templates/pipelines/{name}.json for metadata, generates workflow YAML via heredoc with environment-specific setup steps
  3. Output: Atomic file write to .github/workflows/shipwright-{template}.yml, README badge section to stdout

Error Handling

  • Missing template: lists available templates from templates/pipelines/, exits 1
  • Existing workflow: skips with warning (overridable with --force)
  • No jq: detection returns "unknown", workflow uses placeholder test command
  • Unknown project type: warns and suggests --test-cmd override

Key Bug Fix Required

sw-ci.sh:183 uses ${workflow_name^} (Bash 4+ uppercase). Must replace with tr pattern already used in ci-quickstart.sh:77-79:

first_char=$(printf '%s' "$workflow_name" | cut -c1 | tr '[:lower:]' '[:upper:]')
rest_chars=$(printf '%s' "$workflow_name" | cut -c2-)
display_name="${first_char}${rest_chars}"

NO_GITHUB Gap

sw-ci.sh commands that call git config --get remote.origin.url (badges, secrets check, README section) will fail in air-gapped mode. Guard these with [[ "${NO_GITHUB:-}" == "1" ]] checks and skip gracefully.

Alternatives Considered

  1. Template files with variable substitution (e.g., .yml.tmpl files with sed replacement) — Pros: cleaner YAML readability, easier for users to customize templates / Cons: more files, needs a template engine in bash, diverges from codebase pattern where YAML is generated inline. Not chosen because the heredoc approach is already implemented, tested, and consistent with how other scripts generate config.

  2. Node.js-based generator (leverage the existing src/ TypeScript codebase) — Pros: proper YAML library, type safety, testable with vitest / Cons: ships a runtime dependency for what's a thin CLI operation, breaks the convention that scripts/ are self-contained bash. Not chosen because the CI tooling should work without node_modules installed.

Implementation Plan

  • Files to modify:

    • scripts/sw-ci.sh — Fix Bash 3.2 ${var^} bug at line 183, add NO_GITHUB guards to cmd_generate() and GitHub-dependent commands
    • scripts/lib/ci-quickstart.sh — No structural changes needed, already Bash 3.2 safe
    • scripts/sw-ci-test.sh — Add tests for: NO_GITHUB mode, Bash 3.2 compat on cmd_generate, --template all shorthand, invalid template name, missing templates directory
    • scripts/sw-doctor.sh — Add secrets reference and workflow_dispatch trigger validation to --ci check
    • scripts/sw-doctor-test.sh — Add test for --ci with missing YAML sections
  • Files to create: None

  • Dependencies: None new (jq already required)

  • Risk areas:

    • YAML generation via heredoc is fragile to indentation changes — any edit to generate_quickstart_workflow() must be verified against GitHub Actions' YAML parser
    • The cmd_generate() function (line 167-233) generates YAML from composed pipeline JSON and is separate from the quickstart path — both paths must stay Bash 3.2 safe independently
    • grep -c under pipefail in cmd_analyze() (lines 359-368) uses || true correctly, but new grep patterns must follow the same guard

Validation Criteria

  • shipwright ci quickstart generates valid YAML for each template (fast, standard, full, autonomous) — verified by cmd_validate()
  • Generated workflows contain: issues: types: [labeled] trigger, ready-to-build label check, actions/upload-artifact@v4, secrets.GITHUB_TOKEN check, vars.NO_GITHUB conditional
  • shipwright init --ci delegates correctly to cmd_quickstart()
  • shipwright doctor --ci validates generated workflow structure, label trigger, and artifact upload
  • sw-ci.sh has zero Bash 4+ syntax (grep -E '\$\{[a-z_]+\^' scripts/sw-ci.sh returns empty)
  • All commands work with NO_GITHUB=1 (no GitHub API calls, no git remote resolution required)
  • sw-ci-test.sh passes all tests including new NO_GITHUB and edge case tests
  • npm test full suite passes with no regressions

Clone this wiki locally