Skip to content

Commit e4fa9ba

Browse files
authored
Harden Cloudflare Pages starter validation (#37)
1 parent 93a7a1e commit e4fa9ba

11 files changed

Lines changed: 315 additions & 204 deletions

File tree

.env.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Copy to .env for local shell use only.
2+
# Do not commit real Cloudflare credentials.
3+
4+
PROJECT_NAME=my-site
5+
CLOUDFLARE_ACCOUNT_ID=replace-with-your-account-id
6+
CLOUDFLARE_API_TOKEN=replace-with-your-api-token

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,7 @@
1414
## Testing
1515

1616
- [ ] Linting passes (`npm run lint`)
17+
- [ ] Build contract passes (`npm run build`)
18+
- [ ] Tests pass (`npm test`)
19+
- [ ] High-severity audit passes (`npm audit --audit-level=high`)
1720
- [ ] Tested locally (`npm run dev`)

.github/workflows/ci.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ jobs:
4848
- name: Install dependencies
4949
run: npm ci --ignore-scripts
5050

51+
- name: Audit high-severity vulnerabilities
52+
run: npm audit --audit-level=high
53+
5154
# `eslint --cache` writes to .eslintcache at cwd; persist it across
5255
# runs so warm CI lints incrementally instead of rescanning the tree.
5356
- name: Restore ESLint cache
@@ -64,3 +67,6 @@ jobs:
6467

6568
- name: Test
6669
run: npm test
70+
71+
- name: Build contract
72+
run: npm run build

.github/workflows/setup.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ jobs:
5555
- [ ] Add GitHub Secrets: `CLOUDFLARE_API_TOKEN`, `CLOUDFLARE_ACCOUNT_ID`
5656
- [ ] Create a GitHub Environment named `cloudflare` (Settings → Environments → New)
5757
- [ ] Set GitHub variable `PROJECT_NAME` to your Cloudflare Pages project name
58+
- [ ] For local shell deploys only, copy `.env.example` to `.env` and fill it locally
5859
5960
See [docs/CLOUDFLARE_PAGES_SETUP.md](docs/CLOUDFLARE_PAGES_SETUP.md) for a detailed guide.
6061

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ node_modules/
22
.wrangler/
33
.DS_Store
44
.env
5+
.env.local
6+
.env.*.local
7+
.dev.vars
58
.idea/
69
.claude/
710
.eslintcache

README.ko.md

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ git clone https://github.com/starter-series/cloudflare-pages-starter my-site
3838
cd my-site && npm install && npm run dev
3939
```
4040

41-
> ⚠️ **배포 전 필수: `package.json``name`을 바꾸세요** (`"my-site"` → 실제 Cloudflare Pages 프로젝트 이름), 그리고 `repository.url``YOUR_USERNAME/YOUR_SITE`도 교체하세요. `deploy` 스크립트가 `$npm_package_name`을 Cloudflare Pages 프로젝트 이름으로 사용합니다 — **건너뛰면 CD가 엉뚱한 프로젝트에 배포하거나 실패합니다.** (create-starter는 `name`을 자동으로 설정합니다. `repository.url`은 수동 설정이 필요합니다.)
41+
> ⚠️ **배포 전 필수:** GitHub Actions 변수 `PROJECT_NAME`을 실제 Cloudflare Pages 프로젝트 이름으로 설정하고, `repository.url``YOUR_USERNAME/YOUR_SITE`를 교체하세요. 로컬에서 `npm run deploy`를 사용할 경우 해당 스크립트가 `$npm_package_name`을 사용하므로 `package.json``name``"my-site"`에서 같은 프로젝트 이름으로 바꾸세요. (create-starter는 패키지 이름을 자동으로 설정합니다. `PROJECT_NAME``repository.url`은 여전히 직접 설정해야 합니다.)
42+
>
43+
> 로컬 shell 배포용 값은 `.env.example``.env`로 복사해 채우고, `.env`는 커밋하지 마세요.
4244
4345
## 프로젝트 범위
4446

@@ -47,6 +49,7 @@ cd my-site && npm install && npm run dev
4749
- Pages Functions 예시 (`functions/api/hello.js`) + `node:test` 유닛 테스트.
4850
- KV 기반 카운터 (`functions/api/visits.js`) — best-effort 카운터 (KV는 eventually consistent — compare-and-swap 없음; 동시 트래픽에서 undercount 가능; 정확한 카운트가 필요하면 Durable Object 사용) + NaN 복구.
4951
- CI: gitleaks 시크릿 스캔, ESLint v10, `npm ci --ignore-scripts`, 대용량 파일 가드.
52+
- 빌드 계약: `npm run build`가 번들러 없이 정적 배포 경계(`src/`, `_headers`, Pages Functions, `wrangler.toml`, `package.json` deploy/files 설정)를 검증.
5053
- CD: 수동 배포 + 태그된 GitHub Release; version guard로 중복 태그 차단.
5154
- 보안 헤더 — `_headers` 가 CSP / HSTS / Permissions-Policy / X-Content-Type-Options 를 제공하며 회귀 테스트로 잠겨 있음.
5255
- 주간 CodeQL + maintenance health check + stale-bot.
@@ -88,9 +91,10 @@ cd my-site && npm install && npm run dev
8891
│ ├── headers.test.js # _headers 회귀 가드 (CSP/HSTS/Permissions-Policy)
8992
│ └── bump-version.test.js # version-bump 스크립트 동작 + pre-release 거부 검증
9093
├── wrangler.toml # Pages 설정 + 주석 처리된 KV 바인딩 예시
94+
├── .env.example # 로컬 배포 env placeholder; untracked .env로 복사
9195
├── .github/
9296
│ ├── workflows/
93-
│ │ ├── ci.yml # 린트, 보안 스캔
97+
│ │ ├── ci.yml # 시크릿 스캔, audit, 린트, 테스트, 빌드 계약
9498
│ │ ├── cd.yml # Cloudflare Pages 배포
9599
│ │ └── setup.yml # 첫 사용 시 자동 설정 체크리스트
96100
│ └── PULL_REQUEST_TEMPLATE.md
@@ -99,6 +103,7 @@ cd my-site && npm install && npm run dev
99103
│ └── BRANCH_PROTECTION.md # 권장 main 보호 정책 + gh api payload
100104
├── scripts/
101105
│ ├── bump-version.cjs # 엄격 semver 버전 범퍼
106+
│ ├── check-build-output.cjs # Cloudflare Pages 배포 계약 검증
102107
│ └── check-placeholders.cjs # postinstall placeholder 경고
103108
├── eslint.config.js # ESLint v10 flat config
104109
├── .gitignore
@@ -114,8 +119,9 @@ cd my-site && npm install && npm run dev
114119
- **CD 파이프라인** — 원클릭 Cloudflare Pages 배포 + GitHub Release 자동 생성
115120
- **버전 관리**`npm run version:patch/minor/major`
116121
- **로컬 개발**`npm run dev`로 Cloudflare Pages 에뮬레이션
122+
- **빌드 계약**`npm run build`로 no-bundler 배포 표면 검증
117123
- **템플릿 셋업** — 첫 사용 시 설정 체크리스트 이슈 자동 생성
118-
- **최소 의존성** — devDependency 4개, 빌드 단계 불필요
124+
- **최소 의존성** — devDependency 4개, 번들러 불필요
119125

120126
## CI/CD
121127

@@ -126,8 +132,10 @@ cd my-site && npm install && npm run dev
126132
| 시크릿 스캔 | gitleaks로 유출된 자격증명 감지 |
127133
| 대용량 파일 체크 | 5 MB 초과 파일 방지 (Cloudflare 제한: 25 MB) |
128134
| Install | `npm ci` lockfile 검증 |
135+
| Audit | `npm audit --audit-level=high`로 high severity 의존성 이슈 차단 |
129136
| 린트 | ESLint v10 flat config |
130137
| 테스트 | `node --test`로 Pages Functions 유닛 테스트 실행 |
138+
| 빌드 계약 | `npm run build`로 정적 Pages 배포 경계 검증 |
131139

132140
### 보안 & 유지보수
133141

@@ -172,6 +180,7 @@ cd my-site && npm install && npm run dev
172180
4. GitHub Secrets에 `CLOUDFLARE_API_TOKEN`, `CLOUDFLARE_ACCOUNT_ID` 추가
173181
5. GitHub Environment `cloudflare` 생성
174182
6. GitHub 변수 `PROJECT_NAME`에 Cloudflare Pages 프로젝트 이름 설정
183+
7. 로컬 배포가 필요하면 `.env.example``.env`로 복사해 로컬에서만 값을 채운 뒤 `set -a && . ./.env && set +a` 실행 후 `npm run deploy`
175184

176185
끝. 자세한 가이드는 [docs/CLOUDFLARE_PAGES_SETUP.md](docs/CLOUDFLARE_PAGES_SETUP.md)를 참고하세요.
177186

@@ -202,6 +211,15 @@ npm run lint
202211
# 테스트 실행
203212
npm test
204213

214+
# 정적 배포 계약 검증
215+
npm run build
216+
217+
# high severity 의존성 이슈 audit
218+
npm audit --audit-level=high
219+
220+
# 의도한 npm pack 경계 확인
221+
npm pack --dry-run --json
222+
205223
# 수동 배포
206224
npm run deploy
207225
```

README.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ cd my-site && npm install && npm run dev
3939
```
4040

4141
> ⚠️ **Before deploying:** set the GitHub Actions variable `PROJECT_NAME` to your Cloudflare Pages project name and update `repository.url` (replace `YOUR_USERNAME/YOUR_SITE`). For local `npm run deploy`, rename `package.json` `name` from `"my-site"` to the same project name because that script uses `$npm_package_name`. (create-starter handles the package name automatically; you still need to set `PROJECT_NAME` and `repository.url`.)
42+
>
43+
> For local shell deploys, copy `.env.example` to `.env`, fill the values locally, and keep `.env` untracked.
4244
4345
## Project Scope
4446

@@ -47,6 +49,7 @@ cd my-site && npm install && npm run dev
4749
- Pages Functions example (`functions/api/hello.js`) with `node:test` unit tests.
4850
- KV-backed counter (`functions/api/visits.js`) — best-effort counter (KV is eventually consistent — no compare-and-swap; may undercount under concurrent traffic; use a Durable Object for exact counts) + NaN recovery.
4951
- CI: gitleaks secret scan, ESLint v10, `npm ci --ignore-scripts`, large-file guard.
52+
- Build contract: `npm run build` verifies the static deploy boundary (`src/`, `_headers`, Pages Functions, `wrangler.toml`, and `package.json` deploy/files settings) without adding a bundler.
5053
- CD: manual deploy + tagged GitHub Release; version guard rejects duplicate tags.
5154
- Security headers — `_headers` ships CSP / HSTS / Permissions-Policy / X-Content-Type-Options, locked by a regression test.
5255
- Weekly CodeQL + maintenance health check + stale-bot.
@@ -88,9 +91,10 @@ cd my-site && npm install && npm run dev
8891
│ ├── headers.test.js # _headers regression guard (CSP/HSTS/Permissions-Policy)
8992
│ └── bump-version.test.js # version-bump script behavior + pre-release refusal
9093
├── wrangler.toml # Pages config + commented KV binding example
94+
├── .env.example # Local deploy env placeholders; copy to untracked .env
9195
├── .github/
9296
│ ├── workflows/
93-
│ │ ├── ci.yml # Lint, security scan
97+
│ │ ├── ci.yml # Secret scan, audit, lint, test, build contract
9498
│ │ ├── cd.yml # Deploy to Cloudflare Pages
9599
│ │ └── setup.yml # Auto setup checklist on first use
96100
│ └── PULL_REQUEST_TEMPLATE.md
@@ -99,6 +103,7 @@ cd my-site && npm install && npm run dev
99103
│ └── BRANCH_PROTECTION.md # Recommended main ruleset + gh api payload
100104
├── scripts/
101105
│ ├── bump-version.cjs # Strict-semver version bumper
106+
│ ├── check-build-output.cjs # Validates the Cloudflare Pages deploy contract
102107
│ └── check-placeholders.cjs # postinstall placeholder warning
103108
├── eslint.config.js # ESLint v10 flat config
104109
├── .gitignore
@@ -114,8 +119,9 @@ cd my-site && npm install && npm run dev
114119
- **CD Pipeline** — One-click deploy to Cloudflare Pages + auto GitHub Release
115120
- **Version management**`npm run version:patch/minor/major`
116121
- **Local dev**`npm run dev` with Cloudflare Pages emulation
122+
- **Build contract**`npm run build` validates the no-bundler deploy surface
117123
- **Template setup** — Auto-creates setup checklist issue on first use
118-
- **Minimal** — 4 devDependencies, no build step required
124+
- **Minimal** — 4 devDependencies, no bundler required
119125

120126
## CI/CD
121127

@@ -126,8 +132,10 @@ cd my-site && npm install && npm run dev
126132
| Secret scan | gitleaks scans for leaked credentials |
127133
| Large file check | Prevents files over 5 MB (Cloudflare limit: 25 MB) |
128134
| Install | `npm ci` with lockfile verification |
135+
| Audit | `npm audit --audit-level=high` blocks high-severity dependency issues |
129136
| Lint | ESLint v10 flat config |
130137
| Test | `node --test` runs Pages Functions unit tests |
138+
| Build contract | `npm run build` validates the static Pages deploy boundary |
131139

132140
### Security & Maintenance
133141

@@ -172,6 +180,7 @@ See [docs/CLOUDFLARE_PAGES_SETUP.md](docs/CLOUDFLARE_PAGES_SETUP.md) for the one
172180
4. Add `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID` as GitHub Secrets
173181
5. Create a GitHub Environment named `cloudflare`
174182
6. Set `PROJECT_NAME` as a GitHub variable
183+
7. Optional for local deploys: copy `.env.example` to `.env`, fill it locally, then run `set -a && . ./.env && set +a` before `npm run deploy`
175184

176185
That's it. See [docs/CLOUDFLARE_PAGES_SETUP.md](docs/CLOUDFLARE_PAGES_SETUP.md) for detailed steps.
177186

@@ -202,6 +211,15 @@ npm run lint
202211
# Run tests
203212
npm test
204213

214+
# Validate the static deploy contract
215+
npm run build
216+
217+
# Audit high-severity dependency issues
218+
npm audit --audit-level=high
219+
220+
# Inspect the intended npm pack boundary
221+
npm pack --dry-run --json
222+
205223
# Deploy manually
206224
npm run deploy
207225
```

docs/CLOUDFLARE_PAGES_SETUP.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,23 @@ Create an environment named `cloudflare`.
5151
## 6. Deploy
5252

5353
1. Set the GitHub Actions variable `PROJECT_NAME` to the Cloudflare Pages project name. For local `npm run deploy`, also rename `package.json` `name` to that same value.
54-
2. Commit and push to `main`
55-
3. Go to **Actions** tab → **Deploy to Cloudflare Pages****Run workflow**
54+
2. Run the local gates: `npm run lint`, `npm test`, `npm run build`, and `npm audit --audit-level=high`
55+
3. Commit and push to `main`
56+
4. Go to **Actions** tab → **Deploy to Cloudflare Pages****Run workflow**
5657

5758
Your site will be live at `https://PROJECT_NAME.pages.dev`.
5859

60+
### Optional local deploy environment
61+
62+
For local shell deploys, copy `.env.example` to `.env`, fill the values on your
63+
machine, and keep `.env` untracked:
64+
65+
```bash
66+
cp .env.example .env
67+
set -a && . ./.env && set +a
68+
npm run deploy
69+
```
70+
5971
## Custom Domain (Optional)
6072

6173
1. In Cloudflare dashboard → **Workers & Pages** → your project → **Custom domains**

0 commit comments

Comments
 (0)