@se-studio/skills 1.3.4 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/package.json +1 -1
- package/skills/contentful-cms-regenerate-editor-pack/SKILL.md +7 -8
- package/skills/contentful-cms-setup/SKILL.md +12 -15
- package/skills/se-marketing-sites-create-page/SKILL.md +15 -6
- package/skills/se-marketing-sites-redirects/SKILL.md +29 -10
- package/skills/se-marketing-sites-smoke-test-setup/SKILL.md +277 -0
- package/skills/site-workflows-contentful-vercel-setup/SKILL.md +5 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @se-studio/skills
|
|
2
2
|
|
|
3
|
+
## 1.4.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- f2de9ba: Document CMS smoke integrity (`cmsIntegrity`, `runStaticSmokeTestWithIntegrity`), Vercel `VERCEL_FORCE_NO_BUILD_CACHE`, and update `se-marketing-sites-smoke-test-setup` skill plus `example-empty` reference.
|
|
8
|
+
|
|
9
|
+
## 1.4.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- Bulk version bump: minor for all packages
|
|
14
|
+
|
|
3
15
|
## 1.3.4
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -27,12 +27,11 @@ Run after any of these change:
|
|
|
27
27
|
|
|
28
28
|
| Action | Automatic on git push? |
|
|
29
29
|
|--------|------------------------|
|
|
30
|
-
| Copy **committed** `editor-pack/`
|
|
30
|
+
| Copy **committed** `editor-pack/` into cms-edit-host | Yes (`cms-edit host stage` via `stage-site.mjs` on Vercel build) |
|
|
31
31
|
| Run `editor-pack generate` | **No** — always manual (this skill) |
|
|
32
|
-
| Rebuild `.mcpb` on Vercel | **No** — commit locally (`pnpm cms-edit:mcpb` or `build-mcpb.sh`) when `hosted.mcpUrl` or extension metadata changes |
|
|
33
32
|
| Update hosted guide/prompts from `@se-studio/contentful-cms` | Redeploy host after bumping package version |
|
|
34
33
|
|
|
35
|
-
Editors do **not**
|
|
34
|
+
Editors do **not** reconnect Claude Integrations when only the editor pack changes.
|
|
36
35
|
|
|
37
36
|
**Staging:** `stage-site.mjs` copies committed artifacts into `cms-edit/host/` before build. Requires `CMS_EDIT_ROOT` on Vercel. Single-site repos default to `cms-edit` locally; multi-site repos need `CMS_EDIT_ROOT` in `cms-edit/host/.env.local`. Run manually with `pnpm cms-edit:stage` (or site-specific variant).
|
|
38
37
|
|
|
@@ -88,13 +87,13 @@ git commit -m "chore(cms-edit): regenerate editor pack"
|
|
|
88
87
|
|
|
89
88
|
### Customer repo with `cms-edit/host/` (SE Studio, Pedestal, Headwater)
|
|
90
89
|
|
|
91
|
-
Commit `editor-pack
|
|
90
|
+
Commit `editor-pack/`, then push to the branch Vercel watches (usually `develop`). Changes under `cms-edit/**` trigger a host rebuild (`stage-site.mjs` copies committed artifacts).
|
|
92
91
|
|
|
93
92
|
```bash
|
|
94
93
|
git push
|
|
95
94
|
```
|
|
96
95
|
|
|
97
|
-
Manual deploy without pushing: `pnpm cms-edit:deploy` (single-site) or `pnpm cms-edit:deploy:<site>` (multi-site) — runs validate, stage, then `vercel deploy --prod
|
|
96
|
+
Manual deploy without pushing: `pnpm cms-edit:deploy` (single-site) or `pnpm cms-edit:deploy:<site>` (multi-site) — runs validate, stage, then `vercel deploy --prod` **from the repo root** with `--project <vercel-project>`. Do not `cd cms-edit/host` first (Vercel Root Directory is already `cms-edit/host`).
|
|
98
97
|
|
|
99
98
|
### Customers without `cms-edit/host/` in their repo
|
|
100
99
|
|
|
@@ -114,10 +113,10 @@ SE Studio sites store the marketing homepage as Contentful slug **`index`** (pub
|
|
|
114
113
|
## Verify after deploy
|
|
115
114
|
|
|
116
115
|
```bash
|
|
117
|
-
curl -s
|
|
116
|
+
curl -s "https://<host>/api/health" | jq
|
|
118
117
|
```
|
|
119
118
|
|
|
120
|
-
In Claude Desktop (hosted
|
|
119
|
+
In Claude Desktop (hosted integration): ask to read `cms-edit://customer/routing` and confirm new component types appear in `cms-edit://customer/components-index`.
|
|
121
120
|
|
|
122
121
|
## Related docs
|
|
123
122
|
|
|
@@ -133,4 +132,4 @@ In Claude Desktop (hosted extension): ask to read `cms-edit://customer/routing`
|
|
|
133
132
|
|------------|-------|
|
|
134
133
|
| Guidelines changed | `contentful-cms-update-cms-guidelines` |
|
|
135
134
|
| New registration | `se-marketing-sites-register-cms-features` |
|
|
136
|
-
| Editor
|
|
135
|
+
| Editor Integrations setup | `contentful-cms-setup` (hosted path) |
|
|
@@ -11,35 +11,32 @@ Use this skill when the user wants to install or configure the `cms-edit` MCP se
|
|
|
11
11
|
|
|
12
12
|
| Path | When to use |
|
|
13
13
|
|------|-------------|
|
|
14
|
-
| **Hosted (
|
|
14
|
+
| **Hosted (Integrations)** | Content editor; SE sent an onboarding URL (e.g. `/cms-edit`); no Node.js |
|
|
15
15
|
| **Local (setup wizard)** | Developer; multi-space; or no hosted deployment |
|
|
16
16
|
|
|
17
17
|
Ask which applies if unclear.
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
|
21
|
-
## Hosted setup (
|
|
21
|
+
## Hosted setup (Claude Integrations + OAuth)
|
|
22
22
|
|
|
23
|
-
Use when
|
|
23
|
+
Use when SE sent an **onboarding link** (e.g. `https://your-site.content.se.studio/cms-edit`). Do **not** run the setup wizard.
|
|
24
24
|
|
|
25
|
-
### Step 1:
|
|
25
|
+
### Step 1: Connect to Claude
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
1. Open the link SE sent (or **Connect to Claude** on `/cms-edit`)
|
|
28
|
+
2. Confirm the connector in Claude Desktop or claude.ai
|
|
29
|
+
3. Sign in with **Contentful** when prompted (same account SE invited to the space)
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
1. Open [Claude Desktop](https://claude.ai/download)
|
|
32
|
-
2. **Settings → Extensions → Install Extension…** (or double-click the `.mcpb` file)
|
|
33
|
-
3. Paste the Contentful token when prompted
|
|
34
|
-
4. Fully quit and reopen Claude Desktop
|
|
31
|
+
Manual alternative: Claude → **Customize → Connectors** → Add custom connector → paste the MCP URL (`…/api/mcp`) → Connect.
|
|
35
32
|
|
|
36
33
|
Full editor guide: `packages/contentful-cms/HOSTED.md`
|
|
37
34
|
|
|
38
|
-
### Step
|
|
35
|
+
### Step 2: Verify
|
|
39
36
|
|
|
40
|
-
In Claude, ask to
|
|
37
|
+
In Claude, ask to read the cms-edit guide or verify the connection. If `cms_edit` returns content, setup is complete.
|
|
41
38
|
|
|
42
|
-
**
|
|
39
|
+
**Reconnect:** Settings → Integrations → Connect again if the connection stops working.
|
|
43
40
|
|
|
44
41
|
---
|
|
45
42
|
|
|
@@ -126,4 +123,4 @@ If it returns a page structure, setup is complete. If it errors, see Troubleshoo
|
|
|
126
123
|
It should contain an `mcpServers.cms-edit` entry. If missing, run the wizard again.
|
|
127
124
|
|
|
128
125
|
**Adding another space**
|
|
129
|
-
→ Run `npx @se-studio/contentful-cms@latest setup` again — it merges the new space into the existing config.
|
|
126
|
+
→ Run `npx @se-studio/contentful-cms@latest setup` again — it merges the new space into the existing config.
|
|
@@ -121,16 +121,23 @@ If a route needs a different layout (e.g. no header/footer), create `layout.tsx`
|
|
|
121
121
|
|
|
122
122
|
## A/B Test Variant Route (`page-test`)
|
|
123
123
|
|
|
124
|
-
When A/B testing is enabled, variant pages are served through a `/page-test/[...slugs]` route that the middleware rewrites to. This route **must** use
|
|
124
|
+
When A/B testing is enabled, variant pages are served through a `/page-test/[...slugs]` route that the middleware rewrites to. This route **must** use:
|
|
125
|
+
|
|
126
|
+
- **`generatePageTestMetadata`** (not `generatePageMetadata`) so that `robots`/`indexed` comes from the **canonical/control** page — variant entries are intentionally `indexed: false` in Contentful.
|
|
127
|
+
- **`generatePageTest`** (not `generatePage`) so breadcrumbs, `model.href`, and JSON-LD use the **control/canonical** path while variant **content** still renders.
|
|
128
|
+
|
|
129
|
+
`BasicLayout` should resolve breadcrumbs and structured data from `currentPath ?? model.href` (the factory passes `currentPath={canonicalPath}`).
|
|
125
130
|
|
|
126
131
|
If the slug is not a known variant in `testsByPath`, **return 404** (`notFound()`): there is no control page to merge, and `/page-test/…` is only valid for configured tests.
|
|
127
132
|
|
|
133
|
+
Direct visits to a variant's own CMS URL (normal `[...slugs]` route) continue to use `generatePage` and show variant breadcrumbs.
|
|
134
|
+
|
|
128
135
|
```typescript
|
|
129
136
|
import { findCanonicalPath } from '@se-studio/ab-testing';
|
|
130
137
|
import type { ResolvingMetadata } from 'next';
|
|
131
138
|
import { notFound } from 'next/navigation';
|
|
132
139
|
import { testsByPath } from '@/generated/abTests';
|
|
133
|
-
import {
|
|
140
|
+
import { generatePageTest, generatePageTestMetadata } from '@/lib/route-handlers';
|
|
134
141
|
import { getPageRouteConfig } from '@/lib/routeConfig';
|
|
135
142
|
|
|
136
143
|
/** Variant slug must appear in testsByPath; otherwise this route is not valid. */
|
|
@@ -165,13 +172,15 @@ export async function generateMetadata(
|
|
|
165
172
|
}
|
|
166
173
|
|
|
167
174
|
export default async function (props: PageProps<'/page-test/[...slugs]'>) {
|
|
168
|
-
const { slug
|
|
169
|
-
requireCanonicalPath(slug);
|
|
170
|
-
|
|
175
|
+
const { slug } = await extractDetails(props);
|
|
176
|
+
const canonicalPath = requireCanonicalPath(slug);
|
|
177
|
+
const canonicalSlug = canonicalPath.replace(/^\/|\/$/g, '') || slug;
|
|
178
|
+
// Use generatePageTest: variant contents with control identity (href, breadcrumbs, JSON-LD).
|
|
179
|
+
return generatePageTest(slug, canonicalSlug, canonicalPath, getPageRouteConfig(canonicalSlug));
|
|
171
180
|
}
|
|
172
181
|
```
|
|
173
182
|
|
|
174
|
-
Also export `generatePageTestMetadata` from the app's `src/lib/route-handlers.ts` destructuring alongside `generatePageMetadata`.
|
|
183
|
+
Also export `generatePageTestMetadata` and `generatePageTest` from the app's `src/lib/route-handlers.ts` destructuring alongside `generatePageMetadata`.
|
|
175
184
|
|
|
176
185
|
## See Also
|
|
177
186
|
|
|
@@ -22,12 +22,17 @@ The repo contains the official migration:
|
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
24
|
# Create the redirect content type (clean fromPath + fromInternal / toPath + toInternal model)
|
|
25
|
-
|
|
25
|
+
contentful space migration ... scripts/migrations/18-create-redirect-content-type.js
|
|
26
|
+
|
|
27
|
+
# If the redirect CT already exists but lacks field defaults (statusCode → 301, active → true):
|
|
28
|
+
contentful space migration ... scripts/migrations/22-set-redirect-field-defaults.js
|
|
26
29
|
```
|
|
27
30
|
|
|
28
|
-
Run
|
|
31
|
+
Run with the Contentful CLI (or the contentful-cms package tools) and the usual `CONTENTFUL_SPACE_ID` + `CONTENTFUL_MANAGEMENT_TOKEN` + environment.
|
|
32
|
+
|
|
33
|
+
If you previously created a Redirect CT using an older multi-field version of the migration, **delete the "Redirect" content type first**, then re-run migration 18 to get the clean single-ref model.
|
|
29
34
|
|
|
30
|
-
|
|
35
|
+
Migration 22 only updates the content type definition (editor defaults for **new** entries). It does not backfill existing redirect entries.
|
|
31
36
|
|
|
32
37
|
After running you will have a new (or repaired) content type called **Redirect**.
|
|
33
38
|
|
|
@@ -44,8 +49,8 @@ Key fields (all documented with help text in the migration):
|
|
|
44
49
|
- `toInternal` (single reference picker, same allowed types as fromInternal)
|
|
45
50
|
|
|
46
51
|
**Other**
|
|
47
|
-
- `statusCode` — dropdown: 301 (permanent — recommended), 302, 307, 308.
|
|
48
|
-
- `active` — boolean
|
|
52
|
+
- `statusCode` — dropdown: 301 (permanent — recommended), 302, 307, 308. **Defaults to 301** in the editor for new entries.
|
|
53
|
+
- `active` — boolean. **Defaults to true** for new entries. Turn off to disable a rule without deleting it.
|
|
49
54
|
- `note` — internal editor note (never shown on the site).
|
|
50
55
|
- `cmsLabel` — required internal label.
|
|
51
56
|
|
|
@@ -200,15 +205,29 @@ Pure `fromPath` vanity redirects were already absent from sitemaps (no backing c
|
|
|
200
205
|
|
|
201
206
|
### 4.3 Webhook = Deploy Hook (the trigger)
|
|
202
207
|
|
|
203
|
-
|
|
208
|
+
Baked redirects require a **rebuild**, not ISR revalidation. Use a Contentful webhook that calls your **Vercel Deploy Hook** URL (not `/api/revalidate`).
|
|
209
|
+
|
|
210
|
+
**Automated setup (recommended):**
|
|
204
211
|
|
|
205
|
-
|
|
212
|
+
1. In Vercel → Project → Settings → **Deploy Hooks**, create a hook for Production (and optionally Preview). Copy each URL.
|
|
213
|
+
2. Add to `.env.local`:
|
|
214
|
+
```bash
|
|
215
|
+
VERCEL_DEPLOY_HOOK_URL=https://api.vercel.com/v1/integrations/deploy/...
|
|
216
|
+
# optional:
|
|
217
|
+
VERCEL_PREVIEW_DEPLOY_HOOK_URL=https://api.vercel.com/v1/integrations/deploy/...
|
|
218
|
+
VERCEL_PROTECTION_BYPASS_TOKEN=... # only if you need the bypass header on hook calls
|
|
219
|
+
```
|
|
220
|
+
3. From the app directory (requires `CONTENTFUL_MANAGEMENT_TOKEN` + `CONTENTFUL_SPACE_ID`):
|
|
221
|
+
```bash
|
|
222
|
+
pnpm setup:redirect-deploy-hook
|
|
223
|
+
```
|
|
224
|
+
This creates/replaces Contentful webhooks named `Vercel Deploy Hook (Redirects)` (and `… – Preview` when the preview URL is set). They fire on **Entry publish / unpublish / delete** filtered to content type `redirect`.
|
|
206
225
|
|
|
207
|
-
|
|
226
|
+
The script lives at [`scripts/setup-redirect-deploy-hook.ts`](../../../../scripts/setup-redirect-deploy-hook.ts) in the monorepo; `example-empty` wires it via `package.json`.
|
|
208
227
|
|
|
209
|
-
|
|
228
|
+
**Manual setup:** same triggers and filter (`redirect` only), POST to the deploy hook URL. Do **not** point this at `/api/revalidate` — that only invalidates cache tags and will not regenerate `src/generated/redirects.ts`.
|
|
210
229
|
|
|
211
|
-
That's it. Publish a redirect → Vercel starts a build →
|
|
230
|
+
That's it. Publish a redirect → Vercel starts a build → `prebuild` runs `generate:redirects` → the new static map is in the bundle → redirects work.
|
|
212
231
|
|
|
213
232
|
## 5. Testing
|
|
214
233
|
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: se-marketing-sites-smoke-test-setup
|
|
3
|
+
description: "Set up or regenerate smoke.cases.json for local smoke tests from the production sitemap. Use when adding smoke tests, refreshing smoke URLs after route or content changes, or migrating off smoke.config.ts."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Smoke test setup (static `smoke.cases.json`)
|
|
7
|
+
|
|
8
|
+
Local smoke tests use curated HTTP cases in **`smoke.cases.json`**. Optionally enable **`cmsIntegrity`** for a local-only Contentful article-link check (no `cms-server` import in smoke scripts).
|
|
9
|
+
|
|
10
|
+
Requires `@se-studio/site-check` **^2.6.1** when using `cmsIntegrity` or `pnpm smoke-test` with integrity enabled; **2.1.2+** for cache log audit; **2.0.0+** for HTTP-only static smoke.
|
|
11
|
+
|
|
12
|
+
Preview / `DRAFT_ONLY` Contentful access in local dev is expected and not a smoke failure. **Deployment / live smoke stays HTTP-only** — do not enable `cmsIntegrity` on Vercel deployment checks.
|
|
13
|
+
|
|
14
|
+
## Prerequisites
|
|
15
|
+
|
|
16
|
+
- App has `scripts/smoke-test-run.ts` (see [CMS article-link integrity](#cms-article-link-integrity-optional) below).
|
|
17
|
+
- `package.json`: `"smoke-test:run": "tsx scripts/smoke-test-run.ts"`.
|
|
18
|
+
- `SITEMAP_PROD_URL` in `.env.example` (production sitemap for URL curation).
|
|
19
|
+
- When `cmsIntegrity.enabled` is true: `@se-studio/contentful-rest-api@^1.10.0`, `@se-studio/core-data-types@^1.5.1`, Contentful tokens in `.env.local`.
|
|
20
|
+
|
|
21
|
+
## Script matrix
|
|
22
|
+
|
|
23
|
+
| Script | Purpose |
|
|
24
|
+
|--------|---------|
|
|
25
|
+
| `pnpm smoke-test` | Start dev server + functional smoke (default) |
|
|
26
|
+
| `pnpm smoke-test:run` | Smoke only (server already up) |
|
|
27
|
+
| `pnpm smoke-test:preview` | Smoke against Vercel preview (`runPreviewStaticSmokeTest`; no local server) |
|
|
28
|
+
| `pnpm smoke-test:audit` | Functional smoke + server log cache audit |
|
|
29
|
+
| `pnpm smoke-test:cache` | `build` + `start` + double-pass `x-nextjs-cache` verify |
|
|
30
|
+
| `pnpm smoke-test:deploy-check` | Post-build gate: `start` (no rebuild) + functional smoke — use in Vercel `buildCommand` |
|
|
31
|
+
| `pnpm smoke-test:live` | Live deployment URL smoke — GitHub Action + Vercel Deployment Checks |
|
|
32
|
+
|
|
33
|
+
Example `package.json` entries:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
"smoke-test": "smoke-test-one 3012",
|
|
37
|
+
"smoke-test:run": "tsx scripts/smoke-test-run.ts",
|
|
38
|
+
"smoke-test:preview": "bash -c 'set -a && [ -f .env.local ] && . ./.env.local; set +a && exec tsx scripts/smoke-test-preview.ts'",
|
|
39
|
+
"smoke-test:audit": "SMOKE_TEST_AUDIT_CACHE_LOGS=true smoke-test-one 3012",
|
|
40
|
+
"smoke-test:cache": "SMOKE_TEST_SERVER_SCRIPT=start SMOKE_TEST_VERIFY_CACHE=true smoke-test-one 3012",
|
|
41
|
+
"smoke-test:deploy-check": "smoke-test-deploy-check",
|
|
42
|
+
"smoke-test:live": "smoke-test-live"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Vercel build gate** — append to root `vercel.json` so failed smoke fails the build before deploy:
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"buildCommand": "pnpm build && pnpm smoke-test:deploy-check"
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Vercel Deployment Check (live URL)** — GitHub Action on `vercel.deployment.ready` tests `client_payload.url` before production domains alias. Workflow must live on the repo **default branch**. Register the status `name` in Vercel → Settings → Build and Deployment → Deployment Checks.
|
|
54
|
+
|
|
55
|
+
Filter on `client_payload.environment == 'production'` when Deployment Checks target production only. Vercel also dispatches for preview and custom environments (`preview`, `develop`, etc.); skip those to avoid duplicate CI runs. Use `workflow_dispatch` without an environment filter for manual smoke against any URL.
|
|
56
|
+
|
|
57
|
+
```yaml
|
|
58
|
+
# .github/workflows/deployment-smoke.yml
|
|
59
|
+
name: Deployment smoke
|
|
60
|
+
|
|
61
|
+
on:
|
|
62
|
+
repository_dispatch:
|
|
63
|
+
types:
|
|
64
|
+
- vercel.deployment.ready
|
|
65
|
+
|
|
66
|
+
jobs:
|
|
67
|
+
smoke:
|
|
68
|
+
if: |
|
|
69
|
+
github.event.client_payload.project.name == '<vercel-project-name>' &&
|
|
70
|
+
github.event.client_payload.environment == 'production'
|
|
71
|
+
runs-on: ubuntu-latest
|
|
72
|
+
steps:
|
|
73
|
+
- uses: vercel/repository-dispatch/actions/checkout@v1
|
|
74
|
+
- uses: pnpm/action-setup@v4
|
|
75
|
+
with:
|
|
76
|
+
version: 11
|
|
77
|
+
- uses: actions/setup-node@v4
|
|
78
|
+
with:
|
|
79
|
+
node-version: 24
|
|
80
|
+
cache: pnpm
|
|
81
|
+
- run: pnpm install --frozen-lockfile
|
|
82
|
+
- name: Live deployment smoke
|
|
83
|
+
env:
|
|
84
|
+
DEPLOYMENT_URL: ${{ github.event.client_payload.url }}
|
|
85
|
+
VERCEL_PROTECTION_BYPASS_TOKEN: ${{ secrets.VERCEL_PROTECTION_BYPASS_TOKEN }}
|
|
86
|
+
run: pnpm smoke-test:live
|
|
87
|
+
- name: Report Deployment Check status
|
|
88
|
+
if: always()
|
|
89
|
+
uses: vercel/repository-dispatch/actions/status@v1
|
|
90
|
+
with:
|
|
91
|
+
name: "Vercel - <vercel-project-name>: deployment smoke"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
GitHub secret: `VERCEL_PROTECTION_BYPASS_TOKEN` (Vercel → Deployment Protection → Protection Bypass for Automation). Set `SMOKE_TEST_IGNORE=true` to bypass smoke in an emergency.
|
|
95
|
+
|
|
96
|
+
Or from the app directory without a per-app script: `node ../../scripts/smoke-test-preview.mjs` (loads `.env.local` and calls `runPreviewStaticSmokeTest`).
|
|
97
|
+
|
|
98
|
+
## Environment variables
|
|
99
|
+
|
|
100
|
+
| Variable | Purpose |
|
|
101
|
+
|----------|---------|
|
|
102
|
+
| `SITEMAP_PROD_URL` | Production site URL for sitemap fetch when curating `smoke.cases.json` |
|
|
103
|
+
| `PREVIEW_SITE_URL` | Vercel develop/preview deployment URL for `smoke-test:preview` |
|
|
104
|
+
| `DEPLOYMENT_URL` | Live deployment URL for `smoke-test:live` (CI / `repository_dispatch` payload) |
|
|
105
|
+
| `VERCEL_PROTECTION_BYPASS_TOKEN` | Deployment Protection bypass secret (aliases: `VERCEL_AUTOMATION_BYPASS_SECRET`, `VERCEL_BYPASS_TOKEN`) |
|
|
106
|
+
| `PRODUCTION_SITE_URL` | Optional canonical production URL for audits and related tooling |
|
|
107
|
+
| `SMOKE_TEST_SKIP_INTEGRITY` | Set by `smoke-test-one` when integrity runs in parallel; HTTP-only child run |
|
|
108
|
+
| `VERCEL_FORCE_NO_BUILD_CACHE` | **Vercel project env only** (`1`) — skip remote build cache when webpack `WasmHash` failures occur on Vercel (not `vercel.json`) |
|
|
109
|
+
|
|
110
|
+
Add smoke-related vars to `.env.example` under an SEO / site-check section. `runPreviewStaticSmokeTest` requires `PREVIEW_SITE_URL` and a bypass token in `.env.local`.
|
|
111
|
+
|
|
112
|
+
### Vercel build cache (`VERCEL_FORCE_NO_BUILD_CACHE`)
|
|
113
|
+
|
|
114
|
+
Intermittent Vercel `next build` failures (`WasmHash._updateWithBuffer` / `Cannot read properties of undefined (reading 'length')`) often correlate with a stale remote build cache. Mitigation:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
vercel env add VERCEL_FORCE_NO_BUILD_CACHE preview develop --value 1 --yes
|
|
118
|
+
vercel env add VERCEL_FORCE_NO_BUILD_CACHE production --value 1 --yes
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
- Set on the **Vercel project** (Preview and/or Production). Not in app `.env.local`.
|
|
122
|
+
- Tradeoff: cold builds ~1m vs ~30s cached.
|
|
123
|
+
- Roll out to other marketing sites on the next Vercel config pass when builds fail intermittently locally pass.
|
|
124
|
+
- Document in the app `AGENTS.md` when applied.
|
|
125
|
+
|
|
126
|
+
## Workflow
|
|
127
|
+
|
|
128
|
+
### 1. Confirm port
|
|
129
|
+
|
|
130
|
+
Read `package.json` — e.g. `next dev -p 3012` → port **3012**.
|
|
131
|
+
|
|
132
|
+
### 2. Fetch sitemap (production first)
|
|
133
|
+
|
|
134
|
+
Use `SITEMAP_PROD_URL` from `.env.example`:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
curl -sSL "$SITEMAP_PROD_URL"
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Parse all `<loc>` URLs. Strip host; keep pathnames with trailing slashes as in sitemap.
|
|
141
|
+
|
|
142
|
+
Use **local** sitemap only when verifying routes not yet on production:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
curl -sS "http://localhost:<port>/sitemap.xml"
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### 3. Understand route patterns
|
|
149
|
+
|
|
150
|
+
Read (do not guess from constants alone):
|
|
151
|
+
|
|
152
|
+
- `src/app/(cms-routes)/` — custom segments (e.g. se-website uses `/work/`, not `/articles/`).
|
|
153
|
+
- `src/lib/constants.ts` — `ARTICLES_BASE`, feature flags, slugs.
|
|
154
|
+
- Prior smoke output or `.md` probes for listing pages that do not export markdown.
|
|
155
|
+
|
|
156
|
+
### 4. Bucket URLs into categories
|
|
157
|
+
|
|
158
|
+
| Category | Typical paths | Samples |
|
|
159
|
+
|----------|---------------|---------|
|
|
160
|
+
| `home` | `/` | 1 |
|
|
161
|
+
| `page` | Top-level CMS pages (not home, not article trees) | 2 |
|
|
162
|
+
| `article-type-index` | e.g. `/work/`, `/blog/` | 1 |
|
|
163
|
+
| `article` | Nested article/case-study URLs | 2 |
|
|
164
|
+
| `tag` / `tags-index` | If in sitemap and enabled | 1–2 each |
|
|
165
|
+
| `person` / `people-listing` | If in sitemap and enabled | 1–2 each |
|
|
166
|
+
|
|
167
|
+
**Exclude** obvious non-prod slugs: `tmp-*`, draft pages, unless intentionally tested.
|
|
168
|
+
|
|
169
|
+
Keep sets lean (6–10 cases). Avoid heavy paginated listing URLs unless needed for coverage.
|
|
170
|
+
|
|
171
|
+
### 5. Set `expectMarkdown`
|
|
172
|
+
|
|
173
|
+
- **true** — page and `.md` should return 200 with `text/markdown` (home, pages, articles).
|
|
174
|
+
- **false** — listing shells that return 404 or “not supported” for `.md` (optional; omit markdown checks).
|
|
175
|
+
|
|
176
|
+
Probe when unsure (local dev server):
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
curl -sI "http://localhost:<port>/some-path.md"
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### 6. Write `smoke.cases.json`
|
|
183
|
+
|
|
184
|
+
```json
|
|
185
|
+
{
|
|
186
|
+
"siteName": "my-app",
|
|
187
|
+
"port": 3012,
|
|
188
|
+
"cases": [
|
|
189
|
+
{ "category": "home", "label": "Home", "path": "/", "expectMarkdown": true },
|
|
190
|
+
{ "category": "page", "label": "About", "path": "/about/", "expectMarkdown": true }
|
|
191
|
+
]
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Optional `cmsIntegrity` (local only — see section below):
|
|
196
|
+
|
|
197
|
+
```json
|
|
198
|
+
{
|
|
199
|
+
"cmsIntegrity": {
|
|
200
|
+
"enabled": true,
|
|
201
|
+
"routing": {
|
|
202
|
+
"articleTypesBasePath": "/resources",
|
|
203
|
+
"tagsBasePath": "/topics",
|
|
204
|
+
"peopleBasePath": "/team",
|
|
205
|
+
"defaultTopic": "other",
|
|
206
|
+
"enablePrimaryTagPartOfSlug": true,
|
|
207
|
+
"topiclessArticleTypeSlugs": ["ebooks"]
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Map `routing` from `src/lib/constants.ts` (or the app’s URL rules). `enabled: false` omits the integrity fetch.
|
|
214
|
+
|
|
215
|
+
- `path` — site-relative, trailing slash when the site uses trailing slashes.
|
|
216
|
+
- `port` — default; override at run time with `SMOKE_TEST_PORT`.
|
|
217
|
+
|
|
218
|
+
### 7. Verify
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
pnpm smoke-test:run # HTTP (+ integrity when cmsIntegrity.enabled)
|
|
222
|
+
pnpm smoke-test # starts dev server; integrity runs in parallel when enabled
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Expect `Summary: N cases, 0 failed`. With integrity: `article link integrity: ok`. Preview API / `DRAFT_ONLY` logs in dev are OK.
|
|
226
|
+
|
|
227
|
+
Optional cache audit:
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
pnpm smoke-test:audit
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Expect `cache log audit: ok` with no 2MB errors. `cache skip` lines are warnings by default.
|
|
234
|
+
|
|
235
|
+
### 8. Commit
|
|
236
|
+
|
|
237
|
+
Commit `smoke.cases.json`. Remove legacy `smoke.config.ts` if present.
|
|
238
|
+
|
|
239
|
+
## CMS article-link integrity (optional)
|
|
240
|
+
|
|
241
|
+
Local-only check: one Contentful fetch validates all article/person/tag link `href` values (broken links, conversion errors). Runs in parallel with server boot when using `pnpm smoke-test`.
|
|
242
|
+
|
|
243
|
+
**`scripts/smoke-test-run.ts`:**
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
import {
|
|
247
|
+
formatCombinedSmokeReport,
|
|
248
|
+
getCombinedSmokeExitCode,
|
|
249
|
+
runStaticSmokeTestWithIntegrity,
|
|
250
|
+
} from '@se-studio/site-check/smoke-test';
|
|
251
|
+
|
|
252
|
+
async function main(): Promise<number> {
|
|
253
|
+
const skipIntegrity = (process.env.SMOKE_TEST_SKIP_INTEGRITY ?? '').toLowerCase() === 'true';
|
|
254
|
+
const { integrity, http } = await runStaticSmokeTestWithIntegrity('smoke.cases.json', {
|
|
255
|
+
skipIntegrity,
|
|
256
|
+
});
|
|
257
|
+
console.log(formatCombinedSmokeReport(integrity, http));
|
|
258
|
+
return getCombinedSmokeExitCode(integrity, http);
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Remove legacy `smoke-test:validate-links`, `smoke-test-preload.cjs`, and bespoke link-validation scripts when migrating.
|
|
263
|
+
|
|
264
|
+
**Per-site rollout:** bump `@se-studio/site-check@^2.6.1`, add `cmsIntegrity` to `smoke.cases.json`, update `smoke-test-run.ts`, run `pnpm smoke-test` locally. Reference consumer: `om1-website` (`develop`).
|
|
265
|
+
|
|
266
|
+
## Migrating from `smoke.config.ts`
|
|
267
|
+
|
|
268
|
+
1. Delete `smoke.config.ts`.
|
|
269
|
+
2. Simplify `smoke-test:run` script (no bash, no `NODE_OPTIONS`, no preload).
|
|
270
|
+
3. Update `@se-studio/site-check` to `^2.6.1` when adopting integrity; otherwise `^2.1.2` minimum.
|
|
271
|
+
4. Follow workflow above to create `smoke.cases.json`.
|
|
272
|
+
|
|
273
|
+
## Reference
|
|
274
|
+
|
|
275
|
+
- HTTP + integrity example: `apps/example-empty/smoke.cases.json` and `scripts/smoke-test-run.ts` (monorepo).
|
|
276
|
+
- Package API: `runStaticSmokeTest`, `runStaticSmokeTestWithIntegrity`, `runPreviewStaticSmokeTest`, `auditCacheLogs` from `@se-studio/site-check/smoke-test`.
|
|
277
|
+
- CMS integrity types: `@se-studio/site-check/cms-integrity`.
|
|
@@ -399,16 +399,12 @@ Expected: `200 OK`. A `401` means the secret in the header doesn't match the dep
|
|
|
399
399
|
|
|
400
400
|
## (Optional) Redirects via the rebuild pattern
|
|
401
401
|
|
|
402
|
-
If the project uses the `se-marketing-sites-redirects` skill, also
|
|
402
|
+
If the project uses the `se-marketing-sites-redirects` skill, also wire redirect rebuild webhooks (separate from `/api/revalidate`):
|
|
403
403
|
|
|
404
|
-
|
|
404
|
+
1. Create Vercel Deploy Hook(s) (Production + optional Preview) and set `VERCEL_DEPLOY_HOOK_URL` (and optionally `VERCEL_PREVIEW_DEPLOY_HOOK_URL`) in `.env.local`.
|
|
405
|
+
2. Run `pnpm setup:redirect-deploy-hook` from the app (monorepo script: `scripts/setup-redirect-deploy-hook.ts`).
|
|
405
406
|
|
|
406
|
-
-
|
|
407
|
-
- The exact content type fields + recommended help text
|
|
408
|
-
- The build-time generate script + static middleware
|
|
409
|
-
- Wiring the deploy hook webhook
|
|
407
|
+
See `.agents/skills/se-marketing-sites-redirects/SKILL.md` for migrations, generate script, middleware, and full webhook details.
|
|
410
408
|
|
|
411
|
-
This is
|
|
412
|
-
|
|
413
|
-
You can have both the normal revalidation webhooks **and** the redirect deploy-hook webhook active at the same time.
|
|
409
|
+
This is the same "publish → deploy hook → build bakes static data" pattern as A/B tests. You can run **both** ISR revalidation webhooks and redirect deploy-hook webhooks at the same time.
|
|
414
410
|
- **To rotate the secret later**: re-run `scripts/setup-contentful-webhooks.ts` — it will generate a new secret and update everything automatically.
|