Track HiveGrid Cloud planning reference
This commit is contained in:
1 parent
b3d5ea6764
commit
e341c5938a
5784 files changed
+1104929
-3
No files matched your search
@@ -0,0 +1,82 @@
|
||||
name: Sentry Release
|
||||
|
||||
# Reusable workflow that tags a release in our self-hosted Sentry,
|
||||
# optionally uploads source-maps, correlates commits and marks a deploy.
|
||||
# Consumed via `uses: ./.github/workflows/_sentry-release.yml`.
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
project:
|
||||
description: "Sentry project slug (boilerplate-api|boilerplate-worker|boilerplate-web)"
|
||||
required: true
|
||||
type: string
|
||||
environment:
|
||||
description: "Deployment environment label (production|staging|preview)"
|
||||
required: true
|
||||
type: string
|
||||
sourcemaps-path:
|
||||
description: "Directory containing source-maps to upload, or 'none'"
|
||||
required: false
|
||||
type: string
|
||||
default: "none"
|
||||
sourcemaps-artifact:
|
||||
description: "Optional workflow artifact to download before source-map upload, or 'none'"
|
||||
required: false
|
||||
type: string
|
||||
default: "none"
|
||||
secrets:
|
||||
SENTRY_AUTH_TOKEN:
|
||||
description: "Sentry auth token (project:releases scope)"
|
||||
required: true
|
||||
SENTRY_ORG:
|
||||
description: "Sentry organisation slug"
|
||||
required: true
|
||||
SENTRY_URL:
|
||||
description: "Sentry self-hosted base URL"
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Sentry release (${{ inputs.project }})
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Prepare pnpm 10.33.0
|
||||
run: corepack prepare pnpm@10.33.0 --activate
|
||||
|
||||
- name: Download source-map artifact
|
||||
if: ${{ inputs.sourcemaps-artifact != 'none' }}
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ inputs.sourcemaps-artifact }}
|
||||
path: ${{ inputs.sourcemaps-path }}
|
||||
|
||||
- name: Verify sentry-cli is reachable
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_URL: ${{ secrets.SENTRY_URL }}
|
||||
run: |
|
||||
node -v
|
||||
npx --yes @sentry/cli@2.41.1 --version
|
||||
|
||||
- name: Tag Sentry release
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_URL: ${{ secrets.SENTRY_URL }}
|
||||
run: |
|
||||
bash scripts/ci/sentry-release.sh \
|
||||
--project "${{ inputs.project }}" \
|
||||
--environment "${{ inputs.environment }}" \
|
||||
--release "${{ github.sha }}" \
|
||||
--sourcemaps "${{ inputs.sourcemaps-path }}"
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
name: Accessibility
|
||||
|
||||
# Runs the axe-core vitest suites (apps/web ui components, SkipToContent)
|
||||
# and the WCAG-2.2-AA color-contrast verifier on every PR + push to main.
|
||||
#
|
||||
# Spec: docs/improvement-plan/10-compliance-polish/wcag-internal.md
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: a11y-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
axe:
|
||||
name: axe-core + color-contrast
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
DATABASE_URL: postgresql://toolctrl:toolctrl@localhost:5432/toolctrl?schema=public
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Prepare pnpm 10.33.0
|
||||
run: corepack prepare pnpm@10.33.0 --activate
|
||||
|
||||
- name: Configure pnpm store
|
||||
run: pnpm config set store-dir .pnpm-store
|
||||
|
||||
- name: Cache pnpm store
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .pnpm-store
|
||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile --prefer-offline
|
||||
|
||||
- name: Run color-contrast verifier
|
||||
run: pnpm run verify:color-contrast
|
||||
|
||||
- name: Verify accessibility coverage
|
||||
run: pnpm run verify:a11y-coverage
|
||||
|
||||
- name: Run axe vitest suite (test:a11y)
|
||||
run: pnpm run test:a11y
|
||||
|
||||
- name: Verify accessibility lint baseline
|
||||
run: pnpm run verify:a11y-baseline
|
||||
|
||||
- name: Lint accessibility rules (eslint-plugin-jsx-a11y)
|
||||
run: pnpm run lint
|
||||
@@ -0,0 +1,74 @@
|
||||
name: Bundle Budget Baseline
|
||||
|
||||
# Captures the main-branch bundle report as an artifact so the
|
||||
# bundle-budget PR workflow can render Δ-from-main columns. Runs on
|
||||
# push to main; downstream PR workflow downloads `bundle-baseline-main`.
|
||||
# Spec: docs/improvement-plan/10-compliance-polish/bundle-budget.md
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "apps/web/**"
|
||||
- "packages/brand/**"
|
||||
- "packages/config/**"
|
||||
- "packages/entitlements/**"
|
||||
- "packages/form-widget/**"
|
||||
- "packages/foundation/**"
|
||||
- "packages/i18n/**"
|
||||
- "packages/plugin-runtime/**"
|
||||
- "packages/ui*/**"
|
||||
- "packages/url/**"
|
||||
- "packages/view-models/**"
|
||||
- "package.json"
|
||||
- "pnpm-lock.yaml"
|
||||
- "scripts/bundle-budgets.json"
|
||||
- "scripts/verify-bundle-size.mjs"
|
||||
- ".github/workflows/bundle-budget-baseline.yml"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
capture:
|
||||
name: Capture bundle baseline
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Prepare pnpm 10.33.0
|
||||
run: corepack prepare pnpm@10.33.0 --activate
|
||||
|
||||
- name: Configure pnpm store
|
||||
run: pnpm config set store-dir .pnpm-store
|
||||
|
||||
- name: Cache pnpm store
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .pnpm-store
|
||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build web
|
||||
run: pnpm --filter @toolctrl/web build
|
||||
|
||||
- name: Verify aggregate bundle budgets
|
||||
run: pnpm run verify:bundle-size
|
||||
|
||||
- name: Measure bundle sizes
|
||||
run: node scripts/verify-bundle-size.mjs --json > main-bundle-report.json
|
||||
|
||||
- name: Upload baseline artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bundle-baseline-main
|
||||
path: main-bundle-report.json
|
||||
retention-days: 30
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
name: Bundle Budget
|
||||
|
||||
# Posts a sticky PR comment with per-route gzip sizes vs scripts/bundle-budgets.json.
|
||||
# Non-blocking: exceeds-limit routes show a warning row but never fail the PR check.
|
||||
# Spec: docs/improvement-plan/10-compliance-polish/bundle-budget.md
|
||||
# Docs: docs/architecture/bundle-budgets.md
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "apps/web/**"
|
||||
- "packages/brand/**"
|
||||
- "packages/config/**"
|
||||
- "packages/entitlements/**"
|
||||
- "packages/form-widget/**"
|
||||
- "packages/foundation/**"
|
||||
- "packages/i18n/**"
|
||||
- "packages/plugin-runtime/**"
|
||||
- "packages/ui*/**"
|
||||
- "packages/url/**"
|
||||
- "packages/view-models/**"
|
||||
- "package.json"
|
||||
- "pnpm-lock.yaml"
|
||||
- "scripts/bundle-budgets.json"
|
||||
- "scripts/verify-bundle-size.mjs"
|
||||
- ".github/workflows/bundle-budget.yml"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
measure:
|
||||
name: Measure & report bundle sizes
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
DATABASE_URL: postgresql://toolctrl:toolctrl@localhost:5432/toolctrl?schema=public
|
||||
steps:
|
||||
- name: Checkout PR head
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Prepare pnpm 10.33.0
|
||||
run: corepack prepare pnpm@10.33.0 --activate
|
||||
|
||||
- name: Configure pnpm store
|
||||
run: pnpm config set store-dir .pnpm-store
|
||||
|
||||
- name: Cache pnpm store
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .pnpm-store
|
||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build web (PR head)
|
||||
run: pnpm --filter @toolctrl/web build
|
||||
|
||||
- name: Verify aggregate bundle budgets
|
||||
run: pnpm run verify:bundle-size
|
||||
|
||||
- name: Measure PR bundle sizes
|
||||
run: node scripts/verify-bundle-size.mjs --json > pr-bundle-report.json
|
||||
|
||||
- name: Upload PR bundle report
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bundle-report-pr-${{ github.event.pull_request.number }}
|
||||
path: pr-bundle-report.json
|
||||
retention-days: 14
|
||||
|
||||
- name: Download main baseline (if available)
|
||||
id: baseline
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
name: bundle-baseline-main
|
||||
workflow: bundle-budget-baseline.yml
|
||||
branch: main
|
||||
path: baseline
|
||||
if_no_artifact_found: warn
|
||||
continue-on-error: true
|
||||
|
||||
- name: Render PR comment markdown
|
||||
run: node scripts/render-bundle-budget-comment.mjs \
|
||||
--current pr-bundle-report.json \
|
||||
--baseline baseline/main-bundle-report.json \
|
||||
--out bundle-comment.md
|
||||
|
||||
- name: Post sticky PR comment
|
||||
uses: marocchino/sticky-pull-request-comment@v2
|
||||
with:
|
||||
header: bundle-budget-report
|
||||
path: bundle-comment.md
|
||||
+402
@@ -0,0 +1,402 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
quality:
|
||||
name: Quality Gates
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
env:
|
||||
DATABASE_URL: postgresql://toolctrl:toolctrl@localhost:5432/toolctrl?schema=public
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Prepare pnpm 10.33.0
|
||||
run: corepack prepare pnpm@10.33.0 --activate
|
||||
|
||||
- name: Configure pnpm store
|
||||
run: pnpm config set store-dir .pnpm-store
|
||||
|
||||
- name: Cache pnpm store
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .pnpm-store
|
||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile --prefer-offline
|
||||
|
||||
- name: Verify GitHub CI workflow
|
||||
run: pnpm run verify:github-ci-workflow
|
||||
|
||||
- name: Verify Gitea CI workflow
|
||||
run: pnpm run verify:gitea-ci-workflow
|
||||
|
||||
- name: Verify Gitea deploy workflow
|
||||
run: pnpm run verify:deploy-workflow
|
||||
|
||||
- name: Verify deploy readiness
|
||||
run: pnpm run verify:deploy-readiness
|
||||
|
||||
- name: Verify CI baseline
|
||||
run: pnpm run verify:ci-baseline
|
||||
|
||||
- name: Verify source health
|
||||
run: pnpm run verify:source-health
|
||||
|
||||
- name: Verify ESLint baseline
|
||||
run: pnpm run verify:eslint-baseline
|
||||
|
||||
- name: Verify env drift
|
||||
run: pnpm run verify:env-drift
|
||||
|
||||
- name: Verify health baseline
|
||||
run: pnpm run verify:health-baseline
|
||||
|
||||
- name: Verify API inventory
|
||||
run: pnpm run verify:api-inventory
|
||||
|
||||
- name: Verify tenant isolation report
|
||||
run: pnpm run verify:tenant-isolation-report
|
||||
|
||||
- name: Verify sidebar routes
|
||||
run: pnpm run verify:sidebar-routes
|
||||
|
||||
- name: Verify i18n keys
|
||||
run: pnpm run verify:i18n
|
||||
|
||||
- name: Verify Tenant Auth tenant and onboarding gates
|
||||
run: |
|
||||
pnpm run verify:tenant-auth-baseline
|
||||
pnpm run verify:onboarding-flow
|
||||
|
||||
- name: Verify Phase C package boundaries
|
||||
run: pnpm run verify:phase-c-boundaries
|
||||
|
||||
- name: Verify Feature Manifest plugin removal gates
|
||||
run: |
|
||||
pnpm run verify:core-build-without-plugins-smoke
|
||||
pnpm run verify:removed-modules
|
||||
|
||||
- name: Verify UI mock static baseline
|
||||
run: pnpm run verify:ui-visual
|
||||
|
||||
- name: Test UI mock hot-swap
|
||||
run: pnpm run test:hot-swap
|
||||
|
||||
- name: Verify accessibility coverage
|
||||
run: pnpm run verify:a11y-coverage
|
||||
|
||||
- name: Test accessibility smoke
|
||||
run: pnpm run test:a11y
|
||||
|
||||
- name: Verify documentation gates
|
||||
run: |
|
||||
pnpm run verify:porting-docs-baseline
|
||||
pnpm run verify:production-docs
|
||||
pnpm run verify:release-docs
|
||||
pnpm run verify:docs-links
|
||||
|
||||
- name: Typecheck
|
||||
run: pnpm typecheck
|
||||
|
||||
- name: Lint
|
||||
run: pnpm lint
|
||||
|
||||
- name: Foundation package tests
|
||||
run: pnpm run test:foundation
|
||||
|
||||
- name: Workspace package tests
|
||||
run: pnpm run test:packages
|
||||
|
||||
- name: Mobile wrapper tests
|
||||
run: pnpm run test:mobile
|
||||
|
||||
- name: Unit tests
|
||||
run: pnpm test
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
TOOLCTRL_BUILD_SOURCEMAPS: "1"
|
||||
run: pnpm run build:ci
|
||||
|
||||
- name: Verify bundle size
|
||||
run: pnpm run verify:bundle-size
|
||||
|
||||
- name: Verify web Sentry sourcemaps
|
||||
run: test -n "$(find apps/web/build/client -type f -name '*.map' -print -quit)"
|
||||
|
||||
- name: Upload web Sentry sourcemaps
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: web-client-sourcemaps
|
||||
path: apps/web/build/client
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
|
||||
- name: Verify production seed artifact
|
||||
run: pnpm run verify:production-seed-artifact
|
||||
|
||||
api:
|
||||
name: API Service
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 25
|
||||
env:
|
||||
DATABASE_URL: postgresql://toolctrl:toolctrl@localhost:5432/toolctrl?schema=public
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Prepare pnpm 10.33.0
|
||||
run: corepack prepare pnpm@10.33.0 --activate
|
||||
|
||||
- name: Configure pnpm store
|
||||
run: pnpm config set store-dir .pnpm-store
|
||||
|
||||
- name: Cache pnpm store
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .pnpm-store
|
||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile --prefer-offline
|
||||
|
||||
- name: Verify OpenAPI security
|
||||
run: pnpm run verify:openapi-security
|
||||
|
||||
- name: Typecheck API
|
||||
run: pnpm --filter @toolctrl/api typecheck
|
||||
|
||||
- name: Test API
|
||||
run: pnpm exec vitest run apps/api/src
|
||||
|
||||
- name: Build API
|
||||
run: pnpm --filter @toolctrl/api build
|
||||
|
||||
worker:
|
||||
name: Worker Service
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 25
|
||||
env:
|
||||
DATABASE_URL: postgresql://toolctrl:toolctrl@localhost:5432/toolctrl?schema=public
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Prepare pnpm 10.33.0
|
||||
run: corepack prepare pnpm@10.33.0 --activate
|
||||
|
||||
- name: Configure pnpm store
|
||||
run: pnpm config set store-dir .pnpm-store
|
||||
|
||||
- name: Cache pnpm store
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .pnpm-store
|
||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile --prefer-offline
|
||||
|
||||
- name: Typecheck Worker
|
||||
run: pnpm --filter @toolctrl/worker typecheck
|
||||
|
||||
- name: Test Worker
|
||||
run: pnpm --filter @toolctrl/worker exec vitest run
|
||||
|
||||
- name: Build Worker
|
||||
run: pnpm --filter @toolctrl/worker build
|
||||
|
||||
- name: Health Check Worker
|
||||
run: pnpm --filter @toolctrl/worker health
|
||||
env:
|
||||
WORKER_ENABLED: "false"
|
||||
|
||||
database:
|
||||
name: Database Checks
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Prepare pnpm 10.33.0
|
||||
run: corepack prepare pnpm@10.33.0 --activate
|
||||
|
||||
- name: Configure pnpm store
|
||||
run: pnpm config set store-dir .pnpm-store
|
||||
|
||||
- name: Cache pnpm store
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .pnpm-store
|
||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile --prefer-offline
|
||||
|
||||
- name: Generate database client
|
||||
run: pnpm run db:generate
|
||||
env:
|
||||
DATABASE_URL: postgresql://toolctrl:toolctrl@localhost:5432/toolctrl?schema=public
|
||||
|
||||
- name: Verify database migration status
|
||||
if: github.event_name != 'pull_request'
|
||||
run: pnpm run verify:db-migrate-status
|
||||
env:
|
||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||
|
||||
- name: Test database package
|
||||
run: pnpm --filter @toolctrl/db test
|
||||
|
||||
docker-smoke:
|
||||
name: Docker Smoke
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
needs: [quality, api, worker, database]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build web Docker target
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
target: web
|
||||
build-args: APP_REVISION=ci-smoke-revision
|
||||
tags: toolctrl-web:smoke
|
||||
load: true
|
||||
push: false
|
||||
cache-from: type=gha,scope=toolctrl-web-smoke
|
||||
cache-to: type=gha,mode=max,scope=toolctrl-web-smoke
|
||||
|
||||
- name: Validate web Docker artifacts
|
||||
run: docker run --rm --entrypoint /bin/sh toolctrl-web:smoke -lc 'test -f /myapp/apps/web/build/server/index.js && test -d /myapp/apps/web/build/client && test -f /myapp/build/server-runtime/server.js'
|
||||
|
||||
- name: Validate web Docker runtime
|
||||
run: bash scripts/docker-web-runtime-smoke.sh toolctrl-web:smoke
|
||||
|
||||
- name: Build API Docker target
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
target: api
|
||||
build-args: APP_REVISION=ci-smoke-revision
|
||||
tags: toolctrl-api:smoke
|
||||
load: true
|
||||
push: false
|
||||
cache-from: type=gha,scope=toolctrl-api-smoke
|
||||
cache-to: type=gha,mode=max,scope=toolctrl-api-smoke
|
||||
|
||||
- name: Validate API Docker runtime
|
||||
run: bash scripts/docker-api-runtime-smoke.sh toolctrl-api:smoke
|
||||
|
||||
- name: Build worker Docker target
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
target: worker
|
||||
build-args: APP_REVISION=ci-smoke-revision
|
||||
tags: toolctrl-worker:smoke
|
||||
load: true
|
||||
push: false
|
||||
cache-from: type=gha,scope=toolctrl-worker-smoke
|
||||
cache-to: type=gha,mode=max,scope=toolctrl-worker-smoke
|
||||
|
||||
- name: Validate worker Docker runtime
|
||||
run: bash scripts/docker-worker-runtime-smoke.sh toolctrl-worker:smoke
|
||||
|
||||
- name: Validate Docker Compose config
|
||||
run: docker compose config
|
||||
|
||||
resolve-sentry-environment:
|
||||
name: Resolve Sentry environment
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
needs: docker-smoke
|
||||
outputs:
|
||||
environment: ${{ steps.resolve.outputs.environment }}
|
||||
steps:
|
||||
- name: Resolve target environment from ref
|
||||
id: resolve
|
||||
run: |
|
||||
REF="${GITHUB_REF:-}"
|
||||
if [ "$REF" = "refs/heads/main" ]; then
|
||||
echo "environment=production" >> "$GITHUB_OUTPUT"
|
||||
elif [ "$REF" = "refs/heads/staging" ]; then
|
||||
echo "environment=staging" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "environment=preview" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
sentry-release-api:
|
||||
name: Sentry release (api)
|
||||
needs: resolve-sentry-environment
|
||||
uses: ./.github/workflows/_sentry-release.yml
|
||||
with:
|
||||
project: boilerplate-api
|
||||
environment: ${{ needs.resolve-sentry-environment.outputs.environment }}
|
||||
sourcemaps-path: none
|
||||
secrets:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_URL: ${{ secrets.SENTRY_URL }}
|
||||
|
||||
sentry-release-worker:
|
||||
name: Sentry release (worker)
|
||||
needs: resolve-sentry-environment
|
||||
uses: ./.github/workflows/_sentry-release.yml
|
||||
with:
|
||||
project: boilerplate-worker
|
||||
environment: ${{ needs.resolve-sentry-environment.outputs.environment }}
|
||||
sourcemaps-path: none
|
||||
secrets:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_URL: ${{ secrets.SENTRY_URL }}
|
||||
|
||||
sentry-release-web:
|
||||
name: Sentry release (web)
|
||||
needs: resolve-sentry-environment
|
||||
uses: ./.github/workflows/_sentry-release.yml
|
||||
with:
|
||||
project: boilerplate-web
|
||||
environment: ${{ needs.resolve-sentry-environment.outputs.environment }}
|
||||
sourcemaps-path: apps/web/build/client
|
||||
sourcemaps-artifact: web-client-sourcemaps
|
||||
secrets:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_URL: ${{ secrets.SENTRY_URL }}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
name: "CLA Assistant"
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request_target:
|
||||
types: [opened,closed,synchronize]
|
||||
|
||||
jobs:
|
||||
CLAAssistant:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: "CLA Assistant"
|
||||
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I acknowledge that I have read the SaasRock CLA Document and I hereby sign the CLA. I acknowledge that I understand this code is not open source and for commercial purposes. I acknowledge that I am waiving all Intellectual Property Rights; including, but not limited to copyright.') || github.event_name == 'pull_request_target'
|
||||
# Beta Release
|
||||
uses: contributor-assistant/github-action@v2.2.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# the below token should have repo scope and must be manually added by you in the repository's secret
|
||||
PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
||||
with:
|
||||
path-to-signatures: 'signatures/version1/cla.json'
|
||||
path-to-document: 'https://gist.github.com/AlexandroMtzG/27a8c5dcc65735a8f744efff84e6693f'
|
||||
branch: 'main'
|
||||
allowlist: user1,bot*
|
||||
custom-notsigned-prcomment: 'Thank you for your contribution, please kindly read and sign our [Contributor License Agreement](https://gist.github.com/AlexandroMtzG/27a8c5dcc65735a8f744efff84e6693f) by commenting the following:'
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
name: Coverage
|
||||
|
||||
# Runs vitest with --coverage and the per-path threshold gate
|
||||
# (scripts/verify-test-coverage.mjs reads scripts/coverage-targets.json).
|
||||
# Uploads the lightweight gate summary as an artifact and posts a sticky PR
|
||||
# comment with the coverage-summary.json totals.
|
||||
#
|
||||
# Spec: docs/improvement-plan/10-compliance-polish/test-coverage.md
|
||||
# Docs: docs/architecture/test-coverage.md
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "apps/**"
|
||||
- "packages/**"
|
||||
- "scripts/coverage-targets.json"
|
||||
- "scripts/verify-test-coverage.mjs"
|
||||
- "scripts/coverage-summary.mjs"
|
||||
- "vitest.config.ts"
|
||||
- ".github/workflows/coverage.yml"
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
coverage:
|
||||
name: Measure coverage and gate thresholds
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Prepare pnpm 10.33.0
|
||||
run: corepack prepare pnpm@10.33.0 --activate
|
||||
|
||||
- name: Configure pnpm store
|
||||
run: pnpm config set store-dir .pnpm-store
|
||||
|
||||
- name: Cache pnpm store
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .pnpm-store
|
||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile --prefer-offline
|
||||
|
||||
- name: Run vitest with coverage (enforces per-path gates)
|
||||
env:
|
||||
TOOLCTRL_COVERAGE_GATE: "1"
|
||||
run: pnpm run verify:test-coverage
|
||||
|
||||
- name: Upload coverage gate summary
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage-gate-summary-${{ github.run_id }}
|
||||
path: coverage-gate/coverage-summary.json
|
||||
retention-days: 14
|
||||
|
||||
- name: Render PR coverage comment
|
||||
if: github.event_name == 'pull_request'
|
||||
env:
|
||||
COVERAGE_SUMMARY_PATH: coverage-gate/coverage-summary.json
|
||||
run: node scripts/coverage-summary.mjs --pr-comment > coverage-comment.md
|
||||
|
||||
- name: Post sticky PR comment
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: marocchino/sticky-pull-request-comment@v2
|
||||
with:
|
||||
header: coverage-report
|
||||
path: coverage-comment.md
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
name: E2E Readiness
|
||||
|
||||
# Runs the deployed staging/readiness browser suite in fail-closed mode.
|
||||
# Main CI intentionally remains browser-free; this lane proves the authenticated
|
||||
# Playwright/mobile/a11y contract against a live target on schedule or demand.
|
||||
#
|
||||
# Docs: docs/testing/e2e-readiness.md
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
plugin_lifecycle_mutation:
|
||||
description: "Operator-approved disposable run: persist plugin grant/revoke and enable/disable state"
|
||||
required: false
|
||||
default: "false"
|
||||
type: boolean
|
||||
plugin_settings_write:
|
||||
description: "Operator-approved disposable run: persist plugin settings iframe writes"
|
||||
required: false
|
||||
default: "false"
|
||||
type: boolean
|
||||
workflow_outbox_drain:
|
||||
description: "Operator-approved run: require worker/outbox drain to terminal WorkflowExecution status"
|
||||
required: false
|
||||
default: "false"
|
||||
type: boolean
|
||||
workflow_webhook_run:
|
||||
description: "Operator-approved run: execute the webhook workflow against workflow_webhook_target_url"
|
||||
required: false
|
||||
default: "false"
|
||||
type: boolean
|
||||
workflow_webhook_target_url:
|
||||
description: "Operator-owned disposable webhook target URL, required when workflow_webhook_run is true"
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
subscription_success:
|
||||
description: "Operator-approved run: require Stripe checkout success return smoke"
|
||||
required: false
|
||||
default: "false"
|
||||
type: boolean
|
||||
subscription_success_path:
|
||||
description: "Externally acquired Stripe checkout success return path, required when subscription_success is true"
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
schedule:
|
||||
# 04:00 UTC nightly, after deploy and Lighthouse windows have settled.
|
||||
- cron: "0 4 * * *"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
E2E_BASE_URL: https://boiler.saas-ctrl.com
|
||||
E2E_A11Y_STRICT: "1"
|
||||
E2E_BROWSER_MATRIX: "1"
|
||||
E2E_ALLOW_WORKFLOW_RUN: "1"
|
||||
REQUIRE_E2E_A11Y: "1"
|
||||
REQUIRE_AUTH_E2E: "1"
|
||||
REQUIRE_E2E_READINESS: "1"
|
||||
E2E_REGISTRATION_EXPECTS_EMAIL_VERIFICATION: "0"
|
||||
E2E_TENANT_ACCOUNT_NAME: E2E Readiness
|
||||
E2E_TENANT_ACCOUNT_SLUG: e2e-readiness
|
||||
E2E_TENANT_DASHBOARD_PATH: /e2e-readiness/dashboard
|
||||
E2E_TENANT_SETTINGS_ACCOUNT_PATH: /e2e-readiness/settings/account
|
||||
E2E_TENANT_SETTINGS_PROFILE_PATH: /e2e-readiness/settings/profile
|
||||
E2E_TENANT_SUBSCRIPTION_PATH: /e2e-readiness/settings/subscription
|
||||
E2E_TENANT_MEMBERS_PATH: /e2e-readiness/settings/members
|
||||
E2E_TENANT_ROLES_AND_PERMISSIONS_PATH: /e2e-readiness/settings/roles-and-permissions
|
||||
E2E_TENANT_NOTIFICATIONS_PATH: /e2e-readiness/settings/notifications
|
||||
E2E_TENANT_LOGS_PATH: /e2e-readiness/settings/logs
|
||||
E2E_TENANT_LOG_EVENTS_PATH: /e2e-readiness/settings/logs/events
|
||||
E2E_TENANT_AUDIT_EXPORT_PATH: /e2e-readiness/settings/audit-export
|
||||
E2E_TENANT_PRIVACY_PATH: /e2e-readiness/settings/privacy
|
||||
E2E_TENANT_CHATBOT_PATH: /e2e-readiness/settings/chatbot
|
||||
E2E_TENANT_AI_USAGE_PATH: /e2e-readiness/settings/ai-usage
|
||||
E2E_TENANT_API_PATH: /e2e-readiness/settings/api
|
||||
E2E_TENANT_API_KEYS_PATH: /e2e-readiness/settings/api/keys
|
||||
E2E_TENANT_API_DOCS_PATH: /e2e-readiness/settings/api/docs
|
||||
E2E_TENANT_API_LOGS_PATH: /e2e-readiness/settings/api/logs
|
||||
E2E_TENANT_POLICIES_PATH: /e2e-readiness/settings/policies
|
||||
E2E_TENANT_PORTAL_ANALYTICS_PATH: /e2e-readiness/portals/e2e-readiness/analytics
|
||||
E2E_TENANT_PORTAL_SETTINGS_PATH: /e2e-readiness/portals/e2e-readiness/settings
|
||||
E2E_ADMIN_EMAIL_SEQUENCES_PATH: /email-sequences
|
||||
E2E_PLUGIN_SETTINGS_PATH: /e2e-readiness/settings/plugins
|
||||
E2E_WORKFLOW_ENGINE_PATH: /e2e-readiness/workflow-engine/workflows
|
||||
E2E_WORKFLOW_BUILDER_PATH: /e2e-readiness/workflow-engine/workflows/wf_123
|
||||
E2E_WORKFLOW_RUN_API_PATH: /e2e-readiness/workflow-engine/workflows/wf_123/run/api
|
||||
E2E_WORKFLOW_RUN_MANUAL_PATH: /e2e-readiness/workflow-engine/workflows/wf_123/run/manual
|
||||
E2E_WORKFLOW_WEBHOOK_BUILDER_PATH: /e2e-readiness/workflow-engine/workflows/wf_webhook_123
|
||||
E2E_WORKFLOW_WEBHOOK_RUN_MANUAL_PATH: /e2e-readiness/workflow-engine/workflows/wf_webhook_123/run/manual
|
||||
E2E_WORKFLOW_WAITING_BUILDER_PATH: /e2e-readiness/workflow-engine/workflows/wf_waiting_123
|
||||
E2E_WORKFLOW_WAITING_RUN_MANUAL_PATH: /e2e-readiness/workflow-engine/workflows/wf_waiting_123/run/manual
|
||||
E2E_WORKFLOW_WAITING_RUN_API_PATH: /e2e-readiness/workflow-engine/workflows/wf_waiting_123/run/api
|
||||
E2E_WORKFLOW_WAITING_EXECUTIONS_PATH: /e2e-readiness/workflow-engine/workflows/wf_waiting_123/executions
|
||||
E2E_WORKFLOW_WAITING_EXECUTION_ID: wfe_waiting_123
|
||||
PLUGIN_BINARY_BASE_PATH: /tmp/e2e-plugins
|
||||
PLAYWRIGHT_BROWSERS_PATH: .cache/ms-playwright
|
||||
|
||||
jobs:
|
||||
readiness:
|
||||
name: Strict Playwright readiness
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
env:
|
||||
E2E_TENANT_EMAIL: ${{ secrets.E2E_TENANT_EMAIL }}
|
||||
E2E_TENANT_PASSWORD: ${{ secrets.E2E_TENANT_PASSWORD }}
|
||||
E2E_PLUGIN_READONLY_EMAIL: ${{ secrets.E2E_PLUGIN_READONLY_EMAIL }}
|
||||
E2E_PLUGIN_READONLY_PASSWORD: ${{ secrets.E2E_PLUGIN_READONLY_PASSWORD }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Prepare pnpm 10.33.0
|
||||
run: corepack prepare pnpm@10.33.0 --activate
|
||||
|
||||
- name: Configure pnpm store
|
||||
run: pnpm config set store-dir .pnpm-store
|
||||
|
||||
- name: Cache pnpm store
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .pnpm-store
|
||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-
|
||||
|
||||
- name: Cache Playwright browsers
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .cache/ms-playwright
|
||||
key: ${{ runner.os }}-playwright-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-playwright-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile --prefer-offline
|
||||
|
||||
- name: Install Playwright browsers
|
||||
run: pnpm exec playwright install --with-deps chromium webkit
|
||||
|
||||
- name: Apply manual operator E2E opt-ins
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
# Bind dispatch inputs to env vars instead of interpolating ${{ inputs.* }}
|
||||
# straight into the shell, so a crafted value cannot inject commands or
|
||||
# extra lines into $GITHUB_ENV (CI script injection). Free-form URL/path
|
||||
# inputs are additionally allowlist-validated before being written.
|
||||
env:
|
||||
OPT_PLUGIN_LIFECYCLE_MUTATION: ${{ inputs.plugin_lifecycle_mutation }}
|
||||
OPT_PLUGIN_SETTINGS_WRITE: ${{ inputs.plugin_settings_write }}
|
||||
OPT_WORKFLOW_OUTBOX_DRAIN: ${{ inputs.workflow_outbox_drain }}
|
||||
OPT_WORKFLOW_WEBHOOK_RUN: ${{ inputs.workflow_webhook_run }}
|
||||
OPT_WORKFLOW_WEBHOOK_TARGET_URL: ${{ inputs.workflow_webhook_target_url }}
|
||||
OPT_SUBSCRIPTION_SUCCESS: ${{ inputs.subscription_success }}
|
||||
OPT_SUBSCRIPTION_SUCCESS_PATH: ${{ inputs.subscription_success_path }}
|
||||
run: |
|
||||
if [ "$OPT_PLUGIN_LIFECYCLE_MUTATION" = "true" ]; then
|
||||
echo "E2E_ALLOW_PLUGIN_LIFECYCLE_MUTATION=1" >> "$GITHUB_ENV"
|
||||
fi
|
||||
if [ "$OPT_PLUGIN_SETTINGS_WRITE" = "true" ]; then
|
||||
echo "E2E_ALLOW_PLUGIN_SETTINGS_WRITE=1" >> "$GITHUB_ENV"
|
||||
fi
|
||||
if [ "$OPT_WORKFLOW_OUTBOX_DRAIN" = "true" ]; then
|
||||
echo "E2E_REQUIRE_WORKFLOW_OUTBOX_DRAIN=1" >> "$GITHUB_ENV"
|
||||
fi
|
||||
if [ "$OPT_WORKFLOW_WEBHOOK_RUN" = "true" ]; then
|
||||
echo "E2E_ALLOW_WORKFLOW_WEBHOOK_RUN=1" >> "$GITHUB_ENV"
|
||||
case "$OPT_WORKFLOW_WEBHOOK_TARGET_URL" in
|
||||
"") ;;
|
||||
*[!a-zA-Z0-9:/?._~=%\&-]*) echo "Invalid workflow_webhook_target_url" >&2; exit 1 ;;
|
||||
*) printf 'E2E_WORKFLOW_WEBHOOK_TARGET_URL=%s\n' "$OPT_WORKFLOW_WEBHOOK_TARGET_URL" >> "$GITHUB_ENV" ;;
|
||||
esac
|
||||
fi
|
||||
if [ "$OPT_SUBSCRIPTION_SUCCESS" = "true" ]; then
|
||||
echo "E2E_REQUIRE_SUBSCRIPTION_SUCCESS=1" >> "$GITHUB_ENV"
|
||||
case "$OPT_SUBSCRIPTION_SUCCESS_PATH" in
|
||||
"") ;;
|
||||
*[!a-zA-Z0-9:/?._~=%\&-]*) echo "Invalid subscription_success_path" >&2; exit 1 ;;
|
||||
*) printf 'E2E_TENANT_SUBSCRIPTION_SUCCESS_PATH=%s\n' "$OPT_SUBSCRIPTION_SUCCESS_PATH" >> "$GITHUB_ENV" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
- name: Run strict E2E readiness suite
|
||||
run: pnpm run verify:e2e-a11y
|
||||
|
||||
- name: Upload Playwright traces
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: e2e-readiness-results
|
||||
path: test-results/
|
||||
if-no-files-found: ignore
|
||||
retention-days: 14
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
name: Lighthouse CI
|
||||
|
||||
# GitHub mirror of .gitea/workflows/lighthouse.yml. Audits the deployed
|
||||
# staging site (https://boiler.saas-ctrl.com) nightly + on manual dispatch.
|
||||
# See the Gitea variant and docs/architecture/lighthouse.md for the rationale
|
||||
# behind auditing a deployed environment rather than a CI-local server.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# 03:00 UTC nightly — after the day's deploys have settled.
|
||||
- cron: "0 3 * * *"
|
||||
|
||||
jobs:
|
||||
lighthouse:
|
||||
name: Run Lighthouse audits
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Mint Lighthouse session cookie
|
||||
run: node scripts/rotate-lighthouse-session-cookie.mjs
|
||||
env:
|
||||
LIGHTHOUSE_LOGIN_URL: https://boiler.saas-ctrl.com/login
|
||||
LIGHTHOUSE_LOGIN_EMAIL: ${{ secrets.LIGHTHOUSE_LOGIN_EMAIL }}
|
||||
LIGHTHOUSE_LOGIN_PASSWORD: ${{ secrets.LIGHTHOUSE_LOGIN_PASSWORD }}
|
||||
LIGHTHOUSE_SESSION_COOKIE: ${{ secrets.LIGHTHOUSE_SESSION_COOKIE }}
|
||||
LIGHTHOUSE_SESSION_COOKIE_NAME: RJ_session
|
||||
LIGHTHOUSE_COOKIE_DOMAIN: boiler.saas-ctrl.com
|
||||
|
||||
- name: Run Lighthouse-CI against staging
|
||||
# ubuntu-latest ships Chrome pre-installed; no browser setup needed.
|
||||
run: npx --yes @lhci/cli@0.13.x autorun --config=./.lighthouserc.staging.json
|
||||
|
||||
- name: Upload Lighthouse artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: lighthouse-ci-results
|
||||
path: .lighthouseci/
|
||||
retention-days: 14
|
||||
@@ -0,0 +1,83 @@
|
||||
name: SDK Codegen Drift
|
||||
|
||||
# Re-runs the @toolctrl/api-client-ts generation pipeline on PRs that touch
|
||||
# the upstream OpenAPI surface (apps/api/src/openapi.ts) or the SDK package
|
||||
# itself, then verifies the committed src/generated/ tree is in sync with
|
||||
# the captured openapi.json.
|
||||
#
|
||||
# Drift is a hard failure: regenerating + committing is part of the PR.
|
||||
#
|
||||
# Spec: docs/improvement-plan/08-mobile-cli-storybook-sdk/ts-sdk.md
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "apps/api/src/openapi.ts"
|
||||
- "apps/api/src/openapi.test.ts"
|
||||
- "packages/api-client-ts/**"
|
||||
- "packages/api-types/**"
|
||||
- ".github/workflows/sdk-codegen.yml"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
drift-check:
|
||||
name: Verify SDK codegen is in sync with OpenAPI spec
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout PR head
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Prepare pnpm 10.33.0
|
||||
run: corepack prepare pnpm@10.33.0 --activate
|
||||
|
||||
- name: Configure pnpm store
|
||||
run: pnpm config set store-dir .pnpm-store
|
||||
|
||||
- name: Cache pnpm store
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .pnpm-store
|
||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile --prefer-offline
|
||||
|
||||
- name: Regenerate OpenAPI capture
|
||||
run: pnpm --filter @toolctrl/api-client-ts generate-spec
|
||||
|
||||
- name: Regenerate typed client (Hey-API)
|
||||
run: pnpm --filter @toolctrl/api-client-ts generate
|
||||
|
||||
- name: Fail if generated code drifts from committed state
|
||||
run: |
|
||||
if ! git diff --exit-code --stat \
|
||||
packages/api-client-ts/openapi.json \
|
||||
packages/api-client-ts/src/generated; then
|
||||
echo ""
|
||||
echo "SDK codegen drift detected. Run:"
|
||||
echo ""
|
||||
echo " pnpm --filter @toolctrl/api-client-ts generate:all"
|
||||
echo ""
|
||||
echo "then commit packages/api-client-ts/openapi.json and packages/api-client-ts/src/generated/."
|
||||
exit 1
|
||||
fi
|
||||
echo "SDK codegen is in sync with the upstream OpenAPI spec."
|
||||
|
||||
- name: Typecheck SDK packages
|
||||
run: |
|
||||
pnpm --filter @toolctrl/api-client-ts typecheck
|
||||
pnpm --filter @toolctrl/api-types typecheck
|
||||
|
||||
- name: Run SDK tests
|
||||
run: |
|
||||
pnpm --filter @toolctrl/api-client-ts test
|
||||
pnpm --filter @toolctrl/api-types test
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
name: Storybook
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "apps/storybook/**"
|
||||
- "packages/ui/src/stories/**"
|
||||
- "packages/ui-app/src/stories/**"
|
||||
- "packages/ui-subframe/src/stories/**"
|
||||
- "packages/ui-mock/src/stories/**"
|
||||
- ".github/workflows/storybook.yml"
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "apps/storybook/**"
|
||||
- "packages/ui/src/stories/**"
|
||||
- "packages/ui-app/src/stories/**"
|
||||
- "packages/ui-subframe/src/stories/**"
|
||||
- "packages/ui-mock/src/stories/**"
|
||||
- ".github/workflows/storybook.yml"
|
||||
|
||||
env:
|
||||
PLAYWRIGHT_BROWSERS_PATH: .cache/ms-playwright
|
||||
|
||||
jobs:
|
||||
build-storybook:
|
||||
name: Build static Storybook
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Prepare pnpm 10.33.0
|
||||
run: corepack prepare pnpm@10.33.0 --activate
|
||||
|
||||
- name: Configure pnpm store
|
||||
run: pnpm config set store-dir .pnpm-store
|
||||
|
||||
- name: Cache pnpm store
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .pnpm-store
|
||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build Storybook static site
|
||||
run: pnpm --filter @toolctrl/storybook build-storybook
|
||||
|
||||
- name: Upload static Storybook artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: storybook-static
|
||||
path: apps/storybook/storybook-static
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
|
||||
test-storybook:
|
||||
name: Storybook test-runner (a11y gate)
|
||||
needs: build-storybook
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Prepare pnpm 10.33.0
|
||||
run: corepack prepare pnpm@10.33.0 --activate
|
||||
|
||||
- name: Configure pnpm store
|
||||
run: pnpm config set store-dir .pnpm-store
|
||||
|
||||
- name: Cache pnpm store
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .pnpm-store
|
||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Download built Storybook
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: storybook-static
|
||||
path: apps/storybook/storybook-static
|
||||
|
||||
- name: Cache Playwright browsers
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .cache/ms-playwright
|
||||
key: ${{ runner.os }}-playwright-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-playwright-
|
||||
|
||||
- name: Install Playwright Chromium
|
||||
run: pnpm --filter @toolctrl/storybook exec playwright install --with-deps chromium
|
||||
|
||||
- name: Serve static Storybook and run test-runner with a11y gate
|
||||
run: |
|
||||
set -euo pipefail
|
||||
pnpm --filter @toolctrl/storybook exec http-server storybook-static --port 6006 --silent &
|
||||
SERVER_PID=$!
|
||||
trap "kill $SERVER_PID 2>/dev/null || true" EXIT
|
||||
pnpm --filter @toolctrl/storybook exec wait-on http://127.0.0.1:6006 --timeout 60000
|
||||
TARGET_URL=http://127.0.0.1:6006 pnpm --filter @toolctrl/storybook test:storybook
|
||||
|
||||
visual-regression:
|
||||
name: Storybook visual-regression (Lost-Pixel)
|
||||
needs: build-storybook
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Check for committed Lost-Pixel baseline
|
||||
id: baseline
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if ls apps/storybook/.lostpixel/baseline/*.png >/dev/null 2>&1; then
|
||||
echo "present=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "present=false" >> "$GITHUB_OUTPUT"
|
||||
echo "::notice::No Lost-Pixel baseline committed yet — skipping visual-regression. Run 'pnpm --filter @toolctrl/storybook visual-regression:update' and commit apps/storybook/.lostpixel/baseline/*.png to enable this gate."
|
||||
fi
|
||||
|
||||
- name: Enable Corepack
|
||||
if: steps.baseline.outputs.present == 'true'
|
||||
run: corepack enable
|
||||
|
||||
- name: Prepare pnpm 10.33.0
|
||||
if: steps.baseline.outputs.present == 'true'
|
||||
run: corepack prepare pnpm@10.33.0 --activate
|
||||
|
||||
- name: Configure pnpm store
|
||||
if: steps.baseline.outputs.present == 'true'
|
||||
run: pnpm config set store-dir .pnpm-store
|
||||
|
||||
- name: Cache pnpm store
|
||||
if: steps.baseline.outputs.present == 'true'
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .pnpm-store
|
||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.baseline.outputs.present == 'true'
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Download built Storybook
|
||||
if: steps.baseline.outputs.present == 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: storybook-static
|
||||
path: apps/storybook/storybook-static
|
||||
|
||||
- name: Cache Playwright browsers
|
||||
if: steps.baseline.outputs.present == 'true'
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .cache/ms-playwright
|
||||
key: ${{ runner.os }}-playwright-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-playwright-
|
||||
|
||||
- name: Install Playwright Chromium
|
||||
if: steps.baseline.outputs.present == 'true'
|
||||
run: pnpm --filter @toolctrl/storybook exec playwright install --with-deps chromium
|
||||
|
||||
- name: Run Lost-Pixel visual-regression diff
|
||||
if: steps.baseline.outputs.present == 'true'
|
||||
run: pnpm --filter @toolctrl/storybook visual-regression
|
||||
|
||||
- name: Upload Lost-Pixel diff images
|
||||
if: steps.baseline.outputs.present == 'true' && failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: lost-pixel-diff
|
||||
path: apps/storybook/.lostpixel/difference
|
||||
if-no-files-found: ignore
|
||||
retention-days: 14
|
||||
Reference in new issue
Block a user