Files
certctl/scripts/ci-guards/openapi-codegen-drift.sh
shankar0123 38f1200f26 fix(api,codegen): ARCH-001-A — Phase 1 Orval codegen + 2 new CI guards (large diff)
Sprint 5 unified-master-audit closure. Pre-fix:

  - api/openapi.yaml: 7,788 LOC of hand-authored spec.
  - web/src/api/generated/: directory did NOT exist (the Phase-5
    scaffolding never had its first generation run).
  - scripts/ci-guards/openapi-codegen-drift.sh: skip-when-absent
    (line 33-39 — informational scaffold).
  - api/openapi.yaml info.version: '2.0.0', latest tag: v2.1.7
    (a 7-version drift between spec and ship).

Net effect: every new route required three coordinated edits (Go
handler, openapi.yaml, frontend client.ts), payload-level breaking
changes shipped unnoticed, and downstream API client integration
cost was permanent.

Phase 1 fix (the audit's literal scope):

  1. **Run Orval**, commit the generated tree. 316 files / ~1.8 MB
     under web/src/api/generated/, tags-split layout (one directory
     per OpenAPI tag), TanStack Query client mode. All output routes
     through web/src/api/mutator.ts which delegates to the existing
     fetchJSON in client.ts so auth/CSRF/401-event semantics stay
     in one place.

  2. **Fix two spec defects** the first orval run surfaced:
     - YAML duplicate-key bug at L77-89 — SCEP's description was
       misplaced under OIDC. Restored to its own tag entry.
     - Missing #/components/schemas/Error referenced by three
       operations. Aliased to the existing ErrorResponse schema.

  3. **Flip the codegen-drift guard from skip-when-absent to
     hard-gate.** A missing generated/ directory now fails the
     build with an actionable restore command. The existing
     regenerate-and-diff path stays as before.

  4. **New openapi-version-tag-parity CI guard.** Asserts
     openapi.yaml info.version equals the latest v* git tag. Falls
     back to api.github.com when the local clone is shallow.
     Bumped openapi.yaml info.version 2.0.0 → 2.1.7 in the same
     commit so the new guard greens out.

  5. **CI workflow** updated to fetch tags on the frontend job's
     checkout so the parity guard reads them locally (the GH API
     fallback still works but adds a network round-trip).

Verified locally:
  - openapi-codegen-drift.sh: clean (re-generation produces
    byte-identical tree to what's tracked).
  - openapi-version-tag-parity.sh: clean (2.1.7 == v2.1.7).
  - tsc --noEmit: exit 0 across the entire frontend (the
    generated tree's responseType field threaded through the
    mutator's CertctlFetchOptions cleanly).
  - Existing Vitest suite: 141/141 pass on the three sampled
    suites (AuthProvider + client + IssuerHierarchyPage).

Follow-on work (NOT in this commit):
  - Per-consumer migration: pages flip from client.ts imports to
    generated/ imports one at a time. Both styles share fetchJSON
    semantics, so the migration is incremental.
  - Server-side oapi-codegen handler stubs (Phase 2 from the
    audit's fix language) — separate sprint.

Closes ARCH-001-A.
2026-05-16 05:19:22 +00:00

94 lines
3.8 KiB
Bash
Executable File

#!/usr/bin/env bash
# scripts/ci-guards/openapi-codegen-drift.sh
#
# Phase 5 ARCH-M6 scaffolding (2026-05-13): block the build when
# api/openapi.yaml changes but web/src/api/generated/ wasn't
# regenerated alongside. The generated tree is git-tracked; running
# `cd web && npm run generate` regenerates from api/openapi.yaml.
#
# Guard logic:
#
# 1. If web/src/api/generated/ does NOT exist yet, do nothing.
# This phase ships the orval.config.ts scaffolding without
# running `npm install orval` from the sandbox (disk-full); the
# first operator-run of `npm run generate` creates the directory
# and the guard activates from that point forward.
#
# 2. If web/src/api/generated/ exists:
# - Regenerate into a tmp dir using `npm run generate`
# (requires orval to be installed locally).
# - Diff against the tracked tree.
# - Fail the build with a clear regenerate-command pointer.
#
# Note: this guard requires Node + npm to be available on the CI
# runner. The frontend job in ci.yml already provisions both
# (.github/workflows/ci.yml frontend-build), so wiring is mechanical.
# Run order matters: this guard must run AFTER `npm ci` in the
# frontend job so orval is in node_modules.
set -e
GENERATED_DIR="web/src/api/generated"
if [ ! -d "$GENERATED_DIR" ]; then
# ARCH-001-A closure (Sprint 5, 2026-05-16). Pre-fix the guard
# tolerated a missing generated/ tree as "Phase 5 scaffolding."
# Phase 5 scaffolded; ARCH-001-A landed the first generation and
# committed the tree. From this point on, a missing generated/
# directory means a contributor deleted it (intentionally or not)
# — the guard fails closed so CI catches the deletion.
echo "::error::openapi-codegen-drift: $GENERATED_DIR does not exist. ARCH-001-A committed the initial generated tree; a deletion has happened since."
echo " Restore via:"
echo " cd web && npm ci && npm run generate"
echo " Then commit the result. Do NOT delete generated/ — the codegen-drift"
echo " guard depends on its presence."
exit 1
fi
# Tolerate the case where orval isn't installed in the local
# environment — in that case the guard is informational. The CI
# pipeline activates it once the frontend job runs `npm ci`.
if [ ! -f "web/node_modules/.bin/orval" ]; then
echo "openapi-codegen-drift: skipped — web/node_modules/.bin/orval not present."
echo " Run 'cd web && npm ci' to install. CI runs npm ci before this guard."
exit 0
fi
# Snapshot the tracked tree, regenerate into a tmpdir, diff.
TMPGEN="$(mktemp -d -t orval-drift.XXXXXX)"
trap 'rm -rf "$TMPGEN"' EXIT
# Copy the tracked tree so we can compare against a fresh regeneration.
cp -r "$GENERATED_DIR" "$TMPGEN/tracked"
# Regenerate in-place; orval honors orval.config.ts output paths.
(cd web && npm run generate --silent) >/dev/null
# Diff the tracked tree against the freshly-regenerated tree.
if diff -r --brief "$TMPGEN/tracked" "$GENERATED_DIR" >/dev/null 2>&1; then
echo "openapi-codegen-drift: clean — generated client matches openapi.yaml"
# Restore the tracked tree (regeneration overwrites it; restore so
# the working tree is back to the tracked state).
rm -rf "$GENERATED_DIR"
cp -r "$TMPGEN/tracked" "$GENERATED_DIR"
exit 0
fi
echo "::error::openapi-codegen-drift regression: $GENERATED_DIR is stale."
echo ""
echo "api/openapi.yaml changed but the generated client tree wasn't"
echo "regenerated alongside. Regenerate with:"
echo ""
echo " cd web && npm run generate"
echo ""
echo "Then commit the updated $GENERATED_DIR/ alongside the openapi.yaml"
echo "change in this PR."
echo ""
echo "Diff (- tracked, + regenerated):"
diff -r "$TMPGEN/tracked" "$GENERATED_DIR" | head -80
# Restore tracked tree so the working tree isn't surprising.
rm -rf "$GENERATED_DIR"
cp -r "$TMPGEN/tracked" "$GENERATED_DIR"
exit 1