@se-studio/skills 1.0.42 → 1.1.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 CHANGED
@@ -1,5 +1,74 @@
1
1
  # @se-studio/skills
2
2
 
3
+ ## 1.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Simplify Redirect content model + add tagType as a first-class internal link target.
8
+
9
+ The original redirect content type (added in the previous minor) used a verbose Contentful model with separate `fromPage`/`fromArticle`/etc. reference fields (and same for "to"). This has been replaced with the clean model:
10
+
11
+ - `fromPath` (raw/custom) + `fromInternal` (single Link→Entry)
12
+ - `toPath` (raw/external) + `toInternal` (single Link→Entry)
13
+
14
+ Both internal fields are restricted via `linkContentType` validation to: page, article, articleType, person, tag, customType, tagType.
15
+
16
+ Updated:
17
+
18
+ - Migration `scripts/migrations/18-create-redirect-content-type.js` (now a clean create for the good model; delete any previous "Redirect" CT first if you ran the old multi-field version).
19
+ - `BaseRedirectSkeleton`, `baseRedirectConverter`, and `IRedirect` (provenance is now `fromInternal`/`toInternal` as `IInternalLink`).
20
+ - `UrlCalculators` now requires a `tagType(slug)` function (to support selecting tag type indexes as redirect targets).
21
+ - Added `baseTagTypeLinkConverter`, registered the resolver, extended `BaseLink` / `InternalType` / exported `ITagTypeLink` + guard.
22
+
23
+ Docs and the `se-marketing-sites-redirects` skill have been updated with the new CT shape, help text guidance, and testing steps.
24
+
25
+ Consumers will need to implement `tagType` in their urlCalculators (and pass it through `createBaseConverterContext`) to select Tag Type entries in redirects and to satisfy the updated type.
26
+
27
+ The runtime redirect map, middleware, and `buildRedirectMap` behaviour are unchanged.
28
+
29
+ ## 1.1.0
30
+
31
+ ### Minor Changes
32
+
33
+ - 31b2200: Add first-class support for managing redirects from Contentful.
34
+
35
+ Editors can now create `redirect` entries (via new content type) using reference pickers for existing Pages/Articles/etc. (for "from" and "to") or raw paths/URLs, plus a status code dropdown (301/302/etc.), active flag, and notes. This makes redirects easy for non-technical users without requiring them to know or type exact slugs.
36
+
37
+ Redirects use the simple rebuild pattern (already proven by A/B tests):
38
+
39
+ - Publish in Contentful → webhook triggers a Vercel Deploy Hook.
40
+ - Build fetches current published redirects (via `getRedirectsWithErrors` / `getRedirectMap` on the helpers from `createAppHelpers`) and bakes a static map (using the new pure `buildRedirectMap`).
41
+ - Middleware performs exact (or lightly normalized) lookups against the baked data with zero runtime cost or external calls.
42
+
43
+ Key additions:
44
+
45
+ - `IRedirect` type + `isRedirect` guard.
46
+ - `BaseRedirectSkeleton`, `baseRedirectConverter` (resolves reference links to concrete `fromPath`/`to` using existing `resolveLink` + project `urlCalculators`).
47
+ - `contentfulRedirectsRest`, revalidation `RedirectTag`, and `buildRedirectMap` / `RedirectMap` types (exported from `@se-studio/contentful-rest-api`).
48
+ - `getRedirectsWithErrors` and `getRedirectMap` (convenience) exposed additively via `createAppHelpers` (like banners).
49
+ - Two Contentful migration scripts: `18-create-redirect-content-type.js` (full CT with rich help text for the picker UX) and `19-remove-redirectTo-fields.js` (cleans up legacy per-page `redirectTo` fields on page/pageVariant).
50
+ - New `@se-studio/skills` entry `se-marketing-sites-redirects` (the canonical guide, including CT spec, migration instructions, generate script example, static middleware example + A/B composition, deploy hook webhook setup, testing, and preview notes).
51
+ - Minimal demo in `example-empty` (middleware + `/api/redirects` route exercising the new helpers at runtime for easy local verification; production apps follow the baked recipe in the skill).
52
+ - Light updates to CLAUDE.md, vercel-setup skill, CONTENT_MODEL.md, and related docs.
53
+
54
+ The model and helpers are forward-compatible if a faster (e.g. Edge Config) path is added later. Legacy `redirectTo` fields are left untouched for now (removal is optional via the migration).
55
+
56
+ This is a new feature (minor bumps).
57
+
58
+ ### Patch Changes
59
+
60
+ - Complete support and documentation for filtered full article lists (#51) + fix client boundary regression (#52).
61
+
62
+ - Re-export `filterRelatedArticles` and `RelatedArticlesOptions` **only** from `@se-studio/core-ui/server` (removed from the main barrel). This keeps the primary `@se-studio/core-ui` entry fully client-safe for common imports (`cn`, `TrackedLink`, `AnalyticsProvider`, `Visual`, etc.). The main barrel no longer transitively pulls `'server-only'`.
63
+
64
+ - `getAllArticleLinks(options)` now fully supports the article listing filter fields (`articleTypeSlugs`/`Ids`, `tagSlugs`/`Ids`, `tagType*`, `authorSlugs`/`Ids` + `strictAuthorMatch`, `before`/`after`, `excludeArticleIds`, `count`, `allowUnindexed`) for News Grids, Publications Lists, person-scoped lists, tag filters, etc. The wrapper delegates to the enhanced `filterRelatedArticles` (OR semantics, slug support, strict author mode, distinct tag scoring, etc.).
65
+
66
+ - Added / expanded docs and examples in core-ui and contentful-rest-api READMEs + llms.md, CMS_INFRASTRUCTURE.md, SERVER_CLIENT_BOUNDARIES.md, the `se-marketing-sites-lib-cms-structure` skill (showing the `getAllArticleLinks({ articleTypeSlugs, strictAuthorMatch, ... })` pattern), and this repo's GitHub issues status tracker (new #51 entry + updated upgrade table).
67
+
68
+ - No changes to runtime behavior for existing bare `getAll*Links()` or `getRelated*` callers (sitemaps, search indexing, llms routes, etc. unaffected). Consumers should import filter symbols from the `/server` subpath when using them directly.
69
+
70
+ This finishes the implementation + docs for the DX improvement requested in #51 (reusable core filtering for full article collections instead of per-site `.filter()` shims). The boundary fix prevents the server-only error that appeared in any app importing from core-ui after the initial #51 changes.
71
+
3
72
  ## 1.0.42
4
73
 
5
74
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@se-studio/skills",
3
- "version": "1.0.42",
3
+ "version": "1.1.1",
4
4
  "description": "SE Studio agent skills for marketing site development with Contentful CMS",
5
5
  "repository": {
6
6
  "type": "git",
@@ -81,6 +81,23 @@ Components and collections in the registration chain (imported by `registrations
81
81
 
82
82
  These are passed via `projectRendererConfig` in `cms-server.ts`. `Section` and `SectionLinks` accept `previewHelpers` / `rendererConfig` props. See `docs/SERVER_CLIENT_BOUNDARIES.md`.
83
83
 
84
+ **Full article lists (News Grid / Publications List / ArticleFilter):** Use the new filter options on `getAllArticleLinks` instead of post-filtering the result in each app:
85
+
86
+ ```ts
87
+ // inside a collection renderer (server component)
88
+ const articlesRes = await rendererConfig.fetchHelpers?.getAllArticleLinks?.({
89
+ articleTypeSlugs: [NEWS_SLUG], // or articleTypeIds
90
+ // On person pages (strict, so pubs grid doesn't bleed into news grid):
91
+ // authorIds: [person.id],
92
+ // strictAuthorMatch: true,
93
+ // tag context:
94
+ // tagSlugs: contextTag ? [contextTag.slug] : undefined,
95
+ });
96
+ const articles = articlesRes?.data ?? [];
97
+ ```
98
+
99
+ `LinkFetchOptions` (and `filterRelatedArticles` / `RelatedArticlesOptions`) are available from `@se-studio/core-ui/server`. This moves the common type/tag/author logic into core (with OR semantics, scoring, indexed rules, legacy author handling still possible client-side or via custom wrapper). See GH #51.
100
+
84
101
  ## See Also
85
102
 
86
103
  - **register-cms-features** – Adding components and collections
@@ -0,0 +1,231 @@
1
+ ---
2
+ name: se-marketing-sites-redirects
3
+ description: "Add support for editor-managed URL redirects using a dedicated redirect content type + the proven A/B-style rebuild pattern (webhook to Vercel deploy hook, build bakes a static map). Includes the official migration script (create the clean CT with fromInternal/toInternal single-ref pickers)."
4
+ ---
5
+
6
+ # SE Marketing Sites — Redirects (rebuild pattern)
7
+
8
+ This skill adds the ability for **non-technical content editors** to manage redirects entirely from Contentful.
9
+
10
+ **Core idea (matches how your A/B tests already work):**
11
+ - Editors create `redirect` entries (they can **pick** existing Pages/Articles/etc. via the single From/To internal pickers or type raw paths).
12
+ - On publish, a Contentful webhook calls your Vercel **Deploy Hook**.
13
+ - The next build fetches the current redirects (via the normal CDA helpers), runs `buildRedirectMap`, and writes a tiny static file.
14
+ - Your `middleware.ts` imports that file and does the redirect **synchronously with zero runtime cost**.
15
+ - Propagation time = normal Vercel build (usually 1–3 minutes). Robust, no extra tokens or Edge Config required.
16
+
17
+ A faster "seconds" path (Edge Config) is explicitly **not** included in v1 per product direction — the model and helpers are forward-compatible if you add it later.
18
+
19
+ ## 1. Run the migration (create the content type)
20
+
21
+ The repo contains the official migration:
22
+
23
+ ```bash
24
+ # Create the redirect content type (clean fromPath + fromInternal / toPath + toInternal model)
25
+ node scripts/migrations/18-create-redirect-content-type.js
26
+ ```
27
+
28
+ Run it with the Contentful CLI (or the contentful-cms package tools) and the usual `CONTENTFUL_SPACE_ID` + `CONTENTFUL_MANAGEMENT_TOKEN` + environment.
29
+
30
+ 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 to get the clean single-ref model.
31
+
32
+ After running you will have a new (or repaired) content type called **Redirect**.
33
+
34
+ ## 2. The Redirect content type (what editors see)
35
+
36
+ Key fields (all documented with help text in the migration):
37
+
38
+ **From (source)**
39
+ - `fromPath` (raw path) — editors type `/old-url/` when they are not using the picker.
40
+ - `fromInternal` (single reference picker) — restricted to page, article, articleType, person, tag, customType, tagType. When an editor picks one, the system automatically uses that item's current URL. This is the magic that makes it easy for non-technical people.
41
+
42
+ **To (destination)**
43
+ - `toPath` (raw path or full external `https://...` URL)
44
+ - `toInternal` (single reference picker, same allowed types as fromInternal)
45
+
46
+ **Other**
47
+ - `statusCode` — dropdown: 301 (permanent — recommended), 302, 307, 308.
48
+ - `active` — boolean (default on). Turn off to disable a rule without deleting it.
49
+ - `note` — internal editor note (never shown on the site).
50
+ - `cmsLabel` — required internal label.
51
+
52
+ **Editor guidance (the help text says this):**
53
+ > For redirects from existing CMS content, use the "From internal" / "To internal" picker instead of typing the slug. Only use the raw path fields for old/vanity URLs that no longer exist in the CMS or for external sites.
54
+
55
+ The converter (`baseRedirectConverter`) resolves any chosen references into concrete `fromPath` / `to` strings at fetch time. The baked map only ever contains simple strings.
56
+
57
+ ## 3. Add the core support (already done by the time you read this)
58
+
59
+ The following are already in the packages:
60
+
61
+ - `IRedirect` + `isRedirect` in `@se-studio/core-data-types` (now uses `fromInternal` / `toInternal`)
62
+ - `baseRedirectConverter` + `BaseRedirectSkeleton`
63
+ - `contentfulRedirectsRest` + `buildRedirectMap` + `getRedirectsWithErrors` / `getRedirectMap` (via `createAppHelpers`)
64
+ - Revalidation tag `redirect` (so the existing webhook machinery knows about the new type)
65
+
66
+ You only need to wire the **rebuild** side in your app.
67
+
68
+ ## 4. Wire the rebuild path in your app (the only recipe for v1)
69
+
70
+ ### 4.1 Build-time generation (the heart of the simple path)
71
+
72
+ Create (or add to) a small script, e.g. `scripts/generate-redirects.ts`:
73
+
74
+ ```ts
75
+ import 'server-only';
76
+ import { writeFileSync, mkdirSync } from 'node:fs';
77
+ import { dirname, resolve } from 'node:path';
78
+ import { fileURLToPath } from 'node:url';
79
+
80
+ import {
81
+ buildRedirectMap,
82
+ getRedirectMap, // or use the helpers directly
83
+ } from '@se-studio/contentful-rest-api'; // or from your local cms-server re-exports
84
+
85
+ // In a real app you would use your project's buildInformation + converterContext + getConfig
86
+ // exactly like you do for sitemaps or other build-time data pulls.
87
+ // For simplicity many teams just import the same helpers used at runtime.
88
+
89
+ async function main() {
90
+ // Example using the convenience that already exists on the helpers object you already create:
91
+ // const { getRedirectMap } = createAppHelpers(...);
92
+ // const map = await getRedirectMap({});
93
+
94
+ // Minimal standalone version (adapt to your converterContext / getContentfulConfig):
95
+ // const map = buildRedirectMap(await fetchAllPublishedRedirectsSomehow());
96
+
97
+ const map = {}; // TODO: replace with real call using your existing helpers
98
+
99
+ const outPath = resolve(dirname(fileURLToPath(import.meta.url)), '../src/generated/redirects.ts');
100
+ mkdirSync(dirname(outPath), { recursive: true });
101
+
102
+ const file = `// AUTO-GENERATED by scripts/generate-redirects.ts — do not edit by hand
103
+ export const redirects = ${JSON.stringify(map, null, 2)} as const;
104
+ export type RedirectMap = typeof redirects;
105
+ `;
106
+ writeFileSync(outPath, file, 'utf8');
107
+ console.log(`Wrote ${Object.keys(map).length} redirects to ${outPath}`);
108
+ }
109
+
110
+ main().catch((e) => {
111
+ console.error(e);
112
+ process.exit(1);
113
+ });
114
+ ```
115
+
116
+ Wire it into the build:
117
+
118
+ ```json
119
+ // in the app's package.json
120
+ {
121
+ "scripts": {
122
+ "generate:redirects": "tsx scripts/generate-redirects.ts",
123
+ "prebuild": "pnpm generate:redirects",
124
+ "build": "next build"
125
+ }
126
+ }
127
+ ```
128
+
129
+ (You can also call it from a `next.config.ts` `webpack` hook or a turbo pipeline step — whatever is consistent with how you generate the A/B static data.)
130
+
131
+ ### 4.2 Static middleware (zero runtime work)
132
+
133
+ Create `middleware.ts` at the root of your Next app (alongside `next.config.ts`):
134
+
135
+ ```ts
136
+ import { NextResponse, type NextRequest } from 'next/server';
137
+ import { redirects } from './src/generated/redirects'; // the file we just generated
138
+
139
+ // If you also use A/B testing static middleware, import and compose here.
140
+ import { createStaticAbTestMiddleware } from '@se-studio/ab-testing/middleware';
141
+ // import { testsByPath } from './src/generated/abTests';
142
+
143
+ export async function middleware(request: NextRequest) {
144
+ const { pathname } = request.nextUrl;
145
+
146
+ // 1. Redirects first (cheap object lookup)
147
+ const rule = (redirects as Record<string, { to: string; status: number }>)[pathname];
148
+ if (rule) {
149
+ // You can add query param forwarding here if you added a field for it.
150
+ return NextResponse.redirect(new URL(rule.to, request.url), { status: rule.status });
151
+ }
152
+
153
+ // 2. A/B (if you use the static version)
154
+ // const ab = createStaticAbTestMiddleware({ testsByPath });
155
+ // const abRes = ab(request);
156
+ // if (abRes) return abRes;
157
+
158
+ return NextResponse.next();
159
+ }
160
+
161
+ export const config = {
162
+ matcher: [
163
+ /*
164
+ * Match all request paths except for the ones starting with:
165
+ * - api (API routes)
166
+ * - _next/static (static files)
167
+ * - _next/image (image optimization files)
168
+ * - favicon.ico, sitemap.xml, robots.txt, etc.
169
+ */
170
+ '/((?!api|_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
171
+ ],
172
+ };
173
+ ```
174
+
175
+ If you already have a `middleware.ts` for A/B or other things, just add the redirect check at the very top (before any other logic).
176
+
177
+ ### 4.3 Webhook = Deploy Hook (the trigger)
178
+
179
+ In Contentful → Settings → Webhooks, create (or reuse) a webhook that fires on:
180
+
181
+ - Entry publish / unpublish / delete for content type `redirect`
182
+
183
+ Point the URL at your **Vercel Deploy Hook** (Project → Settings → Deploy Hooks → create one for Production and optionally for Preview).
184
+
185
+ Add the usual headers your deploys need (`x-vercel-protection-bypass` if you use deployment protection, `REVALIDATION_SECRET` if you also want the normal reval to run, etc.).
186
+
187
+ That's it. Publish a redirect → Vercel starts a build → the generate step runs → the new static map is in the bundle → redirects work.
188
+
189
+ ## 5. Testing
190
+
191
+ 1. Run the create migration.
192
+ 2. In Contentful create a Redirect:
193
+ - Pick a real Page via the single "From internal" picker.
194
+ - Pick (or type) a destination via "To internal" or "To path".
195
+ - Status 301, Active on.
196
+ 3. Publish.
197
+ 4. Watch the deploy hook fire a build in Vercel.
198
+ 5. After the build finishes: `curl -I https://your-site/from-page-url/` → 301 to the target.
199
+ 6. Also test a raw-path rule and an external URL.
200
+ 7. Edit the rule → republish → new build → new behaviour.
201
+ 8. (Optional) If you also want to clean up legacy per-page `redirectTo` fields, run migration 19.
202
+
203
+ ## 6. Preview / draft deployments
204
+
205
+ Because each deployment builds from whatever is **published** in Contentful at build time, your preview deployments automatically see the current published redirects. No extra work.
206
+
207
+ (If you have a `DRAFT_ONLY` preview that shows unpublished content, the redirects list will still be the published ones — which is usually what you want for redirect rules.)
208
+
209
+ ## 7. Updating the skill / docs after changes
210
+
211
+ After you edit anything in this area, run:
212
+
213
+ ```bash
214
+ pnpm skills:validate
215
+ pnpm skills:sync
216
+ ```
217
+
218
+ Then commit the updated `.agents/skills/...` (the monorepo keeps them in sync with the canonical source in `packages/skills/skills/`).
219
+
220
+ ## 8. Future work (explicitly out of scope for v1)
221
+
222
+ - Edge Config + instant sync route (user request: "don't do the fast version at the moment").
223
+ - Wildcards / prefix matching / query param control.
224
+ - A "Redirects index" page visible to editors on the live site.
225
+ - Host-based or A/B-aware redirects.
226
+
227
+ The `redirect` content type and the `buildRedirectMap` / `getRedirectMap` helpers are intentionally stable so a future faster path can consume the same data.
228
+
229
+ ---
230
+
231
+ **You now have editor-managed redirects that are as robust and low-surprise as your A/B tests, using only patterns that already exist in the codebase.**
@@ -396,4 +396,19 @@ Expected: `200 OK`. A `401` means the secret in the header doesn't match the dep
396
396
 
397
397
  - **If inspector mode doesn't highlight fields**: check that `getPreviewFieldProps` is applied to field containers in the components. See `.cursorrules` for the pattern.
398
398
  - **For draft content visibility**: set `DRAFT_ONLY=true` in Vercel on a preview/staging environment (not production) so editors can see unpublished content without affecting live users.
399
+
400
+ ## (Optional) Redirects via the rebuild pattern
401
+
402
+ If the project uses the `se-marketing-sites-redirects` skill, also configure a webhook that fires on the new `redirect` content type and points at a **Vercel Deploy Hook** (not a revalidation URL).
403
+
404
+ See `.agents/skills/se-marketing-sites-redirects/SKILL.md` (or the source in `packages/skills/skills/se-marketing-sites-redirects/SKILL.md`) for:
405
+
406
+ - Running the two official migration scripts (create the CT + later remove legacy `redirectTo` fields)
407
+ - The exact content type fields + recommended help text
408
+ - The build-time generate script + static middleware
409
+ - Wiring the deploy hook webhook
410
+
411
+ This is deliberately the same "publish → deploy hook → build bakes static data" pattern used for A/B tests. It is the only supported path in v1 (Edge Config fast path is future work).
412
+
413
+ You can have both the normal revalidation webhooks **and** the redirect deploy-hook webhook active at the same time.
399
414
  - **To rotate the secret later**: re-run `scripts/setup-contentful-webhooks.ts` — it will generate a new secret and update everything automatically.