@mikulgohil/ai-kit 1.8.0 → 1.9.0

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mikulgohil/ai-kit",
3
- "version": "1.8.0",
4
- "description": "AI-assisted development setup kit. Auto-detects your tech stack and generates tailored CLAUDE.md, .cursorrules, hooks, agents, context modes, slash commands, design token rules, component registries, developer guides, project principles, and spec-first workflows.",
3
+ "version": "1.9.0",
4
+ "description": "AI-assisted development setup kit. Auto-detects your tech stack (Next.js, Sitecore XM Cloud, Optimizely SaaS CMS, Tailwind, TypeScript, Turborepo) and generates tailored CLAUDE.md, .cursorrules, hooks, agents, context modes, slash commands, design token rules, component registries, developer guides, and spec-first workflows.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "ai-kit": "./dist/index.js"
@@ -37,6 +37,13 @@
37
37
  "nextjs",
38
38
  "tailwind",
39
39
  "sitecore",
40
+ "sitecore-xm-cloud",
41
+ "sitecore-jss",
42
+ "optimizely",
43
+ "optimizely-saas-cms",
44
+ "optimizely-graph",
45
+ "optimizely-visual-builder",
46
+ "headless-cms",
40
47
  "docker",
41
48
  "storybook",
42
49
  "i18n",
@@ -0,0 +1,384 @@
1
+ # Optimizely SaaS CMS
2
+
3
+ ## Architecture
4
+ - This is a headless CMS project using Optimizely SaaS CMS (CMS 13+) with Next.js as the frontend
5
+ - Content is authored in the Optimizely cloud editor and delivered via **Optimizely Graph** (GraphQL API at `https://cg.optimizely.com`)
6
+ - Components are mapped to CMS content types through a **ComponentFactory** pattern
7
+ - The SDK ecosystem is `@remkoj/optimizely-*` (community SDK by Remko Jantzen)
8
+ - Visual Builder enables drag-and-drop page composition with Experiences, Sections, Rows, Columns, and Elements
9
+
10
+ ## SDK Packages
11
+
12
+ | Package | Purpose |
13
+ |---|---|
14
+ | `@remkoj/optimizely-cms-react` | Core React components: `<CmsContent>`, `<CmsContentArea>`, `<CmsEditable>`, `<RichText>`, `<OptimizelyComposition>` |
15
+ | `@remkoj/optimizely-cms-nextjs` | Next.js integration: `createPage()`, `createPublishApi()`, `createEditPageComponent()`, middleware |
16
+ | `@remkoj/optimizely-graph-client` | GraphQL client for Optimizely Graph with HMAC/Basic/Token auth |
17
+ | `@remkoj/optimizely-graph-functions` | GraphQL Codegen preset for generating typed queries and fragments |
18
+ | `@remkoj/optimizely-cms-api` | REST API wrapper for CMS Integration API (content CRUD, type management) |
19
+ | `@remkoj/optimizely-cms-cli` | CLI (`opti-cms`) for type sync, component scaffolding, factory generation |
20
+ | `@remkoj/optimizely-graph-cli` | CLI (`opti-graph`) for webhook and source management |
21
+ | `@remkoj/optimizely-one-nextjs` | Browser-side Optimizely products (Web Experimentation, Data Platform, Recommendations) |
22
+
23
+ ## Content Type Hierarchy
24
+
25
+ | Type | Description | GraphQL Fragment Suffix |
26
+ |---|---|---|
27
+ | **Page** | Routable content with a URL path | `.page.graphql` |
28
+ | **Block/Component** | Reusable content placed in Content Areas | `.block.graphql` or `.component.graphql` |
29
+ | **Element** | Lightweight components for Visual Builder | `.element.graphql` |
30
+ | **Experience** | Visual Builder top-level compositions | `.experience.graphql` |
31
+ | **Section** | Visual Builder layout containers | `.section.graphql` |
32
+ | **Media/Image/Video** | Asset types | N/A |
33
+
34
+ ## Component Patterns
35
+
36
+ ### Page Component (catch-all route)
37
+ ```typescript
38
+ // src/app/[[...path]]/page.tsx
39
+ import { createPage } from '@remkoj/optimizely-cms-nextjs/page'
40
+ import { factory } from '@/components/factory'
41
+ import { createClient } from '@remkoj/optimizely-graph-client'
42
+ import { AuthMode } from '@remkoj/optimizely-graph-client'
43
+ import { draftMode } from 'next/headers'
44
+
45
+ const { CmsPage, generateMetadata, generateStaticParams } = createPage(factory, {
46
+ client(token?: string, mode?: 'request' | 'metadata') {
47
+ const client = createClient(undefined, token, { nextJsFetchDirectives: true })
48
+ if (mode === 'request') {
49
+ const { isEnabled } = draftMode()
50
+ if (isEnabled) {
51
+ client.updateAuthentication(AuthMode.HMAC)
52
+ client.enablePreview()
53
+ }
54
+ }
55
+ return client
56
+ },
57
+ })
58
+
59
+ export const dynamic = 'error'
60
+ export const dynamicParams = true
61
+ export const revalidate = false
62
+ export default CmsPage
63
+ export { generateMetadata, generateStaticParams }
64
+ ```
65
+
66
+ ### Component Factory Setup
67
+ ```typescript
68
+ // src/components/factory.ts
69
+ import { DefaultComponentFactory, RichTextComponentDictionary } from '@remkoj/optimizely-cms-react/rsc'
70
+ import { CmsFactory } from './cms/index'
71
+
72
+ const factory = new DefaultComponentFactory()
73
+ factory.registerAll(RichTextComponentDictionary)
74
+ factory.registerAll(CmsFactory)
75
+
76
+ export { factory }
77
+ ```
78
+
79
+ ### Component Dictionary (auto-generated by `opti-cms nextjs:factory`)
80
+ ```typescript
81
+ // src/components/cms/index.ts
82
+ import type { ComponentTypeDictionary } from '@remkoj/optimizely-cms-react'
83
+
84
+ import StandardPage from './page/StandardPage'
85
+ import HeroBlock from './block/HeroBlock'
86
+ import TextElement from './element/TextElement'
87
+
88
+ export const CmsFactory: ComponentTypeDictionary = [
89
+ { type: ['Page', 'StandardPage'], component: StandardPage },
90
+ { type: ['Block', 'HeroBlock'], component: HeroBlock },
91
+ { type: ['Element', 'TextElement'], component: TextElement },
92
+ ]
93
+ ```
94
+
95
+ ### CMS Content Component
96
+ ```typescript
97
+ // src/components/cms/block/HeroBlock/index.tsx
98
+ import { CmsEditable } from '@remkoj/optimizely-cms-react/rsc'
99
+ import { RichText } from '@remkoj/optimizely-cms-react/rsc'
100
+
101
+ interface HeroBlockProps {
102
+ data: {
103
+ heading?: string
104
+ description?: { html?: string }
105
+ image?: { url?: string; altText?: string }
106
+ ctaText?: string
107
+ ctaLink?: { default?: string }
108
+ }
109
+ }
110
+
111
+ export default function HeroBlock({ data }: HeroBlockProps) {
112
+ return (
113
+ <CmsEditable as="section" className="hero-block">
114
+ {data.heading && <h1>{data.heading}</h1>}
115
+ {data.description && <RichText html={data.description.html} />}
116
+ {data.image?.url && (
117
+ <img src={data.image.url} alt={data.image.altText || ''} />
118
+ )}
119
+ {data.ctaText && data.ctaLink?.default && (
120
+ <a href={data.ctaLink.default}>{data.ctaText}</a>
121
+ )}
122
+ </CmsEditable>
123
+ )
124
+ }
125
+ ```
126
+
127
+ ### GraphQL Fragment for a Component
128
+ ```graphql
129
+ # src/components/cms/block/HeroBlock/HeroBlock.block.graphql
130
+ fragment HeroBlockData on HeroBlock {
131
+ heading
132
+ description {
133
+ html
134
+ }
135
+ image {
136
+ url
137
+ altText
138
+ }
139
+ ctaText
140
+ ctaLink {
141
+ default
142
+ }
143
+ }
144
+ ```
145
+
146
+ ## Visual Builder Composition
147
+
148
+ The Visual Builder outputs a **composition tree** that the frontend renders recursively:
149
+
150
+ ```
151
+ Experience (top-level)
152
+ -> Section (layout container)
153
+ -> Row
154
+ -> Column
155
+ -> Component (actual content element)
156
+ ```
157
+
158
+ Render compositions using `<OptimizelyComposition>`:
159
+ ```typescript
160
+ import { OptimizelyComposition } from '@remkoj/optimizely-cms-react/rsc'
161
+
162
+ // The composition data comes from GraphQL and is rendered automatically
163
+ // Structure nodes (experience, section, row, column) are layout containers
164
+ // Component nodes are resolved through the ComponentFactory
165
+ ```
166
+
167
+ ## GraphQL Codegen Setup
168
+ ```typescript
169
+ // codegen.ts
170
+ import type { CodegenConfig } from '@graphql-codegen/cli'
171
+ import getSchemaInfo from '@remkoj/optimizely-graph-client/codegen'
172
+ import OptimizelyGraphPreset from '@remkoj/optimizely-graph-functions/preset'
173
+
174
+ const config: CodegenConfig = {
175
+ schema: getSchemaInfo(),
176
+ documents: ['src/**/*.graphql'],
177
+ generates: {
178
+ 'src/gql/': {
179
+ preset: OptimizelyGraphPreset,
180
+ presetConfig: {
181
+ recursion: true,
182
+ gqlTagName: 'gql',
183
+ injections: [
184
+ { into: 'PageData', pathRegex: 'src/components/cms/.*\\.page\\.graphql' },
185
+ { into: 'BlockData', pathRegex: 'src/components/cms/.*\\.(block|component|section)\\.graphql' },
186
+ { into: 'ElementData', pathRegex: 'src/components/cms/.*\\.element\\.graphql' },
187
+ ],
188
+ },
189
+ },
190
+ },
191
+ }
192
+
193
+ export default config
194
+ ```
195
+
196
+ ## Cache Invalidation Webhook
197
+ ```typescript
198
+ // src/app/.well-known/publish/route.ts
199
+ import createPublishApi from '@remkoj/optimizely-cms-nextjs/publish'
200
+
201
+ const handler = createPublishApi({ optimizePublish: true })
202
+ export const GET = handler
203
+ export const POST = handler
204
+ ```
205
+
206
+ ## Preview / Edit Mode
207
+ ```typescript
208
+ // src/app/preview/page.tsx
209
+ import { createEditPageComponent } from '@remkoj/optimizely-cms-nextjs/preview'
210
+ import { factory } from '@/components/factory'
211
+ import { createClient } from '@remkoj/optimizely-graph-client'
212
+
213
+ export default createEditPageComponent(factory, {
214
+ loader: getContentById,
215
+ clientFactory: (token) => createClient(undefined, token, { cache: false }),
216
+ refreshTimeout: 500,
217
+ })
218
+ ```
219
+
220
+ ```typescript
221
+ // src/app/.well-known/drafts/route.ts
222
+ // Toggles Next.js draft mode for preview in the Optimizely editor
223
+ ```
224
+
225
+ ## CLI Commands
226
+
227
+ ### Content Type Management
228
+ ```bash
229
+ # Pull content types from CMS to local JSON files
230
+ npx opti-cms types:pull
231
+
232
+ # Push local content type definitions to CMS
233
+ npx opti-cms types:push
234
+
235
+ # Generate component factory from content types
236
+ npx opti-cms nextjs:factory
237
+
238
+ # Scaffold new component files
239
+ npx opti-cms nextjs:create
240
+
241
+ # Generate component implementations from types
242
+ npx opti-cms nextjs:components
243
+
244
+ # Generate GraphQL fragments for components
245
+ npx opti-cms nextjs:fragments
246
+ ```
247
+
248
+ ### Optimizely Graph Management
249
+ ```bash
250
+ # Manage webhooks for cache invalidation
251
+ npx opti-graph webhooks:list
252
+ npx opti-graph webhooks:create
253
+
254
+ # Manage content sources
255
+ npx opti-graph sources:list
256
+ ```
257
+
258
+ ### GraphQL Codegen
259
+ ```bash
260
+ # Generate typed queries and fragments
261
+ npx graphql-codegen
262
+ ```
263
+
264
+ ## Content Type Definitions
265
+
266
+ Content types are defined in `.opti-type.json` files:
267
+ ```json
268
+ {
269
+ "key": "HeroBlock",
270
+ "displayName": "Hero Block",
271
+ "description": "A hero banner with heading, description, and CTA",
272
+ "baseType": "block",
273
+ "properties": {
274
+ "heading": { "type": "string", "required": true },
275
+ "description": { "type": "richText" },
276
+ "image": { "type": "contentReference" },
277
+ "ctaText": { "type": "string" },
278
+ "ctaLink": { "type": "url" }
279
+ }
280
+ }
281
+ ```
282
+
283
+ Style definitions use `.opti-style.json` for Visual Builder styling:
284
+ ```json
285
+ {
286
+ "key": "HeroBlockStyles",
287
+ "displayName": "Hero Block Styles",
288
+ "target": "HeroBlock",
289
+ "properties": {
290
+ "theme": { "type": "select", "options": ["light", "dark"] },
291
+ "spacing": { "type": "select", "options": ["compact", "normal", "spacious"] }
292
+ }
293
+ }
294
+ ```
295
+
296
+ ## Environment Setup
297
+
298
+ Required `.env.local` variables:
299
+ ```
300
+ # Optimizely Graph (content delivery)
301
+ OPTIMIZELY_GRAPH_APP_KEY=your-app-key
302
+ OPTIMIZELY_GRAPH_SECRET=your-secret
303
+ OPTIMIZELY_GRAPH_SINGLE_KEY=your-single-key
304
+ OPTIMIZELY_GRAPH_GATEWAY=https://cg.optimizely.com
305
+ OPTIMIZELY_GRAPH_TENANT_ID=your-tenant-id
306
+
307
+ # Optimizely CMS (management API)
308
+ OPTIMIZELY_CMS_URL=https://your-instance.cms.optimizely.com
309
+ OPTIMIZELY_CMS_CLIENT_ID=your-client-id
310
+ OPTIMIZELY_CMS_CLIENT_SECRET=your-client-secret
311
+
312
+ # Frontend
313
+ SITE_DOMAIN=your-frontend-domain.com
314
+ OPTIMIZELY_PUBLISH_TOKEN=your-webhook-token
315
+
316
+ # Debug (optional)
317
+ OPTIMIZELY_DEBUG=0
318
+ ```
319
+
320
+ ## Project File Structure
321
+ ```
322
+ src/
323
+ app/
324
+ [[...path]]/page.tsx # Catch-all CMS page route (createPage)
325
+ preview/page.tsx # Visual editor preview page
326
+ .well-known/
327
+ drafts/route.ts # Draft mode toggle endpoint
328
+ publish/route.ts # Webhook cache invalidation endpoint
329
+ channel/route.ts # Channel info endpoint
330
+ layout.tsx # Root layout
331
+ components/
332
+ factory.ts # ComponentFactory setup
333
+ cms/
334
+ index.ts # Auto-generated component dictionary
335
+ page/ # Page-type components
336
+ StandardPage/
337
+ index.tsx
338
+ StandardPage.page.graphql
339
+ block/ # Block-type components
340
+ HeroBlock/
341
+ index.tsx
342
+ HeroBlock.block.graphql
343
+ element/ # Element-type components (Visual Builder)
344
+ TextElement/
345
+ index.tsx
346
+ TextElement.element.graphql
347
+ experience/ # Experience-type components (Visual Builder)
348
+ section/ # Section-type components (Visual Builder)
349
+ gql/ # Auto-generated by GraphQL Codegen
350
+ functions.ts
351
+ client.ts
352
+ types.ts
353
+ channel.ts # Channel/site definition
354
+ codegen.ts # GraphQL Codegen configuration
355
+ ```
356
+
357
+ ## Rules
358
+ - Always use `<CmsEditable>` wrapper for components that should be editable in the Optimizely editor
359
+ - Use `<RichText>` from `@remkoj/optimizely-cms-react/rsc` for rich text fields — never render raw HTML with `dangerouslySetInnerHTML`
360
+ - GraphQL fragments MUST follow the naming convention: `[Name].[type].graphql` (e.g., `HeroBlock.block.graphql`)
361
+ - Fragment file suffix determines injection target: `.page.graphql` -> PageData, `.block.graphql` -> BlockData, `.element.graphql` -> ElementData
362
+ - Always register new components in the ComponentFactory dictionary — use `opti-cms nextjs:factory` to regenerate
363
+ - Content type arrays use `[category, typeName]` format (e.g., `['Block', 'HeroBlock']`) — match the CMS type definition exactly
364
+ - Use `createPage()` from `@remkoj/optimizely-cms-nextjs/page` for the catch-all route — don't build custom routing
365
+ - Use `createPublishApi()` for the webhook endpoint — don't build custom cache invalidation
366
+ - Handle draft mode properly: check `draftMode().isEnabled` and switch to HMAC auth with `client.enablePreview()`
367
+ - Run `npx graphql-codegen` after changing `.graphql` files to regenerate typed queries
368
+ - Use `opti-cms types:pull` to sync content types from CMS to local JSON — don't manually create `.opti-type.json` files from scratch
369
+ - For Visual Builder components, follow the composition tree hierarchy: Experience > Section > Row > Column > Element
370
+ - Import RSC (React Server Component) versions from `@remkoj/optimizely-cms-react/rsc` — not from the base package
371
+ - Never hardcode content that should come from CMS content types
372
+ - Keep `codegen.ts` injection patterns in sync with your component folder structure
373
+
374
+ ## Common Mistakes to Avoid
375
+ - Don't render rich text with `dangerouslySetInnerHTML` — use `<RichText html={data.field.html} />`
376
+ - Don't forget to wrap editable components with `<CmsEditable>` — the editor won't recognize them without it
377
+ - Don't create GraphQL fragments with wrong file suffixes — `.block.graphql` and `.page.graphql` are injected into different parent fragments
378
+ - Don't manually edit `src/components/cms/index.ts` — it's auto-generated by `opti-cms nextjs:factory`
379
+ - Don't use `getStaticProps` or `getServerSideProps` — use `createPage()` which handles SSG/SSR/ISR automatically
380
+ - Don't forget to run `npx graphql-codegen` after adding/modifying GraphQL fragments
381
+ - Don't mix RSC and client component imports — use `/rsc` sub-path for Server Components
382
+ - Don't skip the `.well-known/publish` webhook route — without it, content updates won't invalidate the cache
383
+ - Don't hardcode the Graph gateway URL — use environment variables and let the client auto-configure
384
+ - Don't forget HMAC auth for preview mode — public auth only returns published content
@@ -0,0 +1,20 @@
1
+ # Optimizely SaaS CMS Rules
2
+
3
+ - This is a headless CMS project using **Optimizely SaaS CMS** with Next.js, delivered via **Optimizely Graph** (GraphQL)
4
+ - SDK packages: `@remkoj/optimizely-cms-react`, `@remkoj/optimizely-cms-nextjs`, `@remkoj/optimizely-graph-client`
5
+ - Use `<CmsEditable>` wrapper for all editable components — the editor won't recognize them without it
6
+ - Use `<RichText html={data.field.html} />` from `@remkoj/optimizely-cms-react/rsc` — never use `dangerouslySetInnerHTML`
7
+ - Import Server Component versions from `@remkoj/optimizely-cms-react/rsc` — not from the base package
8
+ - GraphQL fragment naming: `[Name].page.graphql`, `[Name].block.graphql`, `[Name].element.graphql` — suffix determines injection target
9
+ - Register components in ComponentFactory with `[category, typeName]` format (e.g., `['Block', 'HeroBlock']`)
10
+ - Use `createPage()` from `@remkoj/optimizely-cms-nextjs/page` for the catch-all `[[...path]]/page.tsx` route
11
+ - Use `createPublishApi()` for cache invalidation webhooks at `.well-known/publish/route.ts`
12
+ - Handle draft mode: check `draftMode().isEnabled`, switch to HMAC auth, call `client.enablePreview()`
13
+ - Run `npx graphql-codegen` after modifying `.graphql` files to regenerate typed queries
14
+ - Use `opti-cms nextjs:factory` to regenerate the component dictionary — don't manually edit the auto-generated index
15
+ - Content type definitions use `.opti-type.json` files — sync with `opti-cms types:pull` / `types:push`
16
+ - Visual Builder hierarchy: Experience > Section > Row > Column > Element — render with `<OptimizelyComposition>`
17
+ - Don't hardcode content that should come from CMS content types
18
+ - Don't use `getStaticProps`/`getServerSideProps` — `createPage()` handles SSG/SSR/ISR automatically
19
+ - Keep `codegen.ts` injection patterns in sync with component folder structure
20
+ - Environment: `OPTIMIZELY_GRAPH_SINGLE_KEY` (public), `OPTIMIZELY_GRAPH_APP_KEY` + `SECRET` (HMAC auth)