Skip to content

Commit 736edb7

Browse files
Phase 2 aws infrastructure (#2)
* new infra * Add pnpm workspace * infra updates * updating infra stack * cdk infra updates * finished implementing static site infra * additional config setup * restructure * docs * new infra # Conflicts: # .gitignore # .idea/prettier.xml # .idea/vcs.xml * infra runbook update * update utility compony * fix og-image background * Fix resource naming
1 parent 4da4621 commit 736edb7

58 files changed

Lines changed: 4600 additions & 53 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,19 @@
1-
# .env
1+
# .env.example
22

3+
# --- Client-side (NEXT_PUBLIC_*) — embedded in static output, visible to browsers ---
34
NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=
45
NEXT_PUBLIC_APP_URL=http://localhost:3000
6+
NEXT_PUBLIC_AWS_REGION=
57

6-
# Optional - Cloudflare Web Analytics
8+
# Optional Cloudflare Web Analytics
79
NEXT_PUBLIC_CF_ANALYTICS_TOKEN=
10+
11+
# Optional — CloudWatch RUM (active after CDK deploys App Monitor + Cognito pool)
12+
NEXT_PUBLIC_CW_RUM_APP_MONITOR_ID=
13+
NEXT_PUBLIC_CW_RUM_IDENTITY_POOL_ID=
14+
15+
# Optional — Sentry error tracking
16+
NEXT_PUBLIC_SENTRY_DSN=
17+
18+
# --- Build-time only — never shipped to browsers ---
19+
AWS_REGION=

.github/CI-CD.md

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
[//]: # (.github/CI-CD.md)
2+
3+
## CI/CD & Automation
4+
5+
This repo uses GitHub Actions to build, audit, and deploy a static Next.js export to AWS S3 + CloudFront. The deploy workflow assumes the infrastructure stack already exists and reads its deploy targets from CloudFormation outputs.
6+
7+
### Workflow Layout
8+
9+
| Workflow | File | Trigger | Purpose |
10+
|----------|------|---------|---------|
11+
| CI | [`./workflows/ci.yml`](./workflows/ci.yml) | Push to `master`, pull request to `master`, manual dispatch | Orchestrates Actionlint, build, Lighthouse, and gated deploy |
12+
| Lint GitHub Actions | [`./workflows/lint-github-actions.yml`](./workflows/lint-github-actions.yml) | `workflow_call` | Installs pinned `actionlint` and validates workflow files |
13+
| Build Static Site | [`./workflows/build-static-site.yml`](./workflows/build-static-site.yml) | `workflow_call` | Builds the static site and uploads the deployable `dist/` artifact |
14+
| Lighthouse Static Site | [`./workflows/lighthouse-static-site.yml`](./workflows/lighthouse-static-site.yml) | `workflow_call` | Downloads the build artifact and runs Lighthouse CI against it |
15+
| Deploy Static Site | [`./workflows/deploy-static-site.yml`](./workflows/deploy-static-site.yml) | `workflow_call` | Downloads the artifact, deploys it to AWS, invalidates CloudFront, and creates the next deployment tag |
16+
17+
### Live CI Flow
18+
19+
The top-level CI workflow in [`./workflows/ci.yml`](./workflows/ci.yml) wires the reusable workflows together with the current repo defaults:
20+
21+
| Setting | Current value |
22+
|---------|---------------|
23+
| `AWS_REGION` | GitHub repo variable `vars.AWS_REGION` |
24+
| `CDK_STACK_NAME` | GitHub repo variable `vars.CDK_STACK_NAME` |
25+
| `APP_URL` | GitHub repo variable `vars.APP_URL` |
26+
| `ARTIFACT_NAME` | `dist` |
27+
| `TAG_PREFIX` | `v` |
28+
29+
Jobs run in this order:
30+
31+
| Job | What it does |
32+
|-----|--------------|
33+
| Actionlint | Checks out the repo, installs `actionlint` `v1.7.11` from the official release, and validates `.github/workflows/*.yml` |
34+
| Build | Checks out the repo, installs PNPM, sets up Node from [`.nvmrc`](../.nvmrc), runs `pnpm install --frozen-lockfile`, runs `pnpm build`, and uploads `dist/` |
35+
| Lighthouse | Downloads the same `dist/` artifact and runs `npx @lhci/cli autorun` |
36+
| Deploy | Runs only after Actionlint, Build, and Lighthouse succeed, and only for `push` or `workflow_dispatch` on `refs/heads/master` |
37+
38+
`actionlint` is intentionally scoped to workflow files only. It does not validate repo-local composite action metadata under `.github/actions/`.
39+
40+
### GitHub Actions Linting
41+
42+
Workflow linting is defined in [`./workflows/lint-github-actions.yml`](./workflows/lint-github-actions.yml).
43+
44+
| Step | What it does |
45+
|------|--------------|
46+
| Checkout | Checks out the repo so `actionlint` can inspect all local workflow files and reusable workflow calls |
47+
| Install actionlint | Downloads the pinned official release archive for the current Linux runner architecture and adds the extracted binary to `PATH` |
48+
| Run actionlint | Runs `actionlint` from repo root with default project discovery |
49+
50+
### Build Artifact
51+
52+
The deploy artifact is the static export in `dist/`.
53+
54+
- [`next.config.mjs`](../next.config.mjs) sets `output: 'export'` and `distDir: 'dist'`.
55+
- [`package.json`](../package.json) defines `pnpm build` as `tsx scripts/generate-og-images.tsx && next build --webpack`.
56+
- [`scripts/generate-og-images.tsx`](../scripts/generate-og-images.tsx) generates OG images into `public/og` before the static export runs.
57+
58+
### Lighthouse Behavior
59+
60+
Lighthouse CI is configured by [`.lighthouserc.js`](../.lighthouserc.js).
61+
62+
| Setting | Current value |
63+
|---------|---------------|
64+
| `staticDistDir` | `./dist` |
65+
| URLs audited | `http://localhost/index.html`, `http://localhost/blog/index.html` |
66+
| `numberOfRuns` | `3` |
67+
| Upload target | `temporary-public-storage` |
68+
69+
Current assertions:
70+
71+
| Category | Level | Minimum score |
72+
|----------|-------|---------------|
73+
| Performance | `warn` | `0.9` |
74+
| Accessibility | `error` | `0.9` |
75+
| Best Practices | `warn` | `0.9` |
76+
| SEO | `warn` | `0.9` |
77+
78+
The workflow always uploads `.lighthouseci/` as the `lighthouse-results` artifact, even when the job fails.
79+
80+
### Deployment Behavior
81+
82+
Deploy is defined in [`./workflows/deploy-static-site.yml`](./workflows/deploy-static-site.yml).
83+
84+
| Step | What it does |
85+
|------|--------------|
86+
| Checkout | Checks out the repo with `fetch-depth: 0` so tags are available |
87+
| Download artifact | Downloads the `dist/` artifact into `dist/` |
88+
| Configure AWS | Uses GitHub OIDC with `AWS_DEPLOY_ROLE_ARN` via `aws-actions/configure-aws-credentials@v4` |
89+
| Read stack outputs | Calls `aws cloudformation describe-stacks` for `cdk_stack_name` and reads `BucketName` and `DistributionId` |
90+
| Sync to S3 | Runs `aws s3 sync dist/ "s3://$BUCKET" --delete` |
91+
| Invalidate CloudFront | Runs `aws cloudfront create-invalidation --distribution-id "$DIST_ID" --paths "/*"` |
92+
| Tag deployment | Fetches tags, finds the latest matching semver tag, bumps the patch version, creates the new tag, and pushes it |
93+
94+
Additional live deploy behavior:
95+
96+
- Deploys are serialized with the `production-deploy` concurrency group.
97+
- `cancel-in-progress` is `false`.
98+
- This workflow deploys static assets only. It does not run `cdk deploy` or update infrastructure.
99+
100+
### Versioning
101+
102+
Successful deploys create the next patch tag matching `TAG_PREFIX`.
103+
104+
| Example | Result |
105+
|---------|--------|
106+
| No existing tags | `v0.0.1` |
107+
| Latest tag is `v1.0.0` | Next deploy creates `v1.0.1` |
108+
| Latest tag is `v1.4.9` | Next deploy creates `v1.4.10` |
109+
110+
If you manually create a higher semver tag, future deploys continue patch bumps from that latest tag.
111+
112+
List deployment tags:
113+
114+
```bash
115+
git tag -l 'v*' --sort=-v:refname
116+
```
117+
118+
### Inputs, Secrets, and Repo Variables
119+
120+
Reusable workflow interface:
121+
122+
| Workflow | Inputs | Required secrets |
123+
|----------|--------|------------------|
124+
| Lint GitHub Actions | None | None |
125+
| Build Static Site | `app_url`, `artifact_name` (default `dist`), `aws_region` | `NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME` |
126+
| Lighthouse Static Site | `artifact_name` (default `dist`) | None |
127+
| Deploy Static Site | `artifact_name` (default `dist`), `aws_region`, `cdk_stack_name`, `tag_prefix` (default `v`) | `AWS_DEPLOY_ROLE_ARN` |
128+
129+
Repo-level values used by the live pipeline:
130+
131+
| Kind | Name | Required | Used by | Purpose |
132+
|------|------|----------|---------|---------|
133+
| Secret | `NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME` | Yes | Build | Cloudinary cloud name embedded into the static site |
134+
| Secret | `AWS_DEPLOY_ROLE_ARN` | Yes | Deploy | IAM role ARN used for GitHub OIDC authentication |
135+
| Variable | `AWS_REGION` | Yes | Build, Deploy | Shared AWS region used by the reusable workflows |
136+
| Variable | `NEXT_PUBLIC_CF_ANALYTICS_TOKEN` | No | Build | Optional Cloudflare Web Analytics token |
137+
| Variable | `NEXT_PUBLIC_CW_RUM_APP_MONITOR_ID` | No | Build | Optional CloudWatch RUM App Monitor ID |
138+
| Variable | `NEXT_PUBLIC_CW_RUM_IDENTITY_POOL_ID` | No | Build | Optional CloudWatch RUM Cognito Identity Pool ID |
139+
| Variable | `NEXT_PUBLIC_SENTRY_DSN` | No | Build | Optional Sentry DSN |
140+
141+
Environment validation in the app:
142+
143+
- [`src/clientEnv.ts`](../src/clientEnv.ts) validates the `NEXT_PUBLIC_*` values used by the app.
144+
- [`src/buildEnv.ts`](../src/buildEnv.ts) currently defines only `AWS_REGION` and requires it explicitly if the module is imported into future build-time code.
145+
146+
### First Infrastructure Deploy
147+
148+
The GitHub Actions pipeline deploys static assets only. The first `cdk deploy` stays manual and is documented here:
149+
150+
- Runbook: [`../infrastructure/INITIAL_DEPLOYMENT.md`](../infrastructure/INITIAL_DEPLOYMENT.md)
151+
- Helper script: [`../scripts/initial-aws-deploy.sh`](../scripts/initial-aws-deploy.sh)
152+
- Additional local env required for the first infrastructure deploy:
153+
- `AWS_REGION`
154+
- `CLOUDFRONT_CERTIFICATE_REGION`
155+
156+
### Unused Repo-Local Helpers
157+
158+
These files exist in the repo but are not used by the current workflows:
159+
160+
| Helper | File | Notes |
161+
|--------|------|-------|
162+
| Setup Node.js and PNPM | [`./actions/setup-node-pnpm/action.yml`](./actions/setup-node-pnpm/action.yml) | Current workflows call `pnpm/action-setup@v4` and `actions/setup-node@v6` directly |
163+
| Tag deployment | [`./actions/tag-deployment/action.yml`](./actions/tag-deployment/action.yml) | Supports configurable semver bumps, but the live deploy workflow performs an inline patch bump instead |
164+
165+
### Local Tooling
166+
167+
To match the CI lint job locally, install `actionlint` once on your machine and run it from repo root:
168+
169+
```bash
170+
brew install actionlint
171+
actionlint
172+
```
173+
174+
Pinned Go install alternative:
175+
176+
```bash
177+
go install github.com/rhysd/actionlint/cmd/actionlint@v1.7.11
178+
actionlint
179+
```
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# .github/actions/setup-node-pnpm/action.yml
2+
3+
name: Setup Node.js and PNPM
4+
description: Sets up Node.js and PNPM based on project configuration
5+
6+
runs:
7+
using: composite
8+
steps:
9+
- name: Setup PNPM
10+
uses: pnpm/action-setup@v4
11+
12+
- name: Setup Node.js
13+
uses: actions/setup-node@v6
14+
with:
15+
node-version-file: .nvmrc
16+
cache: pnpm
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Tag deployment
2+
description: Find the latest semantic deployment tag, bump it, and push the new tag
3+
4+
inputs:
5+
tag-prefix:
6+
description: Prefix for deployment tags
7+
required: false
8+
default: "v"
9+
10+
initial-version:
11+
description: Version to start from when no matching tags exist
12+
required: false
13+
default: "0.0.0"
14+
15+
bump:
16+
description: Which semver component to bump (major, minor, patch)
17+
required: false
18+
default: "patch"
19+
20+
remote:
21+
description: Git remote to push the tag to
22+
required: false
23+
default: "origin"
24+
25+
outputs:
26+
new-tag:
27+
description: The newly created tag
28+
value: ${{ steps.tag.outputs.new-tag }}
29+
30+
runs:
31+
using: composite
32+
steps:
33+
- name: Run tag deployment script
34+
id: tag
35+
shell: bash
36+
env:
37+
INPUT_TAG_PREFIX: ${{ inputs.tag-prefix }}
38+
INPUT_INITIAL_VERSION: ${{ inputs.initial-version }}
39+
INPUT_BUMP: ${{ inputs.bump }}
40+
INPUT_REMOTE: ${{ inputs.remote }}
41+
run: |
42+
bash "${{ github.action_path }}/tag-deployment.sh"

0 commit comments

Comments
 (0)