GitHub Action Deployment

This repository uses .github/workflows/deploy.yml for pull-request validation and production deployment. The workflow is designed for a static SPA that depends on a private photo source repository during build time.

Triggers

The workflow runs on:

  • pull requests targeting main
  • pushes to main that touch app, package, script, workflow, or project configuration paths
  • workflow_dispatch
  • repository_dispatch with type photos-updated, sent by Jackyhq/Photography-Photos

Pull requests use a pr-<number> concurrency group and cancel stale runs. Non-PR deployments use the deploy concurrency group.

Pull-Request Validation

The validate job is intentionally independent of private media and deployment credentials. It sets fixture-specific storage variables, then:

  1. Checks out this source repository.
  2. Installs pnpm 10.19.0, Node.js 24, and frozen workspace dependencies.
  3. Runs pnpm run fixtures:photos to create public synthetic photos under apps/web/public/__fixtures/photos.
  4. Runs pnpm run photos:standardize against that fixture.
  5. Runs pnpm run build:manifest -- --force --strict.
  6. Runs pnpm run lint:check and pnpm run type-check.
  7. Runs pnpm run test:coverage so unit tests and coverage gates share one command.
  8. Builds the documentation site and production web app.
  9. Checks the bundle budget, which also requires the current build output.
  10. Installs the Chromium headless shell and runs the full desktop/mobile suite with PLAYWRIGHT_PRODUCTION=true pnpm run test:e2e.

PR validation never checks out Jackyhq/Photography-Photos, reads deployment secrets, pushes photo changes, syncs R2, or mirrors deployment output.

Production Validation

The build-and-deploy job first verifies that every required deployment secret is present. It then checks out Jackyhq/Photography-Photos into ./photos, rejects every symlink outside the nested .git directory, and runs the real-photo pipeline:

  1. Install frozen dependencies and run pnpm run lint:check.
  2. Run pnpm run photos:standardize.
  3. Run pnpm run build:manifest -- --strict.
  4. Run pnpm run type-check and pnpm run test:coverage.
  5. Build the documentation site and production web app.
  6. Check the bundle budget and web output.
  7. Prepare the Google sitemap alias and README preview image.
  8. Run PLAYWRIGHT_PRODUCTION=true pnpm run test:e2e:smoke against the real-photo build.

The web build uses AFILMORY_SKIP_MANIFEST_PRECHECK=true because the strict manifest build has already completed explicitly.

The deployment browser suite selects @smoke tests: normal gallery click and viewer close, public manifest and no-JavaScript photo/video pages, and missing-route metadata. These run on desktop and mobile. They use bundled thumbnails or video posters and do not require newly uploaded R2 originals, because R2 synchronization happens after validation.

The full fixture suite retains keyboard navigation, focus, filtering, sharing, language switching, Live Photo behavior, original-load failures, and metadata history regression checks. Smoke tests run there too. Lint, types, unit coverage, and the docs build remain in production validation because direct pushes, manual runs, and photo dispatches cannot assume the deployed source was validated by a PR.

Playwright media preferences are configured through use.contextOptions. The shared gallery helper checks the browser's reduced-motion preference before a single normal click; it does not retry clicks or wait for an arbitrary animation timeout.

Deployment-Only Steps

Pushes to main, manual runs, and photo-repository dispatches run the build-and-deploy job. External writes start only after every validation step above succeeds. The job then:

  1. Commits standardized photo changes back to Jackyhq/Photography-Photos with [skip dispatch].
  2. Syncs ./photos to Cloudflare R2 under the photos/ prefix.
  3. Checks out Jackyhq/Photography-Web and mirrors apps/web/dist/ into it.
  4. Commits and pushes deployment output only when the mirrored repository changed.

Required Secrets

The source repository requires:

  • PHOTO_REPO_TOKEN: fine-grained PAT with Contents access to Jackyhq/Photography-Photos.
  • DEPLOY_REPO_TOKEN: token used to push static output to Jackyhq/Photography-Web.
  • CLOUDFLARE_R2_ACCESS_KEY_ID: Cloudflare R2 S3 API access key.
  • CLOUDFLARE_R2_SECRET_ACCESS_KEY: Cloudflare R2 S3 API secret key.
  • CLOUDFLARE_R2_ENDPOINT: R2 S3 API endpoint, such as https://<account_id>.r2.cloudflarestorage.com.
  • CLOUDFLARE_R2_BUCKET: bucket name only, such as jackywhq.

The private photo repository requires MAIN_REPO_DISPATCH_TOKEN, a token that can send repository_dispatch events to Jackyhq/Photography.

Cloudflare R2 Sync

The workflow separates bucket name, object prefix, and public URL:

  • CLOUDFLARE_R2_BUCKET is the bucket name only.
  • R2_PHOTOS_PREFIX=photos makes the upload target s3://<bucket>/photos/.
  • CLOUDFLARE_R2_ENDPOINT is the S3 API endpoint.
  • https://photos3.jackyw.cn/photos/ is the public URL used in the manifest.

The sync command uses aws s3 sync ./photos "s3://${R2_BUCKET}/${R2_PHOTOS_PREFIX}/" --size-only --delete --no-follow-symlinks.

It excludes:

  • .git/
  • .github/
  • .gitignore
  • README.md
  • incoming/

--size-only avoids re-uploading unchanged photos when GitHub Actions creates a fresh checkout with new local timestamps. If a same-path replacement has a different file body but the exact same byte size, delete the affected object from R2 or change the file name before redeploying.

Permissions

The workflow defaults to contents: read. The deploy job expands permissions for production deployment:

  • contents: write

Output Targets

The primary build output is:

apps/web/dist/

The deploy job mirrors that output to:

Jackyhq/Photography-Web

Photo source files are not tracked in this public repository. The public site loads original photos from https://photos3.jackyw.cn/photos/, backed by Cloudflare R2.

Notes

  • photos/incoming/ is excluded from the manifest and R2 sync.
  • Photo-repository commits made by CI include [skip dispatch] to avoid deployment loops.
  • pnpm run build normally runs the web precheck; CI sets AFILMORY_SKIP_MANIFEST_PRECHECK=true after explicitly running the builder.
  • Keep workflow documentation synchronized with .github/workflows/deploy.yml.
Created At
Last Modified