Track HiveGrid Cloud planning reference

This commit is contained in:
david committed 2026-06-26 20:48:01 +02:00
1 parent b3d5ea6764
commit e341c5938a
5784 files changed
+1104929 -3

No files matched your search

+82
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+83
View File
@@ -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
View File
@@ -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