mirror of
https://github.com/Sea-Haven-Industries/.github.git
synced 2026-09-30 10:33:12 +00:00
Merge pull request #67 from Sea-Haven-Industries/infra/ci-typescript-frontend-reusable
Some checks are pending
ci / ci / ci (push) Waiting to run
Some checks are pending
ci / ci / ci (push) Waiting to run
Add reusable CI workflow for TypeScript front-end apps
This commit is contained in:
commit
09fa684b37
2 changed files with 205 additions and 0 deletions
203
.github/workflows/ci-typescript-frontend.yaml
vendored
Normal file
203
.github/workflows/ci-typescript-frontend.yaml
vendored
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
name: CI — TypeScript Frontend
|
||||
|
||||
# Reusable CI for bundled TypeScript front-end apps (Vite / React / Vue SPAs
|
||||
# with vitest + Playwright). Emits the single `ci / ci` status context required
|
||||
# by the org branch-protection rulesets — keep the caller job id `ci` so the
|
||||
# context resolves to `ci / ci`.
|
||||
#
|
||||
# Runs, in order: a Sea Haven standards gate (required npm scripts present, no
|
||||
# AI-tool footers / hook bypasses / hardcoded secrets in the added lines),
|
||||
# then format:check, lint, build, unit tests, and an optional Playwright
|
||||
# browser smoke. Every step past the standards gate is individually toggleable.
|
||||
#
|
||||
# Caller example:
|
||||
# jobs:
|
||||
# ci:
|
||||
# uses: Sea-Haven-Industries/.github/.github/workflows/ci-typescript-frontend.yaml@main
|
||||
# with:
|
||||
# node-version: "24"
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
node-version:
|
||||
description: "Node.js version to use"
|
||||
type: string
|
||||
default: "24"
|
||||
working-directory:
|
||||
description: "Directory to run npm/build/test commands from"
|
||||
type: string
|
||||
default: "."
|
||||
cache-dependency-path:
|
||||
description: "Path to package-lock.json for npm cache"
|
||||
type: string
|
||||
default: "package-lock.json"
|
||||
required-scripts:
|
||||
description: "Comma-separated npm scripts that must exist in package.json"
|
||||
type: string
|
||||
default: "format:check,lint,build,test,test:e2e"
|
||||
run-standards:
|
||||
description: "Verify required npm scripts exist"
|
||||
type: boolean
|
||||
default: true
|
||||
run-guard:
|
||||
description: "Guard added lines against AI-tool footers, hook bypasses, and hardcoded secrets"
|
||||
type: boolean
|
||||
default: true
|
||||
run-format-check:
|
||||
description: "Run the format:check script (Prettier)"
|
||||
type: boolean
|
||||
default: true
|
||||
run-lint:
|
||||
description: "Run the lint script (ESLint)"
|
||||
type: boolean
|
||||
default: true
|
||||
run-build:
|
||||
description: "Run the build script"
|
||||
type: boolean
|
||||
default: true
|
||||
run-tests:
|
||||
description: "Run the test script (vitest / unit tests)"
|
||||
type: boolean
|
||||
default: true
|
||||
run-e2e:
|
||||
description: "Run the test:e2e script (Playwright browser smoke)"
|
||||
type: boolean
|
||||
default: true
|
||||
e2e-browser:
|
||||
description: "Playwright browser to install for the e2e smoke"
|
||||
type: string
|
||||
default: "chromium"
|
||||
run-conventions-check:
|
||||
description: "Require README.md and a .gitignore that covers .env"
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
concurrency:
|
||||
group: ci-typescript-frontend-${{ github.workflow }}-${{ github.ref }}-${{ inputs.working-directory }}
|
||||
cancel-in-progress: true
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ inputs.node-version }}
|
||||
cache: npm
|
||||
cache-dependency-path: ${{ inputs.cache-dependency-path }}
|
||||
|
||||
- name: Verify required npm scripts
|
||||
if: ${{ inputs.run-standards }}
|
||||
env:
|
||||
REQUIRED_SCRIPTS: ${{ inputs.required-scripts }}
|
||||
run: |
|
||||
node <<'NODE'
|
||||
const { readFileSync } = require("node:fs");
|
||||
const pkg = JSON.parse(readFileSync("package.json", "utf8"));
|
||||
const required = (process.env.REQUIRED_SCRIPTS || "")
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
const missing = required.filter((script) => !pkg.scripts?.[script]);
|
||||
|
||||
if (missing.length > 0) {
|
||||
console.error(`Missing required scripts: ${missing.join(", ")}`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`All required scripts present: ${required.join(", ")}`);
|
||||
NODE
|
||||
|
||||
- name: Guard changed lines
|
||||
if: ${{ inputs.run-guard }}
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
PUSH_BEFORE: ${{ github.event.before }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [ "${EVENT_NAME}" = "pull_request" ]; then
|
||||
BASE_REF="${PR_BASE_SHA}"
|
||||
else
|
||||
BASE_REF="${PUSH_BEFORE}"
|
||||
fi
|
||||
|
||||
if [ -z "${BASE_REF}" ] || [ "${BASE_REF}" = "0000000000000000000000000000000000000000" ]; then
|
||||
BASE_REF="$(git rev-parse HEAD~1 2>/dev/null || true)"
|
||||
fi
|
||||
|
||||
if [ -z "${BASE_REF}" ]; then
|
||||
echo "No base ref available; skipping changed-line guard."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
ADDED_LINES="$(git diff --unified=0 "${BASE_REF}" "${GITHUB_SHA}" | grep -E '^\+' | grep -vE '^\+\+\+' || true)"
|
||||
|
||||
if printf '%s\n' "${ADDED_LINES}" | grep -E 'Generated with (Claude [C]ode|[C]odex|Chat[G]PT)|--no-[v]erify|HUSKY[=]0'; then
|
||||
echo "Found generated-tool footer or hook bypass wording in added lines."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if printf '%s\n' "${ADDED_LINES}" | grep -E 'A[K]IA[0-9A-Z]{16}|A[S]IA[0-9A-Z]{16}|AWS[_]SECRET[_]ACCESS[_]KEY|aws[_-]?secret[_-]?access[_-]?key|J[W]TAuthenticationHIGHsecuredPassword|P[a]ssword=|S[e]ndGrid.*A[p]iKey'; then
|
||||
echo "Found a likely secret in added lines. Move sensitive values to the environment or secret manager."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Changed-line guard passed."
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Format check
|
||||
if: ${{ inputs.run-format-check }}
|
||||
run: npm run format:check
|
||||
|
||||
- name: Lint
|
||||
if: ${{ inputs.run-lint }}
|
||||
run: npm run lint
|
||||
|
||||
- name: Build
|
||||
if: ${{ inputs.run-build }}
|
||||
run: npm run build
|
||||
|
||||
- name: Unit tests
|
||||
if: ${{ inputs.run-tests }}
|
||||
run: npm test
|
||||
|
||||
- name: Browser smoke
|
||||
if: ${{ inputs.run-e2e }}
|
||||
env:
|
||||
CI: "true"
|
||||
E2E_BROWSER: ${{ inputs.e2e-browser }}
|
||||
run: |
|
||||
npx playwright install --with-deps "${E2E_BROWSER}"
|
||||
npm run test:e2e
|
||||
|
||||
- name: Conventions check
|
||||
if: ${{ inputs.run-conventions-check }}
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
errors=0
|
||||
fail() { echo "::error::$1"; errors=$((errors + 1)); }
|
||||
[[ -f README.md ]] || fail "Missing README.md"
|
||||
if [[ -f .gitignore ]]; then
|
||||
grep -qE '^\.env$|^\.env\b' .gitignore || fail ".gitignore does not include .env"
|
||||
else
|
||||
fail "Missing .gitignore"
|
||||
fi
|
||||
if [[ $errors -gt 0 ]]; then
|
||||
echo "Conventions check failed with $errors error(s)."
|
||||
exit 1
|
||||
fi
|
||||
echo "Conventions check passed."
|
||||
|
|
@ -10,6 +10,8 @@ Organization-level GitHub configuration for Sea Haven Industries.
|
|||
|
||||
**`.github/workflows/ci-typescript-cdk.yaml`** — Reusable CI workflow for TypeScript / CDK repos. Runs `npm ci` + optional `tsc --noEmit`, optional ESLint, optional Jest, and optional `cdk synth`. Also supports Node.js SAM repos via an optional `sam validate` step.
|
||||
|
||||
**`.github/workflows/ci-typescript-frontend.yaml`** — Reusable CI workflow for bundled TypeScript front-end apps (Vite / React / Vue SPAs). Runs a Sea Haven standards gate (required npm scripts present, no AI-tool footers / hook bypasses / hardcoded secrets in added lines), then `format:check`, `lint`, `build`, vitest unit tests, and an optional Playwright browser smoke. Emits the single `ci / ci` status context — keep the caller job id `ci`.
|
||||
|
||||
**`.github/workflows/cd-sam.yaml`** — Reusable CD workflow for SAM repos. Runs `sam build` + `sam deploy` with OIDC credentials and a CloudFormation execution role. Triggers via `workflow_call` from per-repo `deploy.yaml` on push to `main`.
|
||||
|
||||
**`.github/workflows/cd-cdk.yaml`** — Reusable CD workflow for CDK repos (TypeScript and Python). Runs `cdk deploy --all` with OIDC credentials. Supports optional Python setup for Python CDK repos and QEMU emulation for cross-platform Docker builds.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue