From 3c53282cb48aecf0ed03737741616da5911d1add Mon Sep 17 00:00:00 2001 From: Willie Chalmers III Date: Fri, 2 Oct 2026 10:55:59 -0700 Subject: [PATCH 1/2] feat: stage main builds and promote reviewed releases to production Merging to main no longer changes the public website. Each main build, including the scheduled calendar refresh, is validated and saved as a release artifact that records its commit, Actions run ID, and the SHA-256 hash of every file. That release is deployed to preview.lasvegasfortransit.org, which sits behind Cloudflare Access. The new "Promote website release" workflow publishes a release only when a maintainer dispatches it with the ID of a successful main "Deploy staging" run. It rejects pull request, failed, and foreign runs, verifies every saved file, uploads the same compiled Worker and assets with production bindings without rebuilding, runs the browser contract checks, and then activates that exact Worker version. Each deployment serves /lvbt-release.json so staging and production can be checked against the selected release. Preview responses carry noindex and private, no-store headers, and analytics stay off on preview hostnames. Automated checks send the Access service token only to the preview origin and never follow a redirect with it. Pull requests no longer get unprotected Cloudflare Pages previews; same-repository pull requests keep their protected Worker previews. Staging needs CF_ACCESS_CLIENT_ID and CF_ACCESS_CLIENT_SECRET in the worker-preview environment and an Access policy on the lvbt-website-preview Worker before its custom domain is attached. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/cron-rebuild.yml | 8 +- .github/workflows/deploy-preview.yml | 193 --------------- .github/workflows/deploy-production.yml | 114 ++++++--- .github/workflows/deploy-worker-candidate.yml | 182 +++++++-------- .github/workflows/deploy-worker-preview.yml | 23 +- AGENTS.md | 16 +- README.md | 33 ++- apps/site/eslint-suppressions.json | 8 +- apps/site/functions/_middleware.ts | 10 + apps/site/package.json | 4 +- .../scripts/bootstrap/lib/process-runtime.ts | 7 +- apps/site/scripts/bootstrap/phases/deploy.ts | 5 +- apps/site/scripts/deploy/access-auth.ts | 47 ++++ apps/site/scripts/deploy/access-browser.ts | 41 ++++ apps/site/scripts/deploy/release-artifact.ts | 123 ++++++++++ apps/site/scripts/deploy/release-smoke.ts | 71 ++++++ apps/site/scripts/deploy/release-source.ts | 31 +++ apps/site/scripts/deploy/worker-release.ts | 137 +++++++++++ apps/site/tests/e2e/a11y-forms.spec.ts | 2 +- apps/site/tests/e2e/a11y-keyboard.spec.ts | 2 +- apps/site/tests/e2e/a11y-landmarks.spec.ts | 2 +- apps/site/tests/e2e/a11y-patterns.spec.ts | 2 +- .../site/tests/e2e/a11y-pseudo-locale.spec.ts | 2 +- apps/site/tests/e2e/a11y.spec.ts | 2 +- apps/site/tests/e2e/body-links.spec.ts | 32 +-- apps/site/tests/e2e/color-contracts.spec.ts | 2 +- apps/site/tests/e2e/event-metadata.spec.ts | 2 +- apps/site/tests/e2e/footer-colophon.spec.ts | 9 +- apps/site/tests/e2e/header-current.spec.ts | 2 +- apps/site/tests/e2e/not-found-search.spec.ts | 2 +- apps/site/tests/e2e/perf-memory.spec.ts | 2 +- apps/site/tests/e2e/print-layout.spec.ts | 2 +- apps/site/tests/e2e/screenshots.spec.ts | 2 +- .../tests/e2e/typography-contracts.spec.ts | 2 +- apps/site/tests/release-artifact.test.ts | 79 +++++++ apps/site/tests/release-source.test.ts | 45 ++++ apps/site/tests/staging-environment.test.ts | 73 ++++++ .../tests/worker-candidate-workflow.test.ts | 35 ++- apps/site/worker-configuration.d.ts | 10 +- apps/site/wrangler.jsonc | 9 + docs/explanation/events-pipeline.md | 21 +- docs/guides/test-the-workers-candidate.md | 170 +++++--------- docs/reference/deployment-pipeline.md | 220 +++++++++--------- .../specs/2026-10-01-staging-promotion.md | 33 +++ 44 files changed, 1159 insertions(+), 658 deletions(-) delete mode 100644 .github/workflows/deploy-preview.yml create mode 100644 apps/site/functions/_middleware.ts create mode 100644 apps/site/scripts/deploy/access-auth.ts create mode 100644 apps/site/scripts/deploy/access-browser.ts create mode 100644 apps/site/scripts/deploy/release-artifact.ts create mode 100644 apps/site/scripts/deploy/release-smoke.ts create mode 100644 apps/site/scripts/deploy/release-source.ts create mode 100644 apps/site/scripts/deploy/worker-release.ts create mode 100644 apps/site/tests/release-artifact.test.ts create mode 100644 apps/site/tests/release-source.test.ts create mode 100644 apps/site/tests/staging-environment.test.ts create mode 100644 docs/superpowers/specs/2026-10-01-staging-promotion.md diff --git a/.github/workflows/cron-rebuild.yml b/.github/workflows/cron-rebuild.yml index 59ff1afc..ae1821ea 100644 --- a/.github/workflows/cron-rebuild.yml +++ b/.github/workflows/cron-rebuild.yml @@ -1,7 +1,7 @@ name: cron-rebuild -# Periodic redeploy so the events page picks up Google Calendar changes -# without anyone having to push. Triggers `Deploy production` via +# Periodic staging refresh so the events page picks up Google Calendar changes +# without anyone having to push. Triggers `Deploy staging` via # workflow_dispatch — that workflow re-fetches the public LVBT calendar # during its build. # @@ -16,7 +16,7 @@ on: schedule: - cron: '0 2,14 * * *' # Manual button in the Actions UI for ad-hoc rebuilds (e.g. after a - # same-day calendar correction that can't wait two hours). + # same-day calendar correction that cannot wait for the next scheduled build). workflow_dispatch: permissions: @@ -28,7 +28,7 @@ jobs: dispatch: runs-on: ubuntu-latest steps: - - name: Dispatch Deploy production + - name: Dispatch Deploy staging env: GH_TOKEN: ${{ github.token }} run: gh workflow run deploy-production.yml --ref main --repo ${{ github.repository }} diff --git a/.github/workflows/deploy-preview.yml b/.github/workflows/deploy-preview.yml deleted file mode 100644 index 08dd74a9..00000000 --- a/.github/workflows/deploy-preview.yml +++ /dev/null @@ -1,193 +0,0 @@ -name: Deploy preview - -on: - pull_request: - branches: [main] - -permissions: - contents: read - deployments: write - packages: read - pull-requests: write - -# Newer pushes to the same PR cancel an in-flight preview deploy. -concurrency: - group: deploy-preview-${{ github.event.pull_request.number }} - cancel-in-progress: true - -jobs: - # Same-repo PRs only — fork PRs don't get the prod token. Fork contributors - # land via review + main deploy. (Never use `pull_request_target` to work - # around this; it executes the PR's workflow with full secrets — exactly - # the foot-gun we're avoiding.) - guard: - name: Same-repo guard - runs-on: ubuntu-latest - if: >- - github.event.pull_request.head.repo.full_name == github.repository && - vars.CLOUDFLARE_ACCOUNT_ID != '' - outputs: - run: ${{ steps.set.outputs.run }} - steps: - - id: set - env: - CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - run: | - if [ -n "$CLOUDFLARE_API_TOKEN" ]; then - echo "run=true" >> "$GITHUB_OUTPUT" - else - echo "run=false" >> "$GITHUB_OUTPUT" - echo "::notice::Pages previews are not configured for this repository." - fi - - build: - name: Build - needs: guard - if: needs.guard.outputs.run == 'true' - runs-on: ubuntu-latest - timeout-minutes: 10 - # Deployment config baked into the static HTML — see apps/site/src/lib/site.ts. - # Job env propagates into the build-site composite step. - env: - PUBLIC_LVBT_INSTAGRAM: ${{ vars.PUBLIC_LVBT_INSTAGRAM }} - PUBLIC_LVBT_LINKEDIN: ${{ vars.PUBLIC_LVBT_LINKEDIN }} - PUBLIC_LVBT_BLUESKY: ${{ vars.PUBLIC_LVBT_BLUESKY }} - PUBLIC_LVBT_DISCORD: ${{ vars.PUBLIC_LVBT_DISCORD }} - PUBLIC_LVBT_DONATE_URL: ${{ vars.PUBLIC_LVBT_DONATE_URL }} - PUBLIC_LVBT_MEMBERSHIP_FORM_URL: ${{ vars.PUBLIC_LVBT_MEMBERSHIP_FORM_URL }} - PUBLIC_LVBT_NEWSLETTER_URL: ${{ vars.PUBLIC_LVBT_NEWSLETTER_URL }} - PUBLIC_LVBT_NEWSLETTER_FEED_URL: ${{ vars.PUBLIC_LVBT_NEWSLETTER_FEED_URL }} - # Build the preview-only pattern gallery and prototypes, so they are checked and reviewable. - PUBLIC_LVBT_PREVIEW_PAGES: '1' - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: Setup Node + pnpm - uses: ./.github/actions/setup-node-pnpm - - - name: Build site - uses: ./.github/actions/build-site - - deploy: - name: Deploy preview - needs: build - runs-on: ubuntu-latest - timeout-minutes: 10 - environment: - name: preview - url: ${{ steps.deploy.outputs.url }} - env: - CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }} - outputs: - url: ${{ steps.deploy.outputs.url }} - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: Setup Node + pnpm - uses: ./.github/actions/setup-node-pnpm - - - name: Deploy - id: deploy - uses: ./.github/actions/deploy-cloudflare-pages - with: - # Cloudflare uses the branch name to namespace preview URLs. github.head_ref - # is the source branch (only set on pull_request events); fine for our - # same-repo gate. - branch: ${{ github.head_ref }} - - headers-check: - name: Verify response headers - needs: deploy - if: needs.deploy.outputs.url != '' - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - name: Wait for Cloudflare propagation, then assert headers - env: - PREVIEW_URL: ${{ needs.deploy.outputs.url }} - run: | - set -euo pipefail - # Cloudflare's edge can lag a few seconds behind wrangler returning; - # retry up to 6×5s before failing so transient propagation doesn't - # mark good deploys as bad. - headers="" - for attempt in 1 2 3 4 5 6; do - if headers=$(curl -sI --max-time 10 "$PREVIEW_URL" 2>/dev/null) && [ -n "$headers" ]; then - break - fi - echo "attempt $attempt: no response yet, sleeping 5s..." - sleep 5 - done - if [ -z "$headers" ]; then - echo "::error::Could not fetch headers from $PREVIEW_URL" - exit 1 - fi - echo "$headers" - missing=() - for h in \ - 'Content-Security-Policy' \ - 'Strict-Transport-Security' \ - 'X-Content-Type-Options' \ - 'Referrer-Policy' \ - 'Permissions-Policy'; do - if ! printf '%s' "$headers" | grep -qi "^$h:"; then - missing+=("$h") - fi - done - if [ "${#missing[@]}" -gt 0 ]; then - echo "::error::Missing required response headers: ${missing[*]}" - echo "::error::Check apps/site/public/_headers — Cloudflare Pages may not be applying it." - exit 1 - fi - - comment: - name: Comment preview URL on PR - needs: deploy - if: needs.deploy.outputs.url != '' - runs-on: ubuntu-latest - timeout-minutes: 2 - steps: - - name: Upsert PR comment - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - PREVIEW_URL: ${{ needs.deploy.outputs.url }} - with: - script: | - const url = process.env.PREVIEW_URL; - const body = [ - '🚀 **Preview deployed**', - '', - `→ ${url}`, - '', - `_Commit: \`${context.sha.slice(0, 7)}\`_`, - ].join('\n'); - const marker = ''; - const { data: comments } = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - }); - const existing = comments.find((c) => c.body && c.body.includes(marker)); - const fullBody = `${marker}\n${body}`; - if (existing) { - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existing.id, - body: fullBody, - }); - } else { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body: fullBody, - }); - } diff --git a/.github/workflows/deploy-production.yml b/.github/workflows/deploy-production.yml index fb2c920b..7b56aa7b 100644 --- a/.github/workflows/deploy-production.yml +++ b/.github/workflows/deploy-production.yml @@ -1,4 +1,4 @@ -name: Deploy production +name: Deploy staging on: push: @@ -7,16 +7,14 @@ on: permissions: contents: read + actions: read deployments: write packages: read -# Never cancel a prod deploy in flight — let it finish, then queue the next. concurrency: - group: deploy-prod + group: website-staging cancel-in-progress: false -# The site lives in apps/site; every run step works from there unless it -# says otherwise. Artifact and cache paths stay relative to the checkout. defaults: run: working-directory: apps/site @@ -44,6 +42,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false + lfs: true - name: Setup Node + pnpm uses: ./.github/actions/setup-node-pnpm @@ -51,47 +50,92 @@ jobs: - name: Build site uses: ./.github/actions/build-site - # The pattern gallery, prototypes and the en-XA test language exist only - # on previews and the audit. A production build with any of them must - # never deploy. - - name: Check preview-only pages are absent - run: | - if [ -e dist/patterns ] || [ -e dist/prototypes ]; then - echo "::error::dist/patterns or dist/prototypes is in the production build." - exit 1 - fi - # The en-XA long-text language is for the audit only. - if grep -rlq 'lang="en-XA"' dist --include='*.html'; then - echo "::error::The production build is in the en-XA test language. Unset LVBT_PSEUDO_LOCALE." - exit 1 - fi + - name: Validate release + working-directory: . + run: pnpm check + + - name: Package immutable release + env: + RELEASE_ID: ${{ github.run_id }} + run: >- + pnpm worker:release package --directory "$RUNNER_TEMP/website-release" --commit + "$GITHUB_SHA" --release-id "$RELEASE_ID" - deploy: - name: Deploy to Cloudflare Pages + - name: Save reviewed release + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: website-release-${{ github.run_id }} + path: ${{ runner.temp }}/website-release/ + include-hidden-files: true + if-no-files-found: error + retention-days: 90 + + staging: + name: Verify and update staging needs: build - if: vars.LVBT_WORKERS_PRODUCTION_ENABLED != 'true' + if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest - timeout-minutes: 10 - # Scopes secrets to the production environment + surfaces a deployment - # record in the GitHub UI. Add required reviewers via repo settings if - # you want a manual approval gate later. + timeout-minutes: 20 environment: - name: production - url: ${{ steps.deploy.outputs.url }} + name: worker-preview + url: https://preview.lasvegasfortransit.org env: - CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }} + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_WORKERS_API_TOKEN }} + CF_ACCESS_CLIENT_ID: ${{ secrets.CF_ACCESS_CLIENT_ID }} + CF_ACCESS_CLIENT_SECRET: ${{ secrets.CF_ACCESS_CLIENT_SECRET }} + RELEASE_ID: ${{ github.run_id }} steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - + lfs: true - name: Setup Node + pnpm uses: ./.github/actions/setup-node-pnpm - - - name: Deploy - id: deploy - uses: ./.github/actions/deploy-cloudflare-pages + - name: Download release + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - branch: main + name: website-release-${{ github.run_id }} + path: ${{ runner.temp }}/website-release + - name: Install Chromium + run: pnpm exec playwright install --with-deps chromium + - name: Verify saved release + run: >- + pnpm worker:release verify --directory "$RUNNER_TEMP/website-release" --commit + "$GITHUB_SHA" --release-id "$RELEASE_ID" + - name: Upload staging version + id: upload + run: pnpm worker:release upload --directory "$RUNNER_TEMP/website-release" --target preview + - name: Verify protected candidate + env: + PREVIEW_URL: ${{ steps.upload.outputs.url }} + run: >- + pnpm worker:smoke --url "$PREVIEW_URL" --protected --commit "$GITHUB_SHA" --release-id + "$RELEASE_ID" + - name: Browser acceptance + env: + PLAYWRIGHT_BASE_URL: ${{ steps.upload.outputs.url }} + run: | + cp -R "$RUNNER_TEMP/website-release/dist" ./dist + pnpm worker:test:browser --project=ui-contracts + - name: Activate staging + env: + VERSION_ID: ${{ steps.upload.outputs.version }} + run: pnpm worker:release activate --target preview --version "$VERSION_ID" + - name: Verify permanent preview domain + run: >- + pnpm worker:smoke --url https://preview.lasvegasfortransit.org --protected --commit + "$GITHUB_SHA" --release-id "$RELEASE_ID" + - name: Record release + env: + VERSION_ID: ${{ steps.upload.outputs.version }} + run: | + { + echo '### Staging release verified' + echo "Release: $RELEASE_ID" + echo "Commit: $GITHUB_SHA" + echo "Staging Worker version: $VERSION_ID" + echo 'Review: https://preview.lasvegasfortransit.org' + echo "Promote by selecting Actions run $GITHUB_RUN_ID in Promote website release." + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/deploy-worker-candidate.yml b/.github/workflows/deploy-worker-candidate.yml index 55dc0e5b..012910cf 100644 --- a/.github/workflows/deploy-worker-candidate.yml +++ b/.github/workflows/deploy-worker-candidate.yml @@ -1,139 +1,129 @@ -name: Deploy Worker candidate +name: Promote website release on: - workflow_run: - workflows: [Deploy production] - types: [completed] workflow_dispatch: + inputs: + run_id: + description: Successful main Deploy staging Actions run ID to promote + type: string + required: true permissions: contents: read + actions: read deployments: write packages: read concurrency: - group: deploy-worker-candidate + group: website-production cancel-in-progress: false -# The site lives in apps/site; every run step works from there unless it -# says otherwise. defaults: run: working-directory: apps/site jobs: - candidate: - name: Upload and verify candidate - if: >- - vars.CLOUDFLARE_WORKERS_CANDIDATE_ENABLED == 'true' && ((github.event_name == - 'workflow_dispatch' && github.ref == 'refs/heads/main') || (github.event_name == - 'workflow_run' && github.event.workflow_run.conclusion == 'success' && - github.event.workflow_run.head_branch == 'main')) + source: + name: Validate selected staging release + if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 10 + outputs: + commit: ${{ steps.source.outputs.commit }} + release-id: ${{ steps.source.outputs.release-id }} + steps: + - name: Checkout trusted promotion tools + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Setup Node + pnpm + uses: ./.github/actions/setup-node-pnpm + - name: Fetch selected Actions run + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + SOURCE_RUN_ID: ${{ inputs.run_id }} + with: + script: | + const id = process.env.SOURCE_RUN_ID; + if (!/^[1-9][0-9]*$/.test(id)) throw new Error('Select an Actions run ID.'); + const { data } = await github.rest.actions.getWorkflowRun({ ...context.repo, run_id: Number(id) }); + require('node:fs').writeFileSync(`${process.env.RUNNER_TEMP}/source-run.json`, JSON.stringify(data)); + - name: Validate provenance + id: source + env: + SOURCE_RUN_ID: ${{ inputs.run_id }} + run: >- + pnpm worker:release source --run-file "$RUNNER_TEMP/source-run.json" --repository + "$GITHUB_REPOSITORY" --run-id "$SOURCE_RUN_ID" + + promote: + name: Verify and publish selected release + needs: source + runs-on: ubuntu-latest + timeout-minutes: 25 + # Existing production credential environment; dispatch is the explicit publish action. environment: name: worker-candidate - url: ${{ steps.upload.outputs.url }} + url: https://lasvegasfortransit.org env: - CANDIDATE_SHA: ${{ github.event.workflow_run.head_sha || github.sha }} CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }} CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_WORKERS_API_TOKEN }} - LVBT_BEEHIIV_API_KEY: ${{ secrets.LVBT_BEEHIIV_API_KEY }} - LVBT_BEEHIIV_PUBLICATION_ID: ${{ secrets.LVBT_BEEHIIV_PUBLICATION_ID }} - LVBT_MEMBERSHIP_INTAKE_SECRET: ${{ secrets.LVBT_MEMBERSHIP_INTAKE_SECRET }} - LVBT_NOTION_API_KEY: ${{ secrets.LVBT_NOTION_API_KEY }} - LVBT_NOTION_DATA_SOURCE_ID: ${{ secrets.LVBT_NOTION_DATA_SOURCE_ID }} - LVBT_TRANSIT_NEWS_INTAKE_SECRET: ${{ secrets.LVBT_TRANSIT_NEWS_INTAKE_SECRET }} - PUBLIC_LVBT_INSTAGRAM: ${{ vars.PUBLIC_LVBT_INSTAGRAM }} - PUBLIC_LVBT_LINKEDIN: ${{ vars.PUBLIC_LVBT_LINKEDIN }} - PUBLIC_LVBT_BLUESKY: ${{ vars.PUBLIC_LVBT_BLUESKY }} - PUBLIC_LVBT_DISCORD: ${{ vars.PUBLIC_LVBT_DISCORD }} - PUBLIC_LVBT_DONATE_URL: ${{ vars.PUBLIC_LVBT_DONATE_URL }} - PUBLIC_LVBT_MEMBERSHIP_FORM_URL: ${{ vars.PUBLIC_LVBT_MEMBERSHIP_FORM_URL }} - PUBLIC_LVBT_NEWSLETTER_URL: ${{ vars.PUBLIC_LVBT_NEWSLETTER_URL }} - PUBLIC_LVBT_NEWSLETTER_FEED_URL: ${{ vars.PUBLIC_LVBT_NEWSLETTER_FEED_URL }} - PUBLIC_LVBT_CWA_TOKEN: ${{ vars.PUBLIC_LVBT_CWA_TOKEN }} - LVBT_REQUIRE_ANALYTICS: '1' + RELEASE_ID: ${{ needs.source.outputs.release-id }} + RELEASE_COMMIT: ${{ needs.source.outputs.commit }} steps: - - name: Checkout deployed commit + - name: Checkout release verification tools uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - ref: ${{ env.CANDIDATE_SHA }} - lfs: true + ref: ${{ needs.source.outputs.commit }} persist-credentials: false - + lfs: true - name: Setup Node + pnpm uses: ./.github/actions/setup-node-pnpm - + - name: Download selected immutable release + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + github-token: ${{ github.token }} + run-id: ${{ inputs.run_id }} + name: website-release-${{ needs.source.outputs.release-id }} + path: ${{ runner.temp }}/website-release + - name: Verify artifact identity and files + run: >- + pnpm worker:release verify --directory "$RUNNER_TEMP/website-release" --commit + "$RELEASE_COMMIT" --release-id "$RELEASE_ID" - name: Install Chromium run: pnpm exec playwright install --with-deps chromium - - # pnpm check is the repository's own check, so it runs from the root. - - name: Build and validate - working-directory: . - run: pnpm check - - - name: Upload Worker version + - name: Upload production candidate without rebuilding id: upload - run: >- - pnpm worker:preview --alias candidate --message "Candidate $CANDIDATE_SHA" --secrets - - - name: Wait for candidate - env: - PREVIEW_URL: ${{ steps.upload.outputs.url }} - run: | - set -euo pipefail - for _ in 1 2 3 4 5 6; do - if curl --fail --silent --show-error --max-time 15 "$PREVIEW_URL" > /dev/null; then - exit 0 - fi - sleep 5 - done - exit 1 - - - name: Compare with Pages - if: vars.LVBT_WORKERS_PRODUCTION_ENABLED != 'true' + run: + pnpm worker:release upload --directory "$RUNNER_TEMP/website-release" --target production + - name: Verify production candidate env: PREVIEW_URL: ${{ steps.upload.outputs.url }} run: >- - pnpm worker:test:live --pages https://lasvegasfortransit.org --worker "$PREVIEW_URL" - + pnpm worker:smoke --url "$PREVIEW_URL" --commit "$RELEASE_COMMIT" --release-id + "$RELEASE_ID" - name: Browser acceptance env: PLAYWRIGHT_BASE_URL: ${{ steps.upload.outputs.url }} - run: pnpm worker:test:browser - - - name: Record candidate + run: | + cp -R "$RUNNER_TEMP/website-release/dist" ./dist + pnpm worker:test:browser --project=ui-contracts + - name: Activate verified production version env: - PREVIEW_URL: ${{ steps.upload.outputs.url }} VERSION_ID: ${{ steps.upload.outputs.version }} - run: | - { - echo '### Worker candidate verified' - echo - echo "- Commit: \`$CANDIDATE_SHA\`" - echo "- Version: \`$VERSION_ID\`" - echo "- Preview: $PREVIEW_URL" - echo '- Pages reference: https://lvbt-website-5zh.pages.dev' - } >> "$GITHUB_STEP_SUMMARY" - - - name: Deploy verified Worker - if: vars.LVBT_WORKERS_PRODUCTION_ENABLED == 'true' + run: pnpm worker:release activate --target production --version "$VERSION_ID" + - name: Verify public deployment + run: >- + pnpm worker:smoke --url https://lasvegasfortransit.org --commit "$RELEASE_COMMIT" + --release-id "$RELEASE_ID" + - name: Record publication env: VERSION_ID: ${{ steps.upload.outputs.version }} run: | - set -euo pipefail - CURRENT_SHA="$(git ls-remote "https://github.com/${{ github.repository }}.git" refs/heads/main | cut -f1)" - if [ "$CURRENT_SHA" != "$CANDIDATE_SHA" ]; then - echo "::error::main moved during candidate verification; refusing to deploy $CANDIDATE_SHA" - exit 1 - fi - pnpm exec wrangler versions deploy "$VERSION_ID" --name lvbt-website --env "" --message "Production $CANDIDATE_SHA" --yes - - - name: Verify production hostname - if: vars.LVBT_WORKERS_PRODUCTION_ENABLED == 'true' - env: - PREVIEW_URL: ${{ steps.upload.outputs.url }} - run: >- - pnpm worker:test:live --pages "$PREVIEW_URL" --worker https://lasvegasfortransit.org - --skip-analytics + { + echo '### Website release published' + echo "Release: $RELEASE_ID" + echo "Commit: $RELEASE_COMMIT" + echo "Production Worker version: $VERSION_ID" + echo 'Public site: https://lasvegasfortransit.org' + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/deploy-worker-preview.yml b/.github/workflows/deploy-worker-preview.yml index ffae58af..15ad73e3 100644 --- a/.github/workflows/deploy-worker-preview.yml +++ b/.github/workflows/deploy-worker-preview.yml @@ -44,6 +44,9 @@ jobs: PUBLIC_LVBT_NEWSLETTER_FEED_URL: ${{ vars.PUBLIC_LVBT_NEWSLETTER_FEED_URL }} PUBLIC_LVBT_CWA_TOKEN: ${{ vars.PUBLIC_LVBT_CWA_TOKEN }} LVBT_REQUIRE_ANALYTICS: '1' + PUBLIC_LVBT_PREVIEW_PAGES: '1' + CF_ACCESS_CLIENT_ID: ${{ secrets.CF_ACCESS_CLIENT_ID }} + CF_ACCESS_CLIENT_SECRET: ${{ secrets.CF_ACCESS_CLIENT_SECRET }} steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -71,28 +74,12 @@ jobs: - name: Verify preview env: PREVIEW_URL: ${{ steps.upload.outputs.url }} - run: | - set -euo pipefail - for _ in 1 2 3 4 5 6; do - if curl --fail --silent --show-error --max-time 15 "$PREVIEW_URL" > /dev/null; then - exit 0 - fi - sleep 5 - done - exit 1 - - - name: Compare with Pages - if: vars.LVBT_WORKERS_PRODUCTION_ENABLED != 'true' - env: - PREVIEW_URL: ${{ steps.upload.outputs.url }} - run: >- - pnpm worker:test:live --pages https://lvbt-website-5zh.pages.dev --worker "$PREVIEW_URL" - --skip-api + run: pnpm worker:smoke --url "$PREVIEW_URL" --protected - name: Browser acceptance env: PLAYWRIGHT_BASE_URL: ${{ steps.upload.outputs.url }} - run: pnpm worker:test:browser + run: pnpm worker:test:browser --project=ui-contracts - name: Upsert pull request comment uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 diff --git a/AGENTS.md b/AGENTS.md index 83b898d5..b6a1a78a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,8 +4,8 @@ Run `pnpm check` after every change. It is the same command CI runs, and a faili command that fixes it (`pnpm check:fix` repairs everything a machine can). This repository is the Astro site for Las Vegans for Better Transit, in `apps/site`, inside a -Turborepo workspace that follows the organization's repository standard. Cloudflare Pages serves -production while the equivalent Workers deployment completes live acceptance. Changes land through +Turborepo workspace that follows the organization's repository standard. Cloudflare Workers serves +production through explicit promotion of a saved, reviewed staging release. Changes land through pull requests with linear history and the current `Validate` check. New to the project (human or agent)? @@ -34,10 +34,10 @@ Every LVBT repository answers to the same commands, run from the repository root `pnpm bootstrap` and `pnpm preflight` run the site's own setup script in `apps/site/scripts/bootstrap/`, because it also sets up the site's environment file, Worker, domains, and secrets; see [`docs/reference/bootstrap.md`](./docs/reference/bootstrap.md). Production -normally deploys from `main` through GitHub Actions, which verifies each Worker version before it -goes live, so `pnpm run deploy` is for recovery only. Commands only the site has, such as -`event:new` or `worker:dev`, live in `apps/site/package.json`: run them with -`pnpm -C apps/site `. +normally deploys through the explicit Promote website release workflow in GitHub Actions, which +verifies a saved staging release before it goes live, so `pnpm run deploy` is for recovery only. +Commands only the site has, such as `event:new` or `worker:dev`, live in `apps/site/package.json`: +run them with `pnpm -C apps/site `. ## Read these first @@ -185,8 +185,8 @@ markers or GitHub-side prose checks. ## Stack quick map - Astro 7 + Tailwind v4 (MDX content collections under `apps/site/src/content/`) -- Cloudflare Pages in production, with an equivalent Cloudflare Worker built and checked on every - change +- Cloudflare Workers in production, with protected staging updated from main and explicit release + promotion - pnpm 11.25.0 and Node 24.20 or newer in the 24.x line, run through Turborepo - Playwright for tests and ad-hoc screenshots - `apps/site/scripts/bootstrap/` is the interactive setup CLI (`pnpm bootstrap`, `pnpm preflight`) diff --git a/README.md b/README.md index 95ee947e..d07ee814 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,7 @@ New to any of these? Each links to its [glossary](./docs/reference/glossary.md) - [Tailwind](./docs/reference/glossary.md#tailwind) CSS v4 (via `@tailwindcss/vite`) - [Public Sans](https://public-sans.digital.gov/) (USWDS font, self-hosted; Latin woff2 vendored from `@fontsource-variable/public-sans`) -- Hosted on [Cloudflare Pages](./docs/reference/glossary.md#cloudflare-pages) — fully portable to - any static host (Netlify, GitHub Pages, S3+CloudFront). +- Hosted on Cloudflare Workers with static assets and server routes. --- @@ -139,9 +138,10 @@ turbo.json # The tasks every package runs, in order ## Deployment -Pushes to `main` deploy to production at `lasvegasfortransit.org` via GitHub Actions; PRs get a -Cloudflare Pages preview URL commented on the PR. Full pipeline (build settings, env vars, rollback, -manual deploys) is documented in +Pushes to `main` update `preview.lasvegasfortransit.org` behind Cloudflare Access. Explicit +promotion publishes the selected saved release at `lasvegasfortransit.org`. Same-repository PRs +receive independent, protected Worker preview URLs. Full pipeline (build settings, env vars, +rollback, manual deploys) is documented in [`docs/reference/deployment-pipeline.md`](./docs/reference/deployment-pipeline.md). If anything breaks in your environment, run `pnpm preflight` first — it usually points at the @@ -153,12 +153,11 @@ Several workflows in [`.github/workflows/`](./.github/workflows/) build on three in [`.github/actions/`](./.github/actions/) (`setup-node-pnpm`, `build-site`, `deploy-cloudflare-pages`). The ones that talk to Cloudflare: -| Workflow | Trigger | What it does | -| ----------------------------- | ------------------------------------- | ------------------------------------------------------------------------------ | -| `deploy-preview.yml` | Same-repo PRs on `main` | Build + `wrangler pages deploy --branch=` + comment URL | -| `deploy-production.yml` | Pushes to `main`, `workflow_dispatch` | Build + `wrangler pages deploy --branch=main` | -| `deploy-worker-preview.yml` | Same-repo PRs, once enabled | Uploads a version of the separate `lvbt-website-preview` Worker for comparison | -| `deploy-worker-candidate.yml` | After a successful production build | Uploads a `main` Worker version for comparison, then cutover once enabled | +| Workflow | Trigger | What it does | +| ------------------------------------------------------- | ---------------------------------------------- | --------------------------------------------------- | +| `deploy-production.yml` (Deploy staging) | Main pushes and manual dispatch | Save a release and verify protected staging | +| `deploy-worker-preview.yml` | Same-repository PR updates | Verify and comment an independent protected preview | +| `deploy-worker-candidate.yml` (Promote website release) | Explicit selection of a successful staging run | Publish the saved release after candidate checks | `ci.yml` (the required `Validate` check: `pnpm check`, a dependency audit, and a secret scan, no deploy), `audit.yml`, `audit-scheduled.yml`, `cron-rebuild.yml`, `seed-baselines.yml` and @@ -168,14 +167,10 @@ each token — is in [`docs/reference/deployment-pipeline.md`](./docs/reference/deployment-pipeline.md) and [`docs/guides/test-the-workers-candidate.md`](./docs/guides/test-the-workers-candidate.md). -**In short:** `deploy-production.yml` and `deploy-preview.yml` both need a repository secret -`CLOUDFLARE_API_TOKEN` (an **Account · Cloudflare Pages · Edit** custom token — Cloudflare has no -ready-made template for Pages alone) and a repository variable `CLOUDFLARE_ACCOUNT_ID`. Both stay at -the repository level, not scoped to an Environment: `deploy-preview.yml`'s fork-safety job, which -declares no environment, reads them too. `deploy-worker-preview.yml` and -`deploy-worker-candidate.yml` each need their own environment-scoped `CLOUDFLARE_WORKERS_API_TOKEN` -(a narrower **Account · Workers Scripts · Edit** token) under the `worker-preview` and -`worker-candidate` GitHub environments. +Workers credentials are scoped to the `worker-preview` and `worker-candidate` GitHub environments. +The preview environment also needs a Cloudflare Access service token. Credentials remain outside +release artifacts; the account ID is a repository variable. See the linked pipeline reference for +exact secret names and review/promotion steps. ## License diff --git a/apps/site/eslint-suppressions.json b/apps/site/eslint-suppressions.json index cc4bc8d7..de6f5dce 100644 --- a/apps/site/eslint-suppressions.json +++ b/apps/site/eslint-suppressions.json @@ -487,21 +487,15 @@ } }, "tests/e2e/body-links.spec.ts": { - "@typescript-eslint/consistent-type-definitions": { - "count": 2 - }, "@typescript-eslint/no-implied-eval": { "count": 1 }, "@typescript-eslint/no-non-null-assertion": { - "count": 9 + "count": 1 }, "@typescript-eslint/no-unnecessary-condition": { "count": 7 }, - "@typescript-eslint/no-unnecessary-type-assertion": { - "count": 1 - }, "@typescript-eslint/no-unsafe-call": { "count": 1 }, diff --git a/apps/site/functions/_middleware.ts b/apps/site/functions/_middleware.ts new file mode 100644 index 00000000..5f7acd96 --- /dev/null +++ b/apps/site/functions/_middleware.ts @@ -0,0 +1,10 @@ +export function deploymentResponse(response: Response, environment: string): Response { + if (environment !== 'preview') return response; + const protectedResponse = new Response(response.body, response); + protectedResponse.headers.set('X-Robots-Tag', 'noindex, nofollow, noarchive'); + protectedResponse.headers.set('Cache-Control', 'private, no-store'); + return protectedResponse; +} + +export const onRequest: PagesFunction> = async (context) => + deploymentResponse(await context.next(), context.env.LVBT_DEPLOYMENT_ENV); diff --git a/apps/site/package.json b/apps/site/package.json index 1479be77..02e66791 100644 --- a/apps/site/package.json +++ b/apps/site/package.json @@ -48,7 +48,9 @@ "worker:preview": "tsx scripts/deploy/worker-preview.ts", "worker:upload": "pnpm build && wrangler versions upload", "worker:deploy": "pnpm build && wrangler deploy", - "worker:dev": "pnpm build && pnpm preview" + "worker:dev": "pnpm build && pnpm preview", + "worker:release": "tsx scripts/deploy/worker-release.ts", + "worker:smoke": "tsx scripts/deploy/release-smoke.ts" }, "dependencies": { "@astrojs/mdx": "catalog:", diff --git a/apps/site/scripts/bootstrap/lib/process-runtime.ts b/apps/site/scripts/bootstrap/lib/process-runtime.ts index c0b4e1a6..0ee01287 100644 --- a/apps/site/scripts/bootstrap/lib/process-runtime.ts +++ b/apps/site/scripts/bootstrap/lib/process-runtime.ts @@ -32,11 +32,8 @@ function resolveShell(): string { const SUBPROCESS_ENV_DENYLIST: ReadonlySet = new Set(['CLOUDFLARE_API_TOKEN']); function subprocessEnv(): NodeJS.ProcessEnv { - const env: NodeJS.ProcessEnv = {}; - for (const [key, value] of Object.entries(process.env)) { - if (SUBPROCESS_ENV_DENYLIST.has(key)) continue; - env[key] = value; - } + const env = { ...process.env }; + for (const key of SUBPROCESS_ENV_DENYLIST) Reflect.deleteProperty(env, key); return env; } diff --git a/apps/site/scripts/bootstrap/phases/deploy.ts b/apps/site/scripts/bootstrap/phases/deploy.ts index 97fdc8c6..f639e89a 100644 --- a/apps/site/scripts/bootstrap/phases/deploy.ts +++ b/apps/site/scripts/bootstrap/phases/deploy.ts @@ -187,12 +187,11 @@ export async function runDeployPhase( ); log.info( pc.dim( - 'Nothing to do. New code reaches production through the Deploy production workflow on every push to main. To push this checkout on purpose, run `pnpm bootstrap --phase deploy --redeploy`.', + 'Nothing to do. Main pushes update protected staging. Publish a reviewed release with the Promote website release workflow. Direct bootstrap redeployment is for setup or recovery.', ), ); return { success: true, followUpItems }; } - if (state.kind === 'unknown') { log.warn(`Couldn't check whether ${projectName} already has a production deployment.`); if (state.detail) logSubline(pc.dim(state.detail)); @@ -250,7 +249,7 @@ export async function runDeployPhase( : 'https://dash.cloudflare.com/'; followUpItems.push({ kind: 'remote', - message: `From now on, every push to main deploys through the "Deploy production" GitHub Actions workflow. It needs the CLOUDFLARE_API_TOKEN secret and CLOUDFLARE_ACCOUNT_ID variable listed in docs/reference/deployment-pipeline.md. Do not also connect the project to Git at ${dashboardUrl}, or each push would deploy twice.`, + message: `The website workflow publishes Workers through protected staging and explicit promotion. See docs/reference/deployment-pipeline.md for credentials. This Pages project is a recovery option; do not connect it to automatic Git deployments at ${dashboardUrl}.`, }); } diff --git a/apps/site/scripts/deploy/access-auth.ts b/apps/site/scripts/deploy/access-auth.ts new file mode 100644 index 00000000..f3dbc2b6 --- /dev/null +++ b/apps/site/scripts/deploy/access-auth.ts @@ -0,0 +1,47 @@ +export interface AccessCredentials { + clientId: string; + clientSecret: string; +} + +export function accessCredentials( + env: Record, +): AccessCredentials | undefined { + const clientId = env.CF_ACCESS_CLIENT_ID?.trim(); + const clientSecret = env.CF_ACCESS_CLIENT_SECRET?.trim(); + if (Boolean(clientId) !== Boolean(clientSecret)) + throw new Error('Set both Cloudflare Access service credentials.'); + return clientId && clientSecret ? { clientId, clientSecret } : undefined; +} + +export function accessHeaders( + url: string, + origin: string, + credentials?: AccessCredentials, +): Record { + if (!credentials) return {}; + const target = new URL(origin); + if (target.protocol !== 'https:') throw new Error('Access credentials require an HTTPS origin.'); + if (new URL(url).origin !== target.origin) return {}; + return { + 'CF-Access-Client-Id': credentials.clientId, + 'CF-Access-Client-Secret': credentials.clientSecret, + }; +} + +export function accessRequestOptions( + url: string, + origin: string, + credentials?: AccessCredentials, +): { headers: Record; maxRedirects: 0 } { + return { headers: accessHeaders(url, origin, credentials), maxRedirects: 0 }; +} + +export async function accessFetch( + url: string, + origin: string, + credentials?: AccessCredentials, +): Promise { + // Never follow redirects with service credentials. Access redirects are authentication + // failures; application redirects can be tested explicitly at their destination. + return fetch(url, { headers: accessHeaders(url, origin, credentials), redirect: 'manual' }); +} diff --git a/apps/site/scripts/deploy/access-browser.ts b/apps/site/scripts/deploy/access-browser.ts new file mode 100644 index 00000000..8ca4322b --- /dev/null +++ b/apps/site/scripts/deploy/access-browser.ts @@ -0,0 +1,41 @@ +import { test as base, type APIRequestContext } from '@playwright/test'; +import { accessCredentials, accessHeaders, accessRequestOptions } from './access-auth'; + +export * from '@playwright/test'; +export const test = base.extend<{ accessRequest: Pick }>({ + accessRequest: async ({ request, baseURL }, use) => { + if (!baseURL) throw new Error('API browser checks require a baseURL.'); + const origin = new URL(baseURL).origin; + const credentials = accessCredentials(process.env); + await use({ + get: (url, options) => { + const absolute = new URL(url, baseURL).href; + const authentication = accessRequestOptions(absolute, origin, credentials); + return request.get(absolute, { + ...options, + ...authentication, + headers: { ...options?.headers, ...authentication.headers }, + }); + }, + }); + }, + context: async ({ context, baseURL }, use) => { + const credentials = accessCredentials(process.env); + if (credentials) { + if (!baseURL) throw new Error('Access browser checks require a baseURL.'); + const origin = new URL(baseURL).origin; + await context.route('**/*', async (route) => { + const headers = accessHeaders(route.request().url(), origin, credentials); + if (Object.keys(headers).length === 0) return route.continue(); + // route.continue headers survive redirects. Fetch with redirects disabled, + // then let the browser follow the returned response as a new request. + const response = await route.fetch({ + headers: { ...route.request().headers(), ...headers }, + maxRedirects: 0, + }); + await route.fulfill({ response }); + }); + } + await use(context); + }, +}); diff --git a/apps/site/scripts/deploy/release-artifact.ts b/apps/site/scripts/deploy/release-artifact.ts new file mode 100644 index 00000000..9c173934 --- /dev/null +++ b/apps/site/scripts/deploy/release-artifact.ts @@ -0,0 +1,123 @@ +import { createHash } from 'node:crypto'; +import { cp, lstat, mkdir, readFile, readdir, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import { z } from 'zod'; + +const digest = z.string().regex(/^[a-f0-9]{64}$/); +const identitySchema = z + .object({ + commit: z.string().regex(/^[a-f0-9]{40}$/), + releaseId: z.string().regex(/^[a-z0-9][a-z0-9-]{0,63}$/), + }) + .strict(); +const releaseSchema = identitySchema + .extend({ + formatVersion: z.literal(1), + artifactHash: digest, + files: z.array(z.tuple([z.string().min(1), digest])), + }) + .strict(); +export type WebsiteRelease = z.infer; +type Identity = z.infer; + +function hash(value: string | Uint8Array): string { + return createHash('sha256').update(value).digest('hex'); +} + +async function inventory(directory: string, relative = ''): Promise<[string, string][]> { + const stat = await lstat(path.join(directory, relative)); + if (stat.isSymbolicLink()) throw new Error('Release content contains a symbolic link.'); + if (stat.isFile()) return [[relative, hash(await readFile(path.join(directory, relative)))]]; + if (!stat.isDirectory()) throw new Error('Unsupported release file type.'); + const files: [string, string][] = []; + for (const entry of await readdir(path.join(directory, relative))) { + if (relative === '' && entry === 'release.json') continue; + const child = relative ? `${relative}/${entry}` : entry; + files.push(...(await inventory(directory, child))); + } + return files.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)); +} + +function artifactHash(identity: Identity, files: [string, string][]): string { + return hash( + JSON.stringify({ + formatVersion: 1, + commit: identity.commit, + releaseId: identity.releaseId, + files, + }), + ); +} + +export async function packageRelease( + source: string, + destination: string, + identity: Identity, +): Promise { + identitySchema.parse(identity); + const assets = await inventory(path.join(source, 'dist')); + await inventory(path.join(source, '.wrangler/worker')); + const config = await lstat(path.join(source, 'wrangler.jsonc')); + if (config.isSymbolicLink() || !config.isFile()) + throw new Error('Invalid release configuration.'); + if ( + assets.some(([name]) => /^(patterns|prototypes)(\/|$)/.test(name)) || + (await readdir(path.join(source, 'dist'))).some((name) => + ['patterns', 'prototypes'].includes(name), + ) + ) { + throw new Error('Release contains preview-only routes.'); + } + for (const [name] of assets.filter(([file]) => file.endsWith('.html'))) { + if ((await readFile(path.join(source, 'dist', name), 'utf8')).includes('lang="en-XA"')) + throw new Error('Release contains a preview-only test language.'); + } + await mkdir(destination, { recursive: false }); + await cp(path.join(source, 'dist'), path.join(destination, 'dist'), { recursive: true }); + await mkdir(path.join(destination, '.wrangler')); + await cp(path.join(source, '.wrangler/worker'), path.join(destination, '.wrangler/worker'), { + recursive: true, + }); + await cp(path.join(source, 'wrangler.jsonc'), path.join(destination, 'wrangler.jsonc')); + await writeFile( + path.join(destination, 'dist/lvbt-release.json'), + `${JSON.stringify(identity)}\n`, + ); + const files = await inventory(destination); + if ( + !files.some(([name]) => name === 'dist/index.html') || + !files.some(([name]) => name === '.wrangler/worker/index.js') + ) + throw new Error('Release requires the home page and compiled Worker.'); + const release: WebsiteRelease = { + formatVersion: 1, + ...identity, + files, + artifactHash: artifactHash(identity, files), + }; + await writeFile(path.join(destination, 'release.json'), `${JSON.stringify(release, null, 2)}\n`, { + flag: 'wx', + }); + return release; +} + +export async function verifyRelease(directory: string): Promise { + const marker = await lstat(path.join(directory, 'release.json')); + if (marker.isSymbolicLink() || !marker.isFile()) throw new Error('Invalid release manifest.'); + const release = releaseSchema.parse( + JSON.parse(await readFile(path.join(directory, 'release.json'), 'utf8')), + ); + const files = await inventory(directory); + if ( + JSON.stringify(files) !== JSON.stringify(release.files) || + artifactHash(release, files) !== release.artifactHash + ) + throw new Error('Release identity or files do not match the reviewed artifact.'); + if ( + !files.some(([name]) => name === 'dist/index.html') || + !files.some(([name]) => name === '.wrangler/worker/index.js') || + !files.some(([name]) => name === 'wrangler.jsonc') + ) + throw new Error('Release is incomplete.'); + return release; +} diff --git a/apps/site/scripts/deploy/release-smoke.ts b/apps/site/scripts/deploy/release-smoke.ts new file mode 100644 index 00000000..836d610b --- /dev/null +++ b/apps/site/scripts/deploy/release-smoke.ts @@ -0,0 +1,71 @@ +import assert from 'node:assert/strict'; +import { parseArgs } from 'node:util'; +import { chromium } from '@playwright/test'; +import { accessCredentials, accessFetch, accessHeaders } from './access-auth'; + +const { values } = parseArgs({ + options: { + url: { type: 'string' }, + 'release-id': { type: 'string' }, + commit: { type: 'string' }, + protected: { type: 'boolean', default: false }, + }, +}); +if (!values.url) throw new Error('Pass --url with an HTTPS origin.'); +const parsed = new URL(values.url); +if (parsed.protocol !== 'https:' || parsed.pathname !== '/' || parsed.search || parsed.hash) + throw new Error('Pass an HTTPS origin without a path.'); +const origin = parsed.origin; +const credentials = values.protected ? accessCredentials(process.env) : undefined; +if (values.protected && !credentials) + throw new Error('Protected staging verification requires Access credentials.'); +if (values.protected) { + const anonymous = await accessFetch(`${origin}/`, origin); + assert.ok( + [302, 401, 403].includes(anonymous.status), + `Anonymous request reached staging (${anonymous.status}).`, + ); +} +if (values['release-id'] || values.commit) { + const marker = await accessFetch(`${origin}/lvbt-release.json`, origin, credentials); + assert.equal(marker.status, 200, 'Release marker is unavailable or Access rejected credentials.'); + const identity: unknown = await marker.json(); + assert.deepEqual(identity, { commit: values.commit, releaseId: values['release-id'] }); +} +const browser = await chromium.launch(); +try { + const context = await browser.newContext({ serviceWorkers: 'block' }); + await context.route('**/*', async (route) => { + const headers = accessHeaders(route.request().url(), origin, credentials); + if (Object.keys(headers).length === 0) return route.continue(); + const response = await route.fetch({ + headers: { ...route.request().headers(), ...headers }, + maxRedirects: 0, + }); + await route.fulfill({ response }); + }); + const page = await context.newPage(); + const analyticsRequests: string[] = []; + page.on('request', (request) => { + if ( + /cloudflareinsights\.com|events\.lasvegasfortransit\.org/.test( + new URL(request.url()).hostname, + ) + ) + analyticsRequests.push(request.url()); + }); + const response = await page.goto(origin, { waitUntil: 'networkidle' }); + assert.equal(response?.status(), 200, 'The website did not render.'); + assert.equal(new URL(page.url()).origin, origin, 'Browser was redirected away from the website.'); + assert.match(await page.title(), /Las Vegas|LVBT/i); + assert.ok(await page.locator('main').isVisible(), 'The website main content is not visible.'); + if (values.protected) { + assert.equal(response.headers()['x-robots-tag'], 'noindex, nofollow, noarchive'); + assert.deepEqual(analyticsRequests, [], 'Preview sent production analytics.'); + } + process.stdout.write( + `PASS: rendered ${origin}${values['release-id'] ? `, release ${values['release-id']}` : ''}\n`, + ); +} finally { + await browser.close(); +} diff --git a/apps/site/scripts/deploy/release-source.ts b/apps/site/scripts/deploy/release-source.ts new file mode 100644 index 00000000..a2946e32 --- /dev/null +++ b/apps/site/scripts/deploy/release-source.ts @@ -0,0 +1,31 @@ +import { z } from 'zod'; + +const sourceSchema = z.object({ + id: z.number().int().positive(), + run_attempt: z.number().int().positive(), + name: z.literal('Deploy staging'), + path: z.literal('.github/workflows/deploy-production.yml'), + event: z.enum(['push', 'workflow_dispatch']), + head_branch: z.literal('main'), + head_sha: z.string().regex(/^[a-f0-9]{40}$/), + status: z.literal('completed'), + conclusion: z.literal('success'), + repository: z.object({ full_name: z.string() }), + head_repository: z.object({ full_name: z.string() }), +}); + +export function releaseSource( + value: unknown, + repository: string, + runId: string, +): { commit: string; releaseId: string } { + if (!/^[1-9][0-9]*$/.test(runId)) throw new Error('Select an Actions run ID.'); + const source = sourceSchema.parse(value); + if ( + String(source.id) !== runId || + source.repository.full_name !== repository || + source.head_repository.full_name !== repository + ) + throw new Error('The source run must belong to this repository.'); + return { commit: source.head_sha, releaseId: String(source.id) }; +} diff --git a/apps/site/scripts/deploy/worker-release.ts b/apps/site/scripts/deploy/worker-release.ts new file mode 100644 index 00000000..84540c5e --- /dev/null +++ b/apps/site/scripts/deploy/worker-release.ts @@ -0,0 +1,137 @@ +import { execFile } from 'node:child_process'; +import { cp, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import { parseArgs, promisify } from 'node:util'; +import { previewUploadReceipt } from '@lasvegasfortransit/web-platform/release'; +import { packageRelease, verifyRelease, type WebsiteRelease } from './release-artifact'; +import { releaseSource } from './release-source'; + +const execute = promisify(execFile); +function printRelease(release: WebsiteRelease): void { + process.stdout.write( + `${JSON.stringify({ commit: release.commit, releaseId: release.releaseId, artifactHash: release.artifactHash, fileCount: release.files.length })}\n`, + ); +} +const { positionals, values } = parseArgs({ + allowPositionals: true, + options: { + directory: { type: 'string' }, + commit: { type: 'string' }, + 'release-id': { type: 'string' }, + target: { type: 'string' }, + version: { type: 'string' }, + 'run-file': { type: 'string' }, + repository: { type: 'string' }, + 'run-id': { type: 'string' }, + }, +}); +const command = positionals[0]; +const directory = values.directory ? path.resolve(values.directory) : undefined; +const target = values.target; +if (command === 'source') { + if (!values['run-file'] || !values.repository || !values['run-id']) + throw new Error('Pass --run-file, --repository and --run-id.'); + const source = releaseSource( + JSON.parse(await readFile(values['run-file'], 'utf8')), + values.repository, + values['run-id'], + ); + // eslint-disable-next-line turbo/no-undeclared-env-vars -- Actions provides this output file. + const output = process.env.GITHUB_OUTPUT; + if (output) + await writeFile(output, `commit=${source.commit}\nrelease-id=${source.releaseId}\n`, { + flag: 'a', + }); + process.stdout.write(`${JSON.stringify(source)}\n`); +} else if (command === 'package') { + if (!directory || !values.commit || !values['release-id']) + throw new Error('Pass --directory, --commit and --release-id.'); + printRelease( + await packageRelease(process.cwd(), directory, { + commit: values.commit, + releaseId: values['release-id'], + }), + ); +} else if (command === 'verify' || command === 'upload') { + if (!directory) throw new Error('Pass --directory.'); + const release = await verifyRelease(directory); + if (values.commit && release.commit !== values.commit) + throw new Error('Release commit does not match the selected Actions run.'); + if (values['release-id'] && release.releaseId !== values['release-id']) + throw new Error('Release ID does not match the selected Actions run.'); + if (command === 'verify') { + printRelease(release); + } else { + if (target !== 'preview' && target !== 'production') + throw new Error('Pass --target preview or production.'); + const temporary = await mkdtemp(path.join(os.tmpdir(), 'lvbt-release-upload-')); + try { + // Wrangler may write cache files; its working copy cannot modify the saved release. + const copy = path.join(temporary, 'release'); + await cp(directory, copy, { recursive: true }); + const receiptPath = path.join(temporary, 'receipt.jsonl'); + await execute( + 'pnpm', + [ + 'exec', + 'wrangler', + 'versions', + 'upload', + '--config', + path.join(copy, 'wrangler.jsonc'), + '--env', + target === 'preview' ? 'preview' : '', + '--no-bundle', + '--preview-alias', + `release-${release.releaseId}`, + '--message', + `Release ${release.releaseId} at ${release.commit}`, + ], + { + env: { + ...process.env, + WRANGLER_LOG_SANITIZE: 'true', + WRANGLER_OUTPUT_FILE_PATH: receiptPath, + }, + maxBuffer: 16 * 1024 * 1024, + }, + ); + const receipt = previewUploadReceipt( + await readFile(receiptPath, 'utf8'), + target === 'preview' ? 'lvbt-website-preview' : 'lvbt-website', + ); + // eslint-disable-next-line turbo/no-undeclared-env-vars -- Actions provides this output file. + const output = process.env.GITHUB_OUTPUT; + if (output) + await writeFile(output, `url=${receipt.url}\nversion=${receipt.version}\n`, { flag: 'a' }); + process.stdout.write( + `${JSON.stringify({ ...receipt, releaseId: release.releaseId, commit: release.commit, artifactHash: release.artifactHash })}\n`, + ); + } finally { + await rm(temporary, { recursive: true, force: true }); + } + } +} else if (command === 'activate') { + if (target !== 'preview' && target !== 'production') + throw new Error('Pass --target preview or production.'); + if (!values.version || !/^[a-f0-9-]{36}$/.test(values.version)) + throw new Error('Pass an explicit Worker version ID.'); + const { stdout } = await execute( + 'pnpm', + [ + 'exec', + 'wrangler', + 'versions', + 'deploy', + `${values.version}@100%`, + '--name', + target === 'preview' ? 'lvbt-website-preview' : 'lvbt-website', + '--env', + target === 'preview' ? 'preview' : '', + '--yes', + ], + { maxBuffer: 16 * 1024 * 1024 }, + ); + process.stdout.write(stdout); +} else throw new Error('Use package, verify, upload, or activate.'); diff --git a/apps/site/tests/e2e/a11y-forms.spec.ts b/apps/site/tests/e2e/a11y-forms.spec.ts index 5f09b0b5..3574fe39 100644 --- a/apps/site/tests/e2e/a11y-forms.spec.ts +++ b/apps/site/tests/e2e/a11y-forms.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from '@playwright/test'; +import { expect, test } from '../../scripts/deploy/access-browser'; test.describe('newsletter accessibility', () => { test('exposes the hosted newsletter through a named link', async ({ page }) => { diff --git a/apps/site/tests/e2e/a11y-keyboard.spec.ts b/apps/site/tests/e2e/a11y-keyboard.spec.ts index c8314f00..a9d41103 100644 --- a/apps/site/tests/e2e/a11y-keyboard.spec.ts +++ b/apps/site/tests/e2e/a11y-keyboard.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from '@playwright/test'; +import { expect, test } from '../../scripts/deploy/access-browser'; import { preparePageForA11y } from '../support/a11y-helpers'; test.describe('keyboard accessibility', () => { diff --git a/apps/site/tests/e2e/a11y-landmarks.spec.ts b/apps/site/tests/e2e/a11y-landmarks.spec.ts index 572b9b4e..64329cac 100644 --- a/apps/site/tests/e2e/a11y-landmarks.spec.ts +++ b/apps/site/tests/e2e/a11y-landmarks.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from '@playwright/test'; +import { expect, test } from '../../scripts/deploy/access-browser'; import { preparePageForA11y } from '../support/a11y-helpers'; test.describe('landmark accessibility', () => { diff --git a/apps/site/tests/e2e/a11y-patterns.spec.ts b/apps/site/tests/e2e/a11y-patterns.spec.ts index 1c5c4d7e..539cdcea 100644 --- a/apps/site/tests/e2e/a11y-patterns.spec.ts +++ b/apps/site/tests/e2e/a11y-patterns.spec.ts @@ -7,7 +7,7 @@ // builds skip these checks. import AxeBuilder from '@axe-core/playwright'; import { existsSync } from 'node:fs'; -import { expect, test, type Page } from '@playwright/test'; +import { expect, test, type Page } from '../../scripts/deploy/access-browser'; import { preparePageForA11y } from '../support/a11y-helpers'; const galleryBuilt = existsSync(new URL('../../dist/patterns/index.html', import.meta.url)); diff --git a/apps/site/tests/e2e/a11y-pseudo-locale.spec.ts b/apps/site/tests/e2e/a11y-pseudo-locale.spec.ts index 2662b112..afa4b8e6 100644 --- a/apps/site/tests/e2e/a11y-pseudo-locale.spec.ts +++ b/apps/site/tests/e2e/a11y-pseudo-locale.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from '@playwright/test'; +import { expect, test } from '../../scripts/deploy/access-browser'; // The long-text check: with the site built in the en-XA pseudo-language // (LVBT_PSEUDO_LOCALE=1), every member-facing message is about 40 percent diff --git a/apps/site/tests/e2e/a11y.spec.ts b/apps/site/tests/e2e/a11y.spec.ts index 9de2c049..783b898b 100644 --- a/apps/site/tests/e2e/a11y.spec.ts +++ b/apps/site/tests/e2e/a11y.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from '@playwright/test'; +import { expect, test } from '../../scripts/deploy/access-browser'; import { builtHtmlPagePaths } from '../support/sitemap-paths'; import { axeBlockingViolations, diff --git a/apps/site/tests/e2e/body-links.spec.ts b/apps/site/tests/e2e/body-links.spec.ts index 7af3a78d..df20641b 100644 --- a/apps/site/tests/e2e/body-links.spec.ts +++ b/apps/site/tests/e2e/body-links.spec.ts @@ -1,4 +1,4 @@ -import { expect, test, type Locator, type Page } from '@playwright/test'; +import { expect, test, type Locator, type Page } from '../../scripts/deploy/access-browser'; const LINKEDIN_URL = 'https://www.linkedin.com/company/lasvegasfortransit/'; const SITE_URL = 'https://lasvegasfortransit.org'; @@ -128,7 +128,7 @@ test.describe('body content links', () => { test('indexes brand and colophon while keeping QR out of the sitemap', async ({ page, - request, + accessRequest, }) => { for (const path of ['/brand', '/colophon']) { await page.goto(path); @@ -143,7 +143,7 @@ test.describe('body content links', () => { 'noindex,nofollow', ); - const sitemap = await request.get('/sitemap-0.xml'); + const sitemap = await accessRequest.get('/sitemap-0.xml'); expect(sitemap.ok()).toBe(true); const body = await sitemap.text(); expect(body).toContain('https://lasvegasfortransit.org/brand/'); @@ -157,8 +157,12 @@ test.describe('body content links', () => { await page.waitForLoadState('networkidle'); const results = await page.evaluate(async () => { - type PagefindResult = { data: () => Promise<{ url: string }> }; - type Pagefind = { search: (query: string) => Promise<{ results: PagefindResult[] }> }; + interface PagefindResult { + data: () => Promise<{ url: string }>; + } + interface Pagefind { + search: (query: string) => Promise<{ results: PagefindResult[] }>; + } const pagefind = (await Function('return import("/pagefind/pagefind.js")')()) as Pagefind; const urlsFor = async (query: string) => { @@ -260,11 +264,12 @@ test.describe('body content links', () => { document .querySelectorAll('.reveal, .reveal-stat, .reveal-quote') .forEach((el) => el.classList.add('is-visible')); - const footer = document.querySelector('footer') as HTMLElement | null; + const footer = document.querySelector('footer'); window.scrollTo({ top: footer?.offsetTop ?? 0, left: 0, behavior: 'instant' }); }); await page.waitForFunction( - () => Math.abs(document.querySelector('footer')!.getBoundingClientRect().top) < 1, + () => + Math.abs(document.querySelector('footer')?.getBoundingClientRect().top ?? Infinity) < 1, ); return page.evaluate(() => { @@ -337,11 +342,10 @@ test.describe('body content links', () => { const meta = page.locator('footer .footer-meta'); const [metaBox, navBox] = await Promise.all([meta.boundingBox(), utilityNav.boundingBox()]); - expect(metaBox).not.toBeNull(); - expect(navBox).not.toBeNull(); - expect(metaBox!.height).toBeLessThanOrEqual(48); - expect(navBox!.x).toBeGreaterThanOrEqual(metaBox!.x); - expect(navBox!.x + navBox!.width).toBeLessThanOrEqual(metaBox!.x + metaBox!.width); + if (!metaBox || !navBox) throw new Error('Footer meta and utility navigation must render.'); + expect(metaBox.height).toBeLessThanOrEqual(48); + expect(navBox.x).toBeGreaterThanOrEqual(metaBox.x); + expect(navBox.x + navBox.width).toBeLessThanOrEqual(metaBox.x + metaBox.width); }); test('keeps the colophon on the standard type scale without overline labels', async ({ @@ -1170,7 +1174,7 @@ test.describe('body content links', () => { // /events/.ics. Guards two things at once: (a) the route still // emits at build time; (b) the file is RFC 5545 enough that the OS // calendar handler will recognise it. - test('virtual event publishes a valid .ics feed', async ({ page, request }) => { + test('virtual event publishes a valid .ics feed', async ({ page, accessRequest }) => { // firstVirtualEventPath leaves the page on the chosen event's detail view, // so the title and join URL come from the rendered page rather than // hardcoded calendar strings that drift when the event is renamed. @@ -1185,7 +1189,7 @@ test.describe('body content links', () => { .first() .getAttribute('href'); - const response = await request.get(`${eventPath}.ics`); + const response = await accessRequest.get(`${eventPath}.ics`); expect(response.status()).toBe(200); const body = await response.text(); expect(body).toContain('BEGIN:VCALENDAR'); diff --git a/apps/site/tests/e2e/color-contracts.spec.ts b/apps/site/tests/e2e/color-contracts.spec.ts index 2134ef1c..10090ce6 100644 --- a/apps/site/tests/e2e/color-contracts.spec.ts +++ b/apps/site/tests/e2e/color-contracts.spec.ts @@ -1,7 +1,7 @@ import { readdirSync, readFileSync, statSync } from 'node:fs'; import { join, relative } from 'node:path'; import { fileURLToPath } from 'node:url'; -import { expect, test, type Locator, type Page } from '@playwright/test'; +import { expect, test, type Locator, type Page } from '../../scripts/deploy/access-browser'; import { builtSitemapPaths } from '../support/sitemap-paths'; const paths = [...builtSitemapPaths(import.meta.url), '/totally-missing-seo-audit-test/']; diff --git a/apps/site/tests/e2e/event-metadata.spec.ts b/apps/site/tests/e2e/event-metadata.spec.ts index 12dc5617..d029981a 100644 --- a/apps/site/tests/e2e/event-metadata.spec.ts +++ b/apps/site/tests/e2e/event-metadata.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from '@playwright/test'; +import { expect, test } from '../../scripts/deploy/access-browser'; import { eventSchema } from '../../src/lib/structured-data'; type JsonLd = Record; diff --git a/apps/site/tests/e2e/footer-colophon.spec.ts b/apps/site/tests/e2e/footer-colophon.spec.ts index 045b8e8b..c9b99ee0 100644 --- a/apps/site/tests/e2e/footer-colophon.spec.ts +++ b/apps/site/tests/e2e/footer-colophon.spec.ts @@ -1,7 +1,7 @@ -import { expect, test } from '@playwright/test'; +import { expect, test } from '../../scripts/deploy/access-browser'; test.describe('footer colophon', () => { - test('indexes colophon while keeping QR out of the sitemap', async ({ page, request }) => { + test('indexes colophon while keeping QR out of the sitemap', async ({ page, accessRequest }) => { await page.goto('/colophon'); await page.waitForLoadState('networkidle'); await expect(page.locator('meta[name="robots"]')).toHaveCount(0); @@ -12,10 +12,9 @@ test.describe('footer colophon', () => { 'content', 'noindex,nofollow', ); - - const sitemap = await request.get('/sitemap-0.xml'); - expect(sitemap.ok()).toBe(true); + const sitemap = await accessRequest.get('/sitemap-0.xml'); const body = await sitemap.text(); + expect(sitemap.ok()).toBe(true); expect(body).toContain('https://lasvegasfortransit.org/colophon/'); expect(body).not.toContain('https://lasvegasfortransit.org/qr/'); expect(body).not.toContain('https://lasvegasfortransit.org/vision/'); diff --git a/apps/site/tests/e2e/header-current.spec.ts b/apps/site/tests/e2e/header-current.spec.ts index f7827348..2ad4f1b7 100644 --- a/apps/site/tests/e2e/header-current.spec.ts +++ b/apps/site/tests/e2e/header-current.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from '@playwright/test'; +import { expect, test } from '../../scripts/deploy/access-browser'; test.describe('header current navigation', () => { test('marks nav links current only on exact route matches', async ({ page }) => { diff --git a/apps/site/tests/e2e/not-found-search.spec.ts b/apps/site/tests/e2e/not-found-search.spec.ts index 917ddbce..ed96bc6b 100644 --- a/apps/site/tests/e2e/not-found-search.spec.ts +++ b/apps/site/tests/e2e/not-found-search.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from '@playwright/test'; +import { expect, test } from '../../scripts/deploy/access-browser'; test.describe('not found search recovery', () => { test('serves a transit-flavored not found page for missing routes', async ({ page }) => { diff --git a/apps/site/tests/e2e/perf-memory.spec.ts b/apps/site/tests/e2e/perf-memory.spec.ts index 726462b7..f4361fe3 100644 --- a/apps/site/tests/e2e/perf-memory.spec.ts +++ b/apps/site/tests/e2e/perf-memory.spec.ts @@ -1,7 +1,7 @@ import { readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, resolve } from 'node:path'; -import { expect, test } from '@playwright/test'; +import { expect, test } from '../../scripts/deploy/access-browser'; import { builtSitemapPaths } from '../support/sitemap-paths'; // Same sitemap-driven URL list as a11y.spec.ts / screenshots.spec.ts so diff --git a/apps/site/tests/e2e/print-layout.spec.ts b/apps/site/tests/e2e/print-layout.spec.ts index ef7ebbf1..19fbb034 100644 --- a/apps/site/tests/e2e/print-layout.spec.ts +++ b/apps/site/tests/e2e/print-layout.spec.ts @@ -1,4 +1,4 @@ -import { expect, test, type Page } from '@playwright/test'; +import { expect, test, type Page } from '../../scripts/deploy/access-browser'; async function openForPrint(page: Page, path: string) { await page.emulateMedia({ media: 'print' }); diff --git a/apps/site/tests/e2e/screenshots.spec.ts b/apps/site/tests/e2e/screenshots.spec.ts index c35f9a59..36cc6e7f 100644 --- a/apps/site/tests/e2e/screenshots.spec.ts +++ b/apps/site/tests/e2e/screenshots.spec.ts @@ -1,7 +1,7 @@ import { readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, resolve } from 'node:path'; -import { expect, test } from '@playwright/test'; +import { expect, test } from '../../scripts/deploy/access-browser'; // Routes are sourced from the built sitemap so dynamic content-collection // routes (projects, events) stay in sync without a hand-maintained list. diff --git a/apps/site/tests/e2e/typography-contracts.spec.ts b/apps/site/tests/e2e/typography-contracts.spec.ts index 2cd8680b..b04fcc10 100644 --- a/apps/site/tests/e2e/typography-contracts.spec.ts +++ b/apps/site/tests/e2e/typography-contracts.spec.ts @@ -1,4 +1,4 @@ -import { expect, test, type Page } from '@playwright/test'; +import { expect, test, type Page } from '../../scripts/deploy/access-browser'; import { builtHtmlPagePaths } from '../support/sitemap-paths'; const paths = builtHtmlPagePaths(import.meta.url); diff --git a/apps/site/tests/release-artifact.test.ts b/apps/site/tests/release-artifact.test.ts new file mode 100644 index 00000000..5849db89 --- /dev/null +++ b/apps/site/tests/release-artifact.test.ts @@ -0,0 +1,79 @@ +import assert from 'node:assert/strict'; +import { mkdtemp, mkdir, readFile, rm, symlink, writeFile } from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import { test } from 'node:test'; +import { packageRelease, verifyRelease } from '../scripts/deploy/release-artifact'; + +const identity = { commit: 'a'.repeat(40), releaseId: '12345' }; + +async function fixture() { + const root = await mkdtemp(path.join(os.tmpdir(), 'lvbt-release-test-')); + const source = path.join(root, 'source'); + const release = path.join(root, 'release'); + await mkdir(path.join(source, 'dist'), { recursive: true }); + await mkdir(path.join(source, '.wrangler/worker'), { recursive: true }); + await writeFile(path.join(source, 'dist/index.html'), '

Arts District

'); + await writeFile(path.join(source, '.wrangler/worker/index.js'), 'export default {}'); + await writeFile(path.join(source, 'wrangler.jsonc'), '{"name":"lvbt-website"}'); + return { root, source, release }; +} + +void test('a release retains reviewed files and identity after its source changes', async () => { + const f = await fixture(); + try { + const release = await packageRelease(f.source, f.release, identity); + await writeFile(path.join(f.source, 'dist/index.html'), '

Newer build

'); + assert.deepEqual(await verifyRelease(f.release), release); + assert.equal( + await readFile(path.join(f.release, 'dist/index.html'), 'utf8'), + '

Arts District

', + ); + assert.equal(release.releaseId, '12345'); + assert.equal( + release.files.some(([name]) => name === '.wrangler/worker/index.js'), + true, + ); + } finally { + await rm(f.root, { recursive: true, force: true }); + } +}); + +for (const mutation of ['changed', 'added', 'removed', 'identity', 'symlink']) { + void test(`verification rejects ${mutation} release content`, async () => { + const f = await fixture(); + try { + await packageRelease(f.source, f.release, identity); + const index = path.join(f.release, 'dist/index.html'); + if (mutation === 'changed') await writeFile(index, 'tampered'); + if (mutation === 'added') + await writeFile(path.join(f.release, 'dist/extra.txt'), 'unreviewed'); + if (mutation === 'removed') await rm(index); + if (mutation === 'symlink') { + await rm(index); + await symlink(path.join(f.source, 'dist/index.html'), index); + } + if (mutation === 'identity') { + const manifest = await verifyRelease(f.release); + manifest.commit = 'b'.repeat(40); + await writeFile(path.join(f.release, 'release.json'), JSON.stringify(manifest)); + } + await assert.rejects(verifyRelease(f.release)); + } finally { + await rm(f.root, { recursive: true, force: true }); + } + }); +} + +void test('release packaging rejects prototype routes and symlinked source assets', async () => { + const f = await fixture(); + try { + await mkdir(path.join(f.source, 'dist/prototypes')); + await assert.rejects(packageRelease(f.source, f.release, identity), /preview-only/i); + await rm(path.join(f.source, 'dist/prototypes'), { recursive: true }); + await symlink(path.join(f.source, 'dist/index.html'), path.join(f.source, 'dist/linked.html')); + await assert.rejects(packageRelease(f.source, f.release, identity), /symbolic/i); + } finally { + await rm(f.root, { recursive: true, force: true }); + } +}); diff --git a/apps/site/tests/release-source.test.ts b/apps/site/tests/release-source.test.ts new file mode 100644 index 00000000..2f09dbfd --- /dev/null +++ b/apps/site/tests/release-source.test.ts @@ -0,0 +1,45 @@ +import assert from 'node:assert/strict'; +import { test } from 'node:test'; +import { releaseSource } from '../scripts/deploy/release-source'; + +const run = { + id: 123, + run_attempt: 1, + name: 'Deploy staging', + path: '.github/workflows/deploy-production.yml', + event: 'push', + head_branch: 'main', + head_sha: 'a'.repeat(40), + status: 'completed', + conclusion: 'success', + repository: { full_name: 'LasVegasForTransit/website' }, + head_repository: { full_name: 'LasVegasForTransit/website' }, +}; +void test('promotion selects the immutable successful main staging run', () => { + assert.deepEqual(releaseSource(run, 'LasVegasForTransit/website', '123'), { + commit: 'a'.repeat(40), + releaseId: '123', + }); +}); + +void test('retrying a failed staging job preserves the previously saved release identity', () => { + assert.deepEqual(releaseSource({ ...run, run_attempt: 2 }, 'LasVegasForTransit/website', '123'), { + commit: 'a'.repeat(40), + releaseId: '123', + }); +}); +for (const mutation of [ + { event: 'pull_request' }, + { head_branch: 'feature' }, + { status: 'in_progress' }, + { conclusion: 'failure' }, + { path: '.github/workflows/deploy-worker-preview.yml' }, + { name: 'Deploy production' }, + { id: 124 }, + { head_repository: { full_name: 'outsider/website' } }, +]) { + void test(`promotion rejects ${JSON.stringify(mutation)}`, () => + assert.throws(() => + releaseSource({ ...run, ...mutation }, 'LasVegasForTransit/website', '123'), + )); +} diff --git a/apps/site/tests/staging-environment.test.ts b/apps/site/tests/staging-environment.test.ts new file mode 100644 index 00000000..d9903289 --- /dev/null +++ b/apps/site/tests/staging-environment.test.ts @@ -0,0 +1,73 @@ +import assert from 'node:assert/strict'; +import { test } from 'node:test'; +import { shouldEnable } from '@lasvegasfortransit/analytics'; +import { + accessHeaders, + accessCredentials, + accessRequestOptions, +} from '../scripts/deploy/access-auth'; +import { deploymentResponse } from '../functions/_middleware'; + +void test('Access headers are supplied only to the configured HTTPS origin', () => { + const credentials = { clientId: 'test-id', clientSecret: 'test-secret' }; + const origin = 'https://preview.lasvegasfortransit.org'; + assert.equal( + accessHeaders(`${origin}/about`, origin, credentials)['CF-Access-Client-Id'], + 'test-id', + ); + for (const url of [ + 'https://example.com/', + 'https://preview.lasvegasfortransit.org.evil.test/', + 'http://preview.lasvegasfortransit.org/', + ]) { + assert.deepEqual(accessHeaders(url, origin, credentials), {}); + } + assert.throws(() => accessCredentials({ CF_ACCESS_CLIENT_ID: 'id' }), /both/); + assert.equal(accessCredentials({}), undefined); +}); + +void test('API and browser request options authenticate the target without following redirects', () => { + const origin = 'https://preview.lasvegasfortransit.org'; + const credentials = { clientId: 'test-id', clientSecret: 'test-secret' }; + assert.deepEqual(accessRequestOptions(`${origin}/sitemap-0.xml`, origin, credentials), { + headers: { 'CF-Access-Client-Id': 'test-id', 'CF-Access-Client-Secret': 'test-secret' }, + maxRedirects: 0, + }); + assert.deepEqual(accessRequestOptions('https://example.com/', origin, credentials), { + headers: {}, + maxRedirects: 0, + }); + assert.deepEqual(accessRequestOptions('http://localhost/sitemap-0.xml', 'http://localhost'), { + headers: {}, + maxRedirects: 0, + }); +}); + +void test('preview responses cannot be indexed or cached, including redirects', () => { + for (const status of [200, 302, 404]) { + const response = deploymentResponse(new Response(null, { status }), 'preview'); + assert.equal(response.headers.get('x-robots-tag'), 'noindex, nofollow, noarchive'); + assert.equal(response.headers.get('cache-control'), 'private, no-store'); + assert.equal(response.status, status); + } + const publicResponse = deploymentResponse(new Response('public'), 'production'); + assert.equal(publicResponse.headers.get('x-robots-tag'), null); +}); + +void test('the same production analytics bundle stays disabled on every preview hostname', () => { + const input = { + site: 'lasvegasfortransit.org', + token: 'public-token', + pathname: '/', + gpc: false, + dnt: false, + framed: false, + }; + for (const hostname of [ + 'preview.lasvegasfortransit.org', + 'pr-123-lvbt-website-preview.las-vegas-for-better-transit.workers.dev', + ]) { + assert.equal(shouldEnable({ ...input, hostname }).enabled, false); + } + assert.equal(shouldEnable({ ...input, hostname: 'lasvegasfortransit.org' }).enabled, true); +}); diff --git a/apps/site/tests/worker-candidate-workflow.test.ts b/apps/site/tests/worker-candidate-workflow.test.ts index 84badfca..18617bfb 100644 --- a/apps/site/tests/worker-candidate-workflow.test.ts +++ b/apps/site/tests/worker-candidate-workflow.test.ts @@ -11,22 +11,27 @@ const previewWorkflowUrl = new URL( import.meta.url, ); -void test('Worker candidates remain parallel to Pages production', async () => { +void test('production promotion requires an explicit selection and never rebuilds', async () => { const workflow = await readFile(workflowUrl, 'utf8'); - assert.match(workflow, /workflows: \[Deploy production\]/); - assert.match(workflow, /github\.event\.workflow_run\.conclusion == 'success'/); - assert.match(workflow, /CLOUDFLARE_WORKERS_CANDIDATE_ENABLED == 'true'/); - assert.match(workflow, /pnpm worker:preview/); - assert.match(workflow, /pnpm worker:test:live/); + assert.match(workflow, /workflow_dispatch:/); + assert.match(workflow, /run_id:/); + assert.doesNotMatch(workflow, /workflow_run:/); + assert.match(workflow, /pnpm worker:release source/); + assert.match(workflow, /run-id: \$\{\{ inputs.run_id \}\}/); + assert.match(workflow, /pnpm worker:release verify/); + assert.match(workflow, /pnpm worker:release upload/); + assert.match(workflow, /pnpm worker:release activate/); assert.match(workflow, /pnpm worker:test:browser/); - assert.doesNotMatch(workflow, /wrangler deploy/); - assert.doesNotMatch(workflow, /worker:deploy/); + assert.doesNotMatch(workflow, /pnpm build|pnpm check\s|wrangler deploy|worker:deploy/); }); void test('Worker builds use the production analytics contract', async () => { const workflows = await Promise.all( - [workflowUrl, previewWorkflowUrl].map((url) => readFile(url, 'utf8')), + [ + new URL('../../../.github/workflows/deploy-production.yml', import.meta.url), + previewWorkflowUrl, + ].map((url) => readFile(url, 'utf8')), ); for (const workflow of workflows) { @@ -34,3 +39,15 @@ void test('Worker builds use the production analytics contract', async () => { assert.match(workflow, /LVBT_REQUIRE_ANALYTICS: '1'/); } }); + +void test('main deploys staging, stores hidden compiled files and never activates production', async () => { + const workflow = await readFile( + new URL('../../../.github/workflows/deploy-production.yml', import.meta.url), + 'utf8', + ); + assert.match(workflow, /name: Deploy staging/); + assert.match(workflow, /include-hidden-files: true/); + assert.match(workflow, /worker:release package/); + assert.match(workflow, /worker:release activate --target preview/); + assert.doesNotMatch(workflow, /--target production|deploy-cloudflare-pages/); +}); diff --git a/apps/site/worker-configuration.d.ts b/apps/site/worker-configuration.d.ts index 63b333d1..e7f153ad 100644 --- a/apps/site/worker-configuration.d.ts +++ b/apps/site/worker-configuration.d.ts @@ -1,8 +1,9 @@ /* eslint-disable */ -// Generated by Wrangler by running `wrangler types --include-runtime=false` (hash: f91e94b7377d5d1b9b8261edcb4e9a7a) +// Generated by Wrangler by running `wrangler types --include-runtime=false` (hash: 4869a2ef1757cceddc057ab664adb02c) interface __BaseEnv_Env { PLATFORM_DB: D1Database; ASSETS: Fetcher; + LVBT_DEPLOYMENT_ENV: "preview" | "production"; } declare namespace Cloudflare { interface GlobalProps { @@ -11,7 +12,14 @@ declare namespace Cloudflare { interface PreviewEnv { PLATFORM_DB: D1Database; ASSETS: Fetcher; + LVBT_DEPLOYMENT_ENV: "preview"; } interface Env extends __BaseEnv_Env {} } interface Env extends __BaseEnv_Env {} +type StringifyValues> = { + [Binding in keyof EnvType]: EnvType[Binding] extends string ? EnvType[Binding] : string; +}; +declare namespace NodeJS { + interface ProcessEnv extends StringifyValues> {} +} diff --git a/apps/site/wrangler.jsonc b/apps/site/wrangler.jsonc index 048bf1bd..ac2fb4cb 100644 --- a/apps/site/wrangler.jsonc +++ b/apps/site/wrangler.jsonc @@ -6,6 +6,7 @@ "main": ".wrangler/worker/index.js", "workers_dev": false, "preview_urls": true, + "vars": { "LVBT_DEPLOYMENT_ENV": "production" }, "assets": { "directory": "./dist", "binding": "ASSETS", @@ -42,6 +43,14 @@ ], "env": { "preview": { + "vars": { "LVBT_DEPLOYMENT_ENV": "preview" }, + "routes": [{ "pattern": "preview.lasvegasfortransit.org", "custom_domain": true }], + "assets": { + "directory": "./dist", + "binding": "ASSETS", + "not_found_handling": "404-page", + "run_worker_first": true, + }, "d1_databases": [ { "binding": "PLATFORM_DB", diff --git a/docs/explanation/events-pipeline.md b/docs/explanation/events-pipeline.md index 0cc788a7..27664b96 100644 --- a/docs/explanation/events-pipeline.md +++ b/docs/explanation/events-pipeline.md @@ -1,8 +1,8 @@ # Events pipeline This page explains how events get onto the site — for anyone adding an event or touching the events -code. The short version: you create events in Google Calendar, and the site pulls them in -automatically when it rebuilds. +code. The short version: you create events in Google Calendar, and the site pulls them in when a +rebuilt release is promoted. Events on the site come from a public Google Calendar. The site rebuilds against the calendar on a schedule; the calendar is the source of truth for both event metadata (title, time, location, join @@ -122,12 +122,13 @@ the calendar's UID isn't user-friendly enough to use as the slug. A GitHub Actions (GitHub's built-in automation that runs scripts on a schedule or on each push) scheduled workflow — a "cron" job, meaning it runs on a fixed timetable — at [`.github/workflows/cron-rebuild.yml`](../../.github/workflows/cron-rebuild.yml) fires twice a day -(roughly morning and evening PT) and dispatches the `Deploy production` workflow. The build -re-fetches the calendar before the Worker version is deployed. +(roughly morning and evening PT) and dispatches the `Deploy staging` workflow. The build re-fetches +the calendar and updates the protected preview site. Production changes only after an editor +promotes that successful run with `Promote website release`. Why twice a day: event metadata changes a few times a week at most; morning and evening rebuilds -keep the site current without unnecessary deployments. Tighten the cron in the workflow file if -events start moving faster than that. +keep staging current for review. Tighten the cron in the workflow file if events start moving faster +than that. Why GitHub Actions and not a Cloudflare Worker: the trigger needs zero long-lived credentials this way. The workflow uses the auto-issued `GITHUB_TOKEN`, scope-limited to `actions: write` on this @@ -135,11 +136,13 @@ repo. No PATs, no Worker secrets, no API token rotation. Logs surface in the Act every other deploy. For a same-day correction, push a commit or click **Run workflow** on the `cron-rebuild` (or -`Deploy production`) workflow page — both trigger an immediate redeploy. +`Deploy staging`) workflow page. Review the updated event information on staging, then promote that +run to publish the correction. -Worst-case staleness: ~12 hours (gap between the morning and evening rebuilds) + however long +Staging refresh delay: ~12 hours (gap between the morning and evening rebuilds) + however long Google's ICS edge cache holds (typically near-instant, can be a few hours for public calendars). For -anything time-sensitive, hit **Run workflow** in the Actions UI rather than wait. +anything time-sensitive, build and promote an updated release in the Actions UI. Production has no +automatic freshness guarantee while a release awaits review. ## Failure modes the build will surface diff --git a/docs/guides/test-the-workers-candidate.md b/docs/guides/test-the-workers-candidate.md index e5adba4c..bceadbd6 100644 --- a/docs/guides/test-the-workers-candidate.md +++ b/docs/guides/test-the-workers-candidate.md @@ -1,121 +1,79 @@ -# Test the Workers candidate +# Review and promote a website release -A candidate is a version of the production Worker that receives a versioned preview URL before -deployment. It uses the production bindings, so browser tests use non-destructive data. The public -hostnames stay on the previously deployed version until candidate checks pass. +Use the permanent preview site to review changes, then publish a selected saved release. A merge +into `main` updates staging and leaves the public website on its last promoted release. ## Check locally -Install the pinned toolchain and run the complete repository check: +Use the pinned Node and pnpm versions, then run: ```sh pnpm bootstrap pnpm check ``` -The check compiles the existing Pages Functions into one Worker, validates the upload bundle, and -starts it on an unused local port. A passing result includes `Worker parity checks passed.` +For interactive local inspection, run `pnpm -C apps/site worker:dev`. Local requests use local +storage. Missing integration secrets intentionally return `503 service_unavailable`. -For interactive inspection, build and start the Worker directly: +## Configure protected staging -```sh -pnpm -C apps/site worker:dev -``` +1. Open the `lvbt-website-preview` Worker in Cloudflare, then **Access**. +2. Configure Worker-specific protection with scope **All traffic** and the existing LVBT staff + Access policy. Do not enable an account-wide policy. +3. Create a dedicated preview verification service token. Add a **Service Auth** policy including + that exact token, and attach it to this Worker alongside the staff policy. +4. Save its client ID and secret in the website repository's `worker-preview` GitHub environment as + `CF_ACCESS_CLIENT_ID` and `CF_ACCESS_CLIENT_SECRET`. Use secret input prompts; never place + credentials in command arguments, documentation, or release artifacts. +5. Confirm the existing `CLOUDFLARE_WORKERS_API_TOKEN` environment secret and repository-level + `CLOUDFLARE_ACCOUNT_ID` variable are present. +6. After Access is configured, attach `preview.lasvegasfortransit.org` as a custom domain of + `lvbt-website-preview`. The matching configuration is in `apps/site/wrangler.jsonc`. -Open the printed local URL. Visit the home page, an ordinary content page, and an unknown path. -Submit only test payloads to local API routes; missing secrets intentionally return -`503 service_unavailable`. - -## Enable pull request previews - -Create the `worker-preview` GitHub environment: repository → **Settings → Environments → New -environment**, type `worker-preview`, then **Configure environment**. If it is already listed, open -it instead — nothing below needs redoing. - -Create its token: open `https://dash.cloudflare.com//api-tokens`, click **Create -Token**, then **Create Custom Token** → **Get started** (there is no ready-made template this -narrow). Name it `lvbt-website-preview (GitHub Actions)`. Under **Permissions**, add one row: -**Account · Workers Scripts · Edit** — this uploads Worker versions but cannot touch a zone's routes -or DNS. Under **Account Resources**, choose **Include** and the LVBT account. Leave **Zone -Resources** at its default. Leave the TTL empty. Click **Continue to summary**, then **Create -Token**, and copy it: Cloudflare shows it only once. - -Under the `worker-preview` environment's **Environment secrets**, click **Add environment secret**, -name it `CLOUDFLARE_WORKERS_API_TOKEN`, and paste the token — or run -`gh secret set CLOUDFLARE_WORKERS_API_TOKEN --env worker-preview` and paste it at the prompt. -`CLOUDFLARE_ACCOUNT_ID` is not secret and is read by jobs that declare no environment at all (the -fork-safety check in `deploy-preview.yml`), so add it once as a plain repository variable instead: -**Settings → Secrets and variables → Actions → Variables → New repository variable**, or -`gh variable set CLOUDFLARE_ACCOUNT_ID`. Skip creating it again if it already exists — every -workflow that needs a Cloudflare account ID reads this same one. - -Set the repository variable `CLOUDFLARE_WORKERS_PREVIEW_ENABLED` to `true` after -`pnpm -C apps/site worker:upload --env preview` succeeds for `lvbt-website-preview`, the separate -Worker that pull request previews use. Re-run the pull request workflow and open the -`Worker candidate` link in its comment. - -The preview workflow runs the complete Playwright suite against the Worker URL. The same checks run -locally against an uploaded candidate: +Anonymous requests must reach Access rather than the website. Staff should see the site after +signing in. Automation uses the service token; it does not require an interactive browser login. +Preview integrations use separate test credentials and the preview database. -```sh -pnpm -C apps/site worker:test:live \ - --pages https://lasvegasfortransit.org \ - --worker https://-lvbt-website-preview..workers.dev \ - --skip-api -PLAYWRIGHT_BASE_URL=https://-lvbt-website-preview..workers.dev \ - pnpm -C apps/site worker:test:browser -``` +## Review a pull request + +Open the Worker URL in the pull request comment and sign in through Access. The URL represents that +PR's uploaded version; it does not move the permanent preview site. Fork PRs receive no deployment +secrets. + +Inspect phone and desktop layouts, navigation, nested routes, event calendars, and the branded 404 +page. Use test data for preview APIs. Browser contract checks run automatically through Access. +Visual and accessibility audits also remain available through the ordinary audit workflow. + +## Review staging + +After `Deploy staging` succeeds, open [the preview site](https://preview.lasvegasfortransit.org). +Record the commit and release ID from its Actions summary. `/lvbt-release.json` shows which release +the domain currently serves; record its originating Actions run ID for promotion. + +A new main build can replace this domain while review is ongoing. Review the version URL recorded in +that run's upload step when you need to return to the selected build. + +## Publish the reviewed release + +1. Open **Actions → Promote website release → Run workflow** in the website repository. +2. Select the `main` branch. +3. Enter the successful **Deploy staging Actions run ID** you reviewed. +4. Run the workflow and satisfy any GitHub environment approval required by the repository. + +The workflow rejects invalid source runs, downloads the saved artifact, verifies every file, uploads +a candidate with production bindings, and runs browser checks. It activates the candidate's exact +version ID only after those checks pass. It does not rebuild or promote preview data. + +After success, verify [the public site](https://lasvegasfortransit.org), a nested page, an event +calendar, and the `www` redirect. Check `/lvbt-release.json` against the selected release and the +recorded production version in the workflow summary. + +## Refresh calendar content or roll back + +For a calendar correction, run **Deploy staging**, review the refreshed events, then promote its +run. Scheduled calendar refreshes also stop at staging. -Inspect the navigation at phone and desktop widths. Check the browser console, refresh a nested -route, follow the sitemap redirect, open an event calendar file, and exercise each API with test -credentials. Confirm Cloudflare Web Analytics does not record the preview hostname. - -## Verify a main candidate - -Create a separate `worker-candidate` GitHub environment the same way: **Settings → Environments → -New environment**, type `worker-candidate`, **Configure environment**. It needs its own -`CLOUDFLARE_WORKERS_API_TOKEN` environment secret — create a second custom token exactly as above -(**Account · Workers Scripts · Edit**, scoped to the LVBT account; name it -`lvbt-website candidate (GitHub Actions)` so it reads differently from the preview one in the token -list) and add it under this environment's **Environment secrets**. It reads the same -`CLOUDFLARE_ACCOUNT_ID` repository variable created above — do not make a second copy. Set the -repository variable `CLOUDFLARE_WORKERS_CANDIDATE_ENABLED` to `true` only after the preview workflow -passes. - -Each successful `Deploy production` build starts `Deploy Worker candidate` for the same commit. The -workflow uploads a version with the stable `candidate` preview alias and runs the browser suite. -With `LVBT_WORKERS_PRODUCTION_ENABLED=true`, it deploys that version only after confirming that -`main` still points at the tested commit. Use **Run workflow** on `main` to repeat the release -without another build trigger. - -Record the commit, version, and preview URL from the workflow summary with the release. - -## Check production - -After `Deploy Worker candidate` succeeds, compare `https://lasvegasfortransit.org` with the version -URL recorded in its workflow summary. Check `https://www.lasvegasfortransit.org` redirects to the -apex over valid TLS. Follow a content link, refresh a nested page, inspect an unknown path, and -check an event calendar file. Confirm that production includes the Cloudflare Web Analytics beacon -while version previews do not. - -The `lvbt-website` Worker owns both public hostnames as custom domains. The Pages project has no -custom-domain attachment. Its `lvbt-website-5zh.pages.dev` address remains available for emergency -rollback; an ordinary release regression rolls back the Worker version without changing DNS. The -[deployment pipeline](../reference/deployment-pipeline.md#rollback) records the recovery path. - -## Record acceptance - -Record the commit, Worker version, preview URL, and check time in the release record. Compare these -behaviors before and after deployment: - -| Surface | Required result | -| --------------------- | --------------------------------------------------------------------- | -| `/` and content pages | Status, HTML, canonical metadata, and navigation match the candidate | -| unknown path | Branded `404.html` with status 404 | -| `_headers` | Security and cache headers match the candidate | -| `_redirects` | Every permanent redirect returns 301 to the same location | -| `/events/*.ics` | `text/calendar; charset=utf-8` | -| `/api/*` | Status, CORS, validation, and downstream behavior match the candidate | -| analytics | Production hostname included; preview hostname excluded | - -Keep the previous Worker version and the Pages fallback available until the production check passes. +To republish an older release, select its successful staging run while its artifact is retained. For +urgent recovery using a recorded Worker version, follow the +[rollback reference](../reference/deployment-pipeline.md#rollback). Never rebuild an expired +artifact and treat the new output as the previously reviewed release. diff --git a/docs/reference/deployment-pipeline.md b/docs/reference/deployment-pipeline.md index 14ff73a9..ccfa4870 100644 --- a/docs/reference/deployment-pipeline.md +++ b/docs/reference/deployment-pipeline.md @@ -1,119 +1,117 @@ # Deployment pipeline -GitHub Actions owns builds and deployment. A change is built from a clean checkout, validated, and -sent to Cloudflare with the package and Wrangler versions recorded in the repository. - -The `lvbt-website` Worker serves `lasvegasfortransit.org` and `www.lasvegasfortransit.org` through -Cloudflare custom domains. The former Pages project stays available at `lvbt-website-5zh.pages.dev` -as a rollback artifact; it owns neither public hostname. - -## Build contract - -`pnpm build` creates the static site in `apps/site/dist/`, builds the Pagefind index, and compiles -`apps/site/functions/` into `apps/site/.wrangler/worker/index.js`. Wrangler serves the static tree -through the `ASSETS` binding and invokes the Worker first only for `/api/*`. - -`pnpm check` covers the vendored LVBT standard, formatting, Markdown lint, the organization's -repository-shape rules, lint, type checks, unit tests, documentation links, the production build, -generated Worker binding types, a Wrangler dry run, and local Worker parity. Turborepo runs the -site's part of it in the order `apps/site/turbo.json` declares; the build sees the `PUBLIC_LVBT_*` -and analytics variables that file lists, and no others. The parity check starts the built Worker on -an unused local port and verifies: - -- the home page and branded 404 response; -- security headers and the calendar MIME override; -- the permanent `/get-involved` redirect; -- execution of the compiled subscription API. - -The checked Worker configuration lives in `apps/site/wrangler.jsonc`. Production custom domains are -attached to the existing Worker in Cloudflare. Version uploads do not change those domains, and the -deployment token cannot edit DNS or routes. - -## Pull requests - -Every pull request receives ordinary validation. Same-repository pull requests also receive Pages -and Worker previews. - -The `Deploy Worker preview` workflow runs when the repository variable -`CLOUDFLARE_WORKERS_PREVIEW_ENABLED` is `true`. Its token comes only from the `worker-preview` -GitHub environment. The workflow uploads a version of the separate `lvbt-website-preview` Worker -without deploying it. That Worker is the `preview` environment in `apps/site/wrangler.jsonc` and -uses the preview platform database, so test data never reaches the production database. The workflow -verifies the versioned preview URL, runs the Playwright accessibility and visual suites against the -edge deployment, and updates one pull request comment. Forks never receive the token. The Pages -comparison runs only when Workers production is disabled. - -The preview environment contains: - -| Setting | Kind | Purpose | -| ------------------------------------ | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `CLOUDFLARE_ACCOUNT_ID` | repository variable | Selects the LVBT Cloudflare account. It is a repository variable, not scoped to this environment, because jobs with no environment of their own (the fork-safety check in `deploy-preview.yml`) also read it | -| `CLOUDFLARE_WORKERS_API_TOKEN` | environment secret | Uploads versions for `lvbt-website` and `lvbt-website-preview` without editing zones | -| `CLOUDFLARE_WORKERS_PREVIEW_ENABLED` | repository variable | Enables the candidate workflow after credentials are verified | - -See [test the Workers candidate](../guides/test-the-workers-candidate.md) for the exact clicks to -create the environment and its token. - -Application secrets bind directly to the Worker before endpoint acceptance. Build-time -`PUBLIC_LVBT_*` variables remain GitHub Actions variables and are included in the generated HTML. - -## Platform database - -The Organizing Platform keeps its data in Cloudflare D1 (a SQL database; see the -[glossary](./glossary.md#d1)). Code reaches it through the `PLATFORM_DB` -[binding](./glossary.md#binding), which `apps/site/wrangler.jsonc` points at a different database -for each Worker: - -| Worker | Used by | Database | -| ---------------------- | --------------------------------------------- | ----------------------- | -| `lvbt-website` | production, and the `main` candidate versions | `lvbt-platform` | -| `lvbt-website-preview` | pull request previews | `lvbt-platform-preview` | - -Both databases are on the free plan in Western North America. `pnpm dev` and -`pnpm -C apps/site worker:dev` use a local copy that Wrangler keeps in `apps/site/.wrangler/`, so -local work never touches either one. The -[platform decision record](../explanation/decisions/organizing-platform.md) explains why there is -one database. - -## Production - -`Deploy production` builds `main`. With `LVBT_WORKERS_PRODUCTION_ENABLED=true`, its Pages job is -skipped. The Pages project remains online at its `pages.dev` address but receives no new production -builds. - -After a successful build, `Deploy Worker candidate` uploads the same `main` commit as a versioned -Worker. It runs the browser acceptance suite, records the commit, version, and preview URL, and -confirms that `main` still points at the verified commit before deploying that version. The final -check compares the production hostname with the version preview. A manual run is accepted only from -`main`. - -The `worker-candidate` GitHub environment contains `CLOUDFLARE_WORKERS_API_TOKEN`, scoped to the -LVBT account with Workers Scripts Edit permission. It reads the account ID from the repository-level -`CLOUDFLARE_ACCOUNT_ID` variable. The separate `worker-preview` environment has its own token. -Neither token can change zone DNS or Worker routes. The repository-level `CLOUDFLARE_API_TOKEN` -remains for Pages pull request previews; it is not used for production Worker deployment. - -Cloudflare owns the DNS records and certificates for the two Worker custom domains. The former `/*` -overlay routes and Pages custom-domain associations are absent. The Pages deployment remains -reachable through its `pages.dev` address for emergency recovery. +GitHub Actions builds one release from `main`, deploys it to protected staging, and stores its files +for explicit production promotion. Merging a pull request updates staging; it does not publish the +public website. + +## Hosts and environments + +| Host | Worker | Data | Update trigger | +| --------------------------------------------------------- | ---------------------------------- | ----------------------- | -------------------------- | +| `lasvegasfortransit.org` and `www.lasvegasfortransit.org` | `lvbt-website` | `lvbt-platform` | Explicit promotion | +| `preview.lasvegasfortransit.org` | `lvbt-website-preview` | `lvbt-platform-preview` | Successful main build | +| Versioned PR Worker URLs | Versions of `lvbt-website-preview` | `lvbt-platform-preview` | Same-repository PR updates | + +Staging and PR previews share a preview database. They do not have a separate database per PR. +Production secrets remain bound to the production Worker. Preview integrations need separate test +credentials; missing credentials make affected endpoints unavailable. Promotion moves code and +assets, never databases or test records. + +The former Pages project remains at `lvbt-website-5zh.pages.dev` as an older public recovery option. +New Pages PR deployments are disabled so they cannot expose an unprotected copy of private work. + +## Build and artifact contract + +`pnpm check` covers the repository standard, formatting, lint, types, tests, documentation links, +production build, generated Worker types, Wrangler dry run, and local Worker parity. `pnpm build` +writes static assets and Pagefind to `apps/site/dist/` and compiled Pages Functions to +`apps/site/.wrangler/worker/`. + +`Deploy staging` retains a `website-release-` Actions artifact for 90 days. It contains only +the compiled Worker, static assets, Wrangler configuration, and `release.json`. The manifest records +the commit, release ID, sorted file inventory, and SHA-256 hashes. The `/lvbt-release.json` response +identifies the deployed release. Prototype routes and the audit-only language cannot enter a +promotable artifact. + +Promotion downloads this artifact from its originating run, verifies its identity and all files, and +uploads a temporary copy with bundling disabled. It performs no site or Worker rebuild. Production +and staging have different Worker version IDs because their bindings differ; their compiled code and +asset files come from the same artifact. + +## Cloudflare Access + +The preview Worker must have a Worker-specific Access policy covering **All traffic**. This covers +the custom staging domain and versioned PR URLs without protecting unrelated public Workers. Staff +authenticate with the existing LVBT staff identity policy. Automation uses a dedicated Access +service token in a **Service Auth** policy attached only to this Worker. + +The `worker-preview` GitHub environment supplies: + +| Secret | Purpose | +| ------------------------------ | --------------------------------------------- | +| `CLOUDFLARE_WORKERS_API_TOKEN` | Upload and activate preview versions | +| `CF_ACCESS_CLIENT_ID` | Identify the preview verification service | +| `CF_ACCESS_CLIENT_SECRET` | Authenticate the preview verification service | + +`CLOUDFLARE_ACCOUNT_ID` remains a repository variable. Access credentials are supplied only to the +specific preview origin. HTTP verification disables automatic redirects; browser verification +fetches authenticated responses without following redirects, then lets the browser navigate the +returned response. Third-party origins receive no Access headers. + +Preview responses carry `X-Robots-Tag: noindex, nofollow, noarchive` and +`Cache-Control: private, no-store`. Preview runs the Worker before every asset request to apply +those headers. Production uses selective Worker routing for application endpoints and ordinary asset +delivery for static pages. The shared analytics package enables tracking only on the public apex and +`www` hostname, so the production-shaped artifact sends no analytics on staging or PR URLs. + +A failed staging job can be retried with **Re-run failed jobs**. Each run ID identifies one +immutable artifact; **Re-run all jobs** cannot replace an artifact that already exists. Dispatch a +new staging run when a rebuild is needed. + +## Main and PR deployment + +Main pushes and scheduled calendar rebuilds run `Deploy staging`. The workflow validates and saves +the artifact, uploads a preview version, checks anonymous denial and authenticated browser +rendering, runs browser contract checks, activates that exact preview version, and checks the +permanent staging hostname. Its summary records the release, commit, version, and originating run. + +Same-repository PRs use `Deploy Worker preview` when `CLOUDFLARE_WORKERS_PREVIEW_ENABLED=true`. It +uploads a preview version, verifies Access and browser contracts, and comments its URL on the PR. PR +uploads never activate a version or move the permanent staging domain. Forks receive no Cloudflare +or Access secrets. PRs may include preview-only prototype pages; those builds cannot be promoted. + +## Production promotion + +`Promote website release` accepts the originating **Actions run ID**, and runs only from `main`. It +requires a completed, successful `Deploy staging` run from this repository on `main`, triggered by a +push or manual dispatch. PR runs, old automatic production runs, failed runs, and foreign +repositories are rejected. + +The workflow downloads the selected run's artifact, verifies it against the recorded commit, uploads +a production candidate, checks its release marker and browser contracts, and activates its exact +version ID. It then checks the public release marker and rendered page. Staging and `main` may +advance during this process without changing the selected artifact. + +The existing `worker-candidate` GitHub environment supplies the production Workers token. Its +historical name is retained to reuse the established credential scope. Environment reviewers may +provide an additional publication gate. Dispatching this workflow is the explicit publication +request; nothing automatically dispatches it. ## Rollback -For a Worker release regression, `wrangler rollback --message ` sends all -Worker traffic to the recorded version without changing custom domains or DNS. Verify both public -hostnames afterward. +A previous successful staging run can be promoted again while its artifact is retained. For urgent +Worker recovery, use `wrangler rollback ` with the recorded production version and +verify both public hostnames. Deleted or incompatible data bindings can prevent rollback; schema +migrations require separate compatible rollout planning. -If the Worker itself cannot serve traffic, remove each Worker custom domain, restore the Pages -custom-domain association and its proxied Pages CNAME, then verify TLS and the site contract on both -hostnames. The Pages deployment remains accessible at `lvbt-website-5zh.pages.dev` throughout this -procedure. Restoring Pages changes routing and requires a separate incident decision; it is not the -response to an ordinary bad release. +If the Worker itself cannot serve traffic, restoring the older Pages project requires a separate +routing decision and DNS/custom-domain changes. Normal release rollback changes no domains. Expired +Actions artifacts cannot be silently rebuilt and presented as reviewed releases. Build a new release +and review it, or use an already recorded compatible Worker version. -The rollback version must retain every binding used by that release. Deleted or incompatible storage -bindings prevent Cloudflare from applying an older version. +## Caching and external content -## Caching - -HTML revalidates on every request. Astro gives changed static assets new hashed filenames, while -`_headers` assigns long-lived immutable caching to `/_astro/*` and `/fonts/*`. Normal releases do -not require a cache purge. +Production HTML revalidates; hashed assets retain their existing immutable cache headers. Staging is +private and does not cache responses. Calendar, newsletter, and other build-time content is captured +in the saved release. Scheduled refreshes update staging only; public content becomes current when +an updated release is promoted. diff --git a/docs/superpowers/specs/2026-10-01-staging-promotion.md b/docs/superpowers/specs/2026-10-01-staging-promotion.md new file mode 100644 index 00000000..5e28c86d --- /dev/null +++ b/docs/superpowers/specs/2026-10-01-staging-promotion.md @@ -0,0 +1,33 @@ +# Website staging and release promotion + +## Purpose + +Give maintainers a persistent, protected website at `preview.lasvegasfortransit.org` and an explicit +way to publish the release they reviewed to `lasvegasfortransit.org`. + +## Release contract + +- Successful builds of `main` update staging after repository validation. +- Each release records its commit, originating Actions run, file inventory, and SHA-256 digest. +- Store the production-shaped static assets and compiled Worker together. Promotion verifies and + reuses those files, even after a newer release updates staging. +- Staging uses the existing preview database and Cloudflare Access. Production uses its own database + and secrets. Test data and Access settings never travel with the release. +- Suppress analytics and indexing through staging runtime settings, keeping release files identical. +- Production deployment requires an explicit Actions dispatch naming a successful staging run. +- Validate the production candidate before activating its exact Worker version; verify afterward. +- PR previews continue independently. Automated HTTP and browser checks authenticate only to their + intended preview origin, never forwarding Access credentials to external links or redirects. +- Existing production rollback remains available. Database migrations require separate review. + +## Delivery boundaries + +Configure Access before exposing staging. Reuse the organization's existing identity provider and +reviewer policy where available. Keep service credentials out of source, artifacts, and logs. + +## Acceptance + +Tests reject modified or incomplete artifacts, unsafe paths, untrusted workflow runs, and unpinned +promotion. Live staging denies anonymous access and serves the recorded release to authorized +reviewers and automated checks. Production remains public and changes only through explicit +promotion. The approved production version is recorded for verification and rollback. From b6bb005c6343bd7852b960e6056e42c714bc8d2c Mon Sep 17 00:00:00 2001 From: Willie Chalmers III Date: Sat, 3 Oct 2026 17:31:28 -0700 Subject: [PATCH 2/2] feat(site): publish the Arts District neighborhood campaign Introduce the campaign overview and four gathering pages, with a community listening session on October 17 at Barter Beer + Mall. Explain the neighborhood planning work and invite help with community partnerships, outreach, and event resources. Keep future gathering topics clearly proposed and support links to standard LVBT event pages for RSVP when those pages are available. Co-authored-by: Codex --- .../campaigns/arts-district-sign-2015.jpg | Bin 0 -> 151307 bytes apps/site/src/lib/campaigns/arts-district.ts | 85 ++ .../src/pages/campaigns/arts-district.astro | 878 ++++++++++++++++++ .../arts-district/events/[slug].astro | 88 ++ apps/site/src/pages/projects/index.astro | 12 + 5 files changed, 1063 insertions(+) create mode 100644 apps/site/public/images/campaigns/arts-district-sign-2015.jpg create mode 100644 apps/site/src/lib/campaigns/arts-district.ts create mode 100644 apps/site/src/pages/campaigns/arts-district.astro create mode 100644 apps/site/src/pages/campaigns/arts-district/events/[slug].astro diff --git a/apps/site/public/images/campaigns/arts-district-sign-2015.jpg b/apps/site/public/images/campaigns/arts-district-sign-2015.jpg new file mode 100644 index 0000000000000000000000000000000000000000..50cdbef684da8c709f89d6800134f141ec865b24 GIT binary patch literal 151307 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%0Di2ut+?q=B$YV!Q`~x1jlHX;>HT$T(YG;s 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%0A;x;`auYF9r2PBo=Qm|^R8*FtaG)to48MABBASR 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 Cp&Fh5 literal 0 HcmV?d00001 diff --git a/apps/site/src/lib/campaigns/arts-district.ts b/apps/site/src/lib/campaigns/arts-district.ts new file mode 100644 index 00000000..406e0aab --- /dev/null +++ b/apps/site/src/lib/campaigns/arts-district.ts @@ -0,0 +1,85 @@ +export interface ArtsDistrictEvent { + slug: string; + title: string; + month: string; + year: string; + summary: string; + details: string[]; + proposed?: boolean; + start?: string; + dateLabel?: string; + venue?: string; + venueUrl?: string; + // Add the associated standard event page once it is available. + lvbtEventUrl?: `/events/${string}` | `https://lasvegasfortransit.org/events/${string}`; +} + +export const artsDistrictEvents: readonly [ArtsDistrictEvent, ...ArtsDistrictEvent[]] = [ + { + slug: 'community-listening-session', + title: 'Community listening session', + month: 'October', + year: '2026', + start: '2026-10-17T14:00:00-07:00', + dateLabel: 'Saturday, October 17, 2026 · 2 p.m.', + venue: 'Barter Beer + Mall', + venueUrl: 'https://maps.app.goo.gl/N2YVJTrV4GtqTSn36', + summary: + 'Share what’s difficult about living, working, or spending time in the Arts District. We’ll listen to your experiences and work out which problems the campaign should address first.', + details: [ + 'Bring your experiences, questions, and ideas. Tell us about the bus trip that takes too long, the crossing you avoid, or the cost of parking—and how homes, shops, and public spaces meet your everyday needs.', + 'This first gathering is about understanding the problems before deciding on solutions. Whether you live here, work here, make art here, run a business, or represent a community organization, your experience can help us set the campaign’s priorities.', + ], + }, + { + slug: 'routes-and-connections', + proposed: true, + title: 'Routes and connections', + month: 'November', + year: '2026', + summary: 'Tell us which trips are difficult and what would make them easier.', + details: [ + 'Bring a trip you’d like to make more easily: getting to work, running an errand, going to school, or meeting someone in the neighborhood. We’ll look at the bus routes, walks, and connections involved.', + 'We’ll discuss which destinations are hard to reach and what would make getting to, from, and around the Arts District easier.', + ], + }, + { + slug: 'neighborhood-priorities', + proposed: true, + title: 'Neighborhood priorities', + month: 'December', + year: '2026', + summary: + 'Compare possible improvements to transit, streets, and land use, and help decide what matters most.', + details: [ + 'We’ll bring the problems raised at earlier gatherings into a discussion about possible improvements. Help compare the options and explain which would make the biggest difference in your day-to-day life.', + 'That includes transit and streets, as well as how homes, businesses, and public spaces fit together. Your feedback will help decide what belongs in the neighborhood plan.', + ], + }, + { + slug: 'review-the-plan', + proposed: true, + title: 'Review the plan', + month: 'January', + year: '2027', + summary: + 'Look over the draft neighborhood plan and tell us what works, what’s missing, and what should change.', + details: [ + 'Read through the draft plan with us and see how it responds to the problems people raised. Point out what needs more work or what we’ve missed.', + 'We’ll use your feedback to refine the recommendations before sharing them with transportation and development decision-makers.', + ], + }, +]; + +export function artsDistrictEventPath(event: Pick): string { + return `/campaigns/arts-district/events/${event.slug}/`; +} + +export interface ArtsDistrictDocument { + title: string; + summary: string; + url: string; +} + +// Add links as gathering summaries, proposals, and the neighborhood plan are published. +export const artsDistrictDocuments: readonly ArtsDistrictDocument[] = []; diff --git a/apps/site/src/pages/campaigns/arts-district.astro b/apps/site/src/pages/campaigns/arts-district.astro new file mode 100644 index 00000000..dcade0ad --- /dev/null +++ b/apps/site/src/pages/campaigns/arts-district.astro @@ -0,0 +1,878 @@ +--- +import BaseLayout from '../../layouts/BaseLayout.astro'; +import { + artsDistrictDocuments, + artsDistrictEvents, + artsDistrictEventPath, +} from '../../lib/campaigns/arts-district'; + +const [firstEvent, ...laterEvents] = artsDistrictEvents; +--- + + +
+
+ +
+ The 18b Arts District sign on Casino Center Boulevard +
+ Casino Center Boulevard · 18b + + Photo: Diandra Rodriguez +  · CC BY 2.0 + +
+
+
+
+
+

About the campaign

+

+ Las Vegans for Better Transit is bringing residents, workers, artists, businesses, and + community organizations together to make a neighborhood plan for the Arts District. We’ll + start with the problems you experience, work through possible improvements, and decide + together what the neighborhood needs. +

+

+ Our mission is to advocate for world-class public transit and supportive land use so we + can build better neighborhoods and stronger communities. Here, that means looking at bus + service, streets, homes, shops, and public spaces together. The people who use them every + day should help decide how they change. +

+
+
+

More ways to get here

+ Whether you’re heading to work, a gallery, or First Friday, getting here shouldn’t + require a car. Parking costs and limited transit options can make everyday trips + difficult. Better connections can make it easier to reach the neighborhood and get + around. +

+
+
+

A say in what changes

+ Tell us where a trip breaks down, what feels unsafe, or what’s missing nearby. We’ll + use those experiences to develop recommendations for transit, streets, and land use, + then share the neighborhood plan with the people who make transportation and + development decisions. +

+
+
+
+
+
+
+
+

Upcoming events

+

+ We’ll meet about once a month to talk through the problems you face, explore + improvements, and review a neighborhood plan together. +

+
+
+
+ +
+

Proposed topics for future gatherings

+
+ { + laterEvents.map((event) => ( + + )) + } +
+
+
+

Community priorities and proposals

+

+ You’ll find summaries from our gatherings, the priorities people raise, and the draft + neighborhood plan here. Read through the proposals and let us know what we’ve missed or what + should change before we share them with transportation and development decision-makers. +

+ { + artsDistrictDocuments.length > 0 && ( + + ) + } +
+
+
+

Help organize the campaign

+

+ You can help bring more people into these conversations. Partner with us through your + community organization or business, volunteer with the campaign, or help make the + gatherings happen. +

+
    +
  • Connect us with community organizations and local businesses.
  • +
  • Invite neighbors, coworkers, and artists to join the conversation.
  • +
  • Help organize gatherings and keep outreach going between events.
  • +
  • Provide food, help with publicity, or share other resources for events.
  • +
+
+ +
+
+ + diff --git a/apps/site/src/pages/campaigns/arts-district/events/[slug].astro b/apps/site/src/pages/campaigns/arts-district/events/[slug].astro new file mode 100644 index 00000000..cfc3e5ad --- /dev/null +++ b/apps/site/src/pages/campaigns/arts-district/events/[slug].astro @@ -0,0 +1,88 @@ +--- +import BaseLayout from '../../../../layouts/BaseLayout.astro'; +import { buttonClass } from '../../../../components/app/styles'; +import { + artsDistrictEvents, + type ArtsDistrictEvent, +} from '../../../../lib/campaigns/arts-district'; + +export function getStaticPaths() { + return artsDistrictEvents.map((event) => ({ params: { slug: event.slug }, props: { event } })); +} + +interface Props { + event: ArtsDistrictEvent; +} +const { event } = Astro.props; +--- + + +
+ Arts District Neighborhood Campaign + {event.proposed &&

Proposed gathering

} +

{event.title}

+

{event.summary}

+
+
+
+ {event.proposed ? 'Proposed month' : 'When'} +
+
+ { + event.start ? ( + + ) : ( + <> + {event.month} {event.year} + Date to be announced + + ) + } +
+
+
+
Where
+
{event.venue ?? 'Location to be announced'}
+
+
+ { + (event.lvbtEventUrl || event.venueUrl) && ( +
+ {event.lvbtEventUrl && ( + + Event details and RSVP + + )} + {event.venueUrl && ( + + Get directions + + )} +
+ ) + } +
+
+

+ {event.proposed ? 'What we’d like to discuss' : 'What we’ll discuss'} +

+
+ {event.details.map((paragraph) =>

{paragraph}

)} +
+
+
+

Help organize this gathering

+

+ Help invite neighbors, connect us with a community organization, or contribute food, + publicity, and other resources for the gathering. +

+ Help organize the campaign +
+
diff --git a/apps/site/src/pages/projects/index.astro b/apps/site/src/pages/projects/index.astro index 7416106f..7b5a44c3 100644 --- a/apps/site/src/pages/projects/index.astro +++ b/apps/site/src/pages/projects/index.astro @@ -56,6 +56,18 @@ const renderedInitiatives = initiatives.filter( +
+

+ + Arts District Neighborhood Campaign + +

+

+ Help identify the transit and street improvements the neighborhood needs. Our first gathering + is October 17 at 2 p.m. at Barter Beer + Mall. +

+
+ { /* Each initiative's inline header IS the sticky element — same DOM node morphs from expanded (display title + lede) to a