@cmssy/cli 11.6.0 → 11.8.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.
@@ -1,9 +1,6 @@
1
1
  "use client";
2
2
  import { CmssyLazyEditor } from "@cmssy/react/client";
3
3
 
4
- // The edit bridge is a client island: the editor talks to the page over
5
- // postMessage, and that protocol lives in @cmssy/core - not in React and not in
6
- // Next. That is why the same bridge works here.
7
4
  export default function CmssyEditor(props: Record<string, unknown>) {
8
5
  return <CmssyLazyEditor {...(props as never)} load={() => import("./blocks")} />;
9
6
  }
@@ -1,7 +1,5 @@
1
1
  import { fields, type BlockProps } from "@cmssy/react";
2
2
 
3
- // The schema lives next to the component that reads it, and types it. Rename a
4
- // field here and this file stops compiling - it cannot render an empty block.
5
3
  export const heroProps = {
6
4
  title: fields.text({ label: "Title", required: true }),
7
5
  subtitle: fields.text({ label: "Subtitle" }),
@@ -13,27 +13,11 @@ export interface LayoutSlotProps {
13
13
  locale: string;
14
14
  defaultLocale: string;
15
15
  enabledLocales: string[];
16
- /** Set on a VERIFIED editor request; the loader reports it as `isEdit`. */
17
16
  edit?: { editorOrigin: string | string[] };
18
- /**
19
- * The editor's block data, both halves, straight from the loader. Without
20
- * `resolvedContent` the canvas shows a relation field as the raw ids it
21
- * stores - the site looks right and the editor cannot fill the header.
22
- */
23
17
  data?: Record<string, unknown>;
24
18
  resolvedContent?: Record<string, Record<string, unknown>>;
25
19
  }
26
20
 
27
- /**
28
- * The header and footer, which are layout blocks rather than markup you own.
29
- *
30
- * A React island: Astro renders it on the server for visitors (no client JS),
31
- * and with `client:load` on the edit route, where the bridge needs the browser.
32
- *
33
- * Two renders, one per mode, and the difference is the point: server-rendered
34
- * for visitors, and through the edit bridge for the editor. A header still
35
- * server-rendered in edit mode is one the editor can select and cannot fill.
36
- */
37
21
  export default function LayoutSlot({
38
22
  groups,
39
23
  position,
@@ -1,7 +1,5 @@
1
1
  import { defineCmssyConfig } from "@cmssy/astro";
2
2
 
3
- // Pass the env raw: a `?? ""` fallback turns a missing variable into an empty
4
- // one, and the error then surfaces somewhere unrelated.
5
3
  export const cmssy = defineCmssyConfig({
6
4
  org: process.env.CMSSY_ORG_SLUG,
7
5
  workspaceSlug: process.env.CMSSY_WORKSPACE_SLUG,
@@ -2,10 +2,6 @@ import { CmssyBlock, buildBlockMap, buildBlockContext } from "@cmssy/react";
2
2
  import type { CmssyPageData } from "@cmssy/core";
3
3
  import { blocks } from "../cmssy/blocks";
4
4
 
5
- /**
6
- * The public page: React blocks rendered on the server by Astro, with no client
7
- * JS at all. The visitor gets HTML; the framework stays out of the render path.
8
- */
9
5
  export function Blocks({
10
6
  page,
11
7
  locale,
@@ -1,6 +1,4 @@
1
1
  import { cmssyMiddleware } from "@cmssy/astro";
2
2
  import { cmssy } from "./cmssy.config";
3
3
 
4
- // The whole adapter. It resolves the language, routes a verified editor request
5
- // to /cmssy-edit, and applies the CSP that lets the admin frame this site.
6
4
  export const onRequest = cmssyMiddleware(cmssy);
@@ -7,8 +7,6 @@ import LayoutSlot from "../cmssy/layout-slot";
7
7
 
8
8
  export const prerender = false;
9
9
 
10
- // `blocks` is what lets the loader resolve the editor's layout data. Drop it
11
- // and the site still renders - the editor just cannot fill the header.
12
10
  const { page, layouts, locale, defaultLocale, enabledLocales } = await loadCmssyPage(
13
11
  cmssy,
14
12
  Astro.request,
@@ -5,12 +5,8 @@ import { blocks } from "../../cmssy/blocks";
5
5
  import CmssyEditor from "../../cmssy/editor";
6
6
  import LayoutSlot from "../../cmssy/layout-slot";
7
7
 
8
- // The editor iframe lands here (the middleware rewrites verified requests). It
9
- // must be dynamic: a prerendered page never sees the query string.
10
8
  export const prerender = false;
11
9
 
12
- // `blocks` is what resolves the editor's layout data. Without it the editor
13
- // can select the header and not fill it, and a relation field shows raw ids.
14
10
  const { page, layouts, locale, defaultLocale, enabledLocales, editorData } =
15
11
  await loadCmssyPage(cmssy, Astro.request, Astro.url, { blocks });
16
12
  const editorOrigin = resolveEditorOrigin(cmssy.editorOrigin);
@@ -10,11 +10,6 @@ type PageProps = {
10
10
  searchParams: Promise<Record<string, string | string[] | undefined>>;
11
11
  };
12
12
 
13
- // Three lines that decide whether this site is cached at all. Without
14
- // generateStaticParams a catch-all is rendered on demand every time and
15
- // `revalidate` is ignored - the build prints a blank Revalidate column, which is
16
- // the only warning you get. `dynamicParams` keeps pages published after the
17
- // build working: first request renders them, and they cache from then on.
18
13
  export const revalidate = 3600;
19
14
  export const dynamicParams = true;
20
15
 
@@ -24,10 +19,6 @@ export function generateStaticParams() {
24
19
 
25
20
  const CmssyPage = createCmssyPage(cmssy, blocks, { editor: CmssyEditor });
26
21
 
27
- // The header and footer are layout blocks. They are mounted here rather than in
28
- // app/layout.tsx because this route knows its path, and the language prefix in
29
- // that path is what says which language to render them in - reading it from the
30
- // request header instead would make every page dynamic.
31
22
  export default async function Page(props: PageProps) {
32
23
  const { path } = await props.params;
33
24
  const slot = (position: "header" | "footer") => (
@@ -8,11 +8,6 @@ import { blocks } from "@/cmssy/blocks";
8
8
  import { CmssyEditor } from "@/cmssy/editor";
9
9
  import { EditableLayout } from "@/cmssy/editable-layout";
10
10
 
11
- // The route the middleware rewrites a verified editor request onto. The public
12
- // pages stay static, and a static page never sees the query string that would
13
- // put it in edit mode - which is why the editor needs a route of its own.
14
- //
15
- // Delete this file and the editor preview goes blank.
16
11
  export const dynamic = "force-dynamic";
17
12
 
18
13
  type PageProps = {
@@ -24,15 +19,9 @@ const CmssyEditPage = createCmssyEditPage(cmssy, blocks, {
24
19
  editor: CmssyEditor,
25
20
  });
26
21
 
27
- // The same chrome the public route renders - the editor frames THIS page, so a
28
- // header missing here is a header missing in the editor.
29
22
  export default async function EditPage(props: PageProps) {
30
23
  const { path } = await props.params;
31
24
 
32
- // Not a hard `true`: this route is reachable directly, and only the proxy's
33
- // verified rewrite sets the edit header. Hard-coding it would fetch layouts
34
- // with the draft secret for anyone who typed the URL. Reading a header costs
35
- // nothing here - the route is force-dynamic either way.
36
25
  const editMode = await isCmssyEditMode();
37
26
 
38
27
  const slot = (position: "header" | "footer") => (
@@ -1,7 +1,5 @@
1
1
  import { fields, type BlockProps } from "@cmssy/react";
2
2
 
3
- // The schema lives next to the component that reads it, and types it. Rename a
4
- // field here and this file stops compiling - it cannot render an empty block.
5
3
  export const heroProps = {
6
4
  heading: fields.text({ label: "Heading", required: true }),
7
5
  text: fields.textarea({ label: "Text" }),
@@ -6,12 +6,4 @@ export const heroBlock = defineBlock({
6
6
  label: "Hero",
7
7
  component: Hero,
8
8
  props: heroProps,
9
- // A loader runs SERVER-SIDE before the block renders, so a block can read data
10
- // (products, posts) the same way a page can. Import it dynamically: it may
11
- // touch the cmssy config, which must never reach the browser.
12
- //
13
- // loader: async () => {
14
- // const { loadPosts } = await import("./load-posts");
15
- // return { posts: await loadPosts() };
16
- // },
17
9
  });
@@ -1,5 +1,3 @@
1
1
  import { heroBlock } from "@/blocks/hero/block";
2
2
 
3
- // Every block your app renders. The editor loads this list lazily; the server
4
- // renders from it directly.
5
3
  export const blocks = [heroBlock];
@@ -5,8 +5,6 @@ import {
5
5
  type CmssyLazyLayoutProps,
6
6
  } from "@cmssy/react/client";
7
7
 
8
- // Mounts the layout blocks through the edit bridge, so the header and the footer
9
- // are editable in the editor rather than markup it can select and cannot fill.
10
8
  export function EditableLayout(props: Omit<CmssyLazyLayoutProps, "load">) {
11
9
  return <CmssyLazyLayout {...props} load={() => import("./blocks")} />;
12
10
  }
@@ -3,8 +3,6 @@
3
3
  import { CmssyLazyEditor } from "@cmssy/react/client";
4
4
  import type { CmssyEditorProps } from "@cmssy/next";
5
5
 
6
- // The block registry is loaded lazily ON THE CLIENT, so block loaders - which
7
- // run server-side and read the config - never reach the browser bundle.
8
6
  export function CmssyEditor(props: CmssyEditorProps) {
9
7
  return <CmssyLazyEditor {...props} load={() => import("./blocks")} />;
10
8
  }
@@ -1,12 +1,5 @@
1
1
  import { defineCmssyConfig } from "@cmssy/next";
2
2
 
3
- // Pass process.env raw: defineCmssyConfig validates at startup and names any
4
- // variable you are missing. A `?? ""` fallback would hide that, and the error
5
- // would surface later, somewhere unrelated.
6
- //
7
- // This module reads server env. Never import a VALUE from it (or from a module
8
- // that imports it) in a "use client" component - types are erased, values drag
9
- // process.env into the browser bundle.
10
3
  export const cmssy = defineCmssyConfig({
11
4
  org: process.env.CMSSY_ORG_SLUG,
12
5
  workspaceSlug: process.env.CMSSY_WORKSPACE_SLUG,
@@ -1,13 +1,8 @@
1
1
  import { createCmssyProxy } from "@cmssy/next/middleware";
2
2
  import { cmssy } from "@/cmssy.config";
3
3
 
4
- // Resolves the language, sends verified editor traffic to /cmssy-edit (carrying
5
- // that language and the edit flag), and lets the cmssy admin frame this site.
6
- // The order matters, which is why it lives in the preset and not here.
7
4
  export const proxy = createCmssyProxy(cmssy);
8
5
 
9
- // Next parses this at compile time, so the matcher has to be a literal - an
10
- // imported constant is rejected. Skips Next internals, API routes and files.
11
6
  export const config = {
12
7
  matcher: ["/((?!_next/|api/|.*\\..*).*)"],
13
8
  };
@@ -1,9 +1,6 @@
1
1
  import { createCmssyClient } from "@cmssy/core";
2
2
  import { cmssy } from "@/cmssy.config";
3
3
 
4
- // Your query, in your repo. The SDK does not mirror the graph - it gives you a
5
- // client and gets out of the way, so adding a field here is your call and not a
6
- // release of ours.
7
4
  const PUBLISHED_PAGES = `query PublishedPages($workspaceSlug: String!) {
8
5
  public {
9
6
  page {
@@ -27,24 +24,12 @@ const client = createCmssyClient({
27
24
  workspaceSlug: cmssy.workspaceSlug,
28
25
  });
29
26
 
30
- /**
31
- * The paths to prerender, as `generateStaticParams` wants them.
32
- *
33
- * A catch-all route with no params generated is served on demand and cached by
34
- * nothing, whatever `revalidate` says - so this function is what makes the
35
- * route cacheable at all, not an optimisation on top of it.
36
- */
37
27
  export async function publishedPaths(): Promise<{ path: string[] }[]> {
38
28
  const data = await client.query<PublishedPages>(
39
29
  PUBLISHED_PAGES,
40
30
  { workspaceSlug: cmssy.workspaceSlug },
41
31
  {
42
- // `public` routes through the org-scoped delivery path. Without it the
43
- // request lands on the base endpoint, where an unauthenticated workspace
44
- // lookup is by slug alone - across every org.
45
32
  public: true,
46
- // A build reads this once. A single 429 from the delivery API would fail
47
- // the whole deploy, so retry - it is a read, nothing to double-apply.
48
33
  retry: {},
49
34
  },
50
35
  );
@@ -1,8 +1,6 @@
1
1
  import { CmssyLazyEditor } from "@cmssy/react/client";
2
2
  import type { CmssyPageData } from "@cmssy/core";
3
3
 
4
- // The edit bridge: the editor talks to the page over postMessage, and that
5
- // protocol lives in @cmssy/core - not in React and not in a framework.
6
4
  export function CmssyEditor(props: {
7
5
  page: CmssyPageData | null;
8
6
  locale: string;
@@ -1,7 +1,5 @@
1
1
  import { fields, type BlockProps } from "@cmssy/react";
2
2
 
3
- // The schema lives next to the component that reads it, and types it. Rename a
4
- // field here and this file stops compiling - it cannot render an empty block.
5
3
  export const heroProps = {
6
4
  title: fields.text({ label: "Title", required: true }),
7
5
  subtitle: fields.text({ label: "Subtitle" }),
@@ -13,24 +13,11 @@ export interface LayoutSlotProps {
13
13
  locale: string;
14
14
  defaultLocale: string;
15
15
  enabledLocales: string[];
16
- /** Set on a VERIFIED editor request; the loader reports it as `isEdit`. */
17
16
  edit?: { editorOrigin: string | string[] };
18
- /**
19
- * The editor's block data, both halves, straight from the loader. Without
20
- * `resolvedContent` the canvas shows a relation field as the raw ids it
21
- * stores - the site looks right and the editor cannot fill the header.
22
- */
23
17
  data?: Record<string, unknown>;
24
18
  resolvedContent?: Record<string, Record<string, unknown>>;
25
19
  }
26
20
 
27
- /**
28
- * The header and footer, which are layout blocks rather than markup you own.
29
- *
30
- * Two renders, one per mode, and the difference is the point: server-rendered
31
- * for visitors, and through the edit bridge for the editor. A header still
32
- * server-rendered in edit mode is one the editor can select and cannot fill.
33
- */
34
21
  export function LayoutSlot({
35
22
  groups,
36
23
  position,
@@ -6,12 +6,8 @@ import { CmssyEditor } from "../cmssy/editor";
6
6
  import { LayoutSlot } from "../cmssy/layout-slot";
7
7
  import type { Route } from "./+types/page";
8
8
 
9
- // `blocks` is what lets the loader resolve the editor's layout data. Drop it
10
- // and the site still renders - the editor just cannot fill the header.
11
9
  export const loader = createCmssyLoader(cmssy, { blocks });
12
10
 
13
- // Without these the admin cannot frame the site, and the editor shows an empty
14
- // box with no error anywhere.
15
11
  export const headers = createCmssyHeaders(cmssy);
16
12
 
17
13
  export default function CmssyPage({ loaderData }: Route.ComponentProps) {
@@ -31,8 +27,6 @@ export default function CmssyPage({ loaderData }: Route.ComponentProps) {
31
27
  return <div dangerouslySetInnerHTML={{ __html: diagnostics }} />;
32
28
  }
33
29
 
34
- // A verified editor request renders the same page through the edit bridge.
35
- // No separate route: a React Router page always sees its query string.
36
30
  const slot = (position: "header" | "footer") => (
37
31
  <LayoutSlot
38
32
  groups={layouts}
@@ -1,7 +1,5 @@
1
1
  import { type RouteConfig, index, route } from "@react-router/dev/routes";
2
2
 
3
- // The splat does not match "/", so the homepage needs its own entry - it is the
4
- // same route module, mounted twice.
5
3
  export default [
6
4
  index("routes/page.tsx"),
7
5
  route("*", "routes/page.tsx", { id: "cmssy-catch-all" }),
@@ -1,7 +1,5 @@
1
1
  import { defineCmssyConfig } from "@cmssy/remix";
2
2
 
3
- // Pass the env raw: a `?? ""` fallback turns a missing variable into an empty
4
- // one, and the error then surfaces somewhere unrelated.
5
3
  export const cmssy = defineCmssyConfig({
6
4
  org: process.env.CMSSY_ORG_SLUG,
7
5
  workspaceSlug: process.env.CMSSY_WORKSPACE_SLUG,
package/dist/index.js CHANGED
@@ -163,19 +163,63 @@ var FRAMEWORKS = [
163
163
  detect: ["next"],
164
164
  dependencies: ["@cmssy/next", "@cmssy/react", "@cmssy/core"],
165
165
  files: [
166
- "cmssy.config.ts",
167
- "proxy.ts",
168
- "services/pages.ts",
169
- "cmssy/blocks.ts",
170
- "cmssy/editor.tsx",
171
- "cmssy/editable-layout.tsx",
172
- "blocks/hero/block.ts",
173
- "blocks/hero/Hero.tsx",
174
- "app/[[...path]]/layout.tsx",
175
- "app/[[...path]]/page.tsx",
176
- "app/cmssy-edit/[[...path]]/layout.tsx",
177
- "app/cmssy-edit/[[...path]]/page.tsx",
178
- "app/api/draft/route.ts"
166
+ {
167
+ path: "cmssy.config.ts",
168
+ purpose: "reads server env and validates it at startup; server-only, never import a value from it in a client component"
169
+ },
170
+ {
171
+ path: "proxy.ts",
172
+ purpose: "resolves the language, rewrites verified editor traffic to /cmssy-edit, lets the admin frame the site; its matcher must stay a literal"
173
+ },
174
+ {
175
+ path: "services/pages.ts",
176
+ purpose: "your delivery queries, in your repo; generateStaticParams lives here and is what makes the catch-all cacheable at all"
177
+ },
178
+ {
179
+ path: "cmssy/blocks.ts",
180
+ purpose: "every block the app renders - the server renders from this list, the editor loads it lazily"
181
+ },
182
+ {
183
+ path: "cmssy/editor.tsx",
184
+ purpose: "loads the block registry on the client, so server-side block loaders never reach the browser bundle"
185
+ },
186
+ {
187
+ path: "cmssy/editable-layout.tsx",
188
+ purpose: "mounts header and footer through the edit bridge; without it the editor can select them and cannot fill them"
189
+ },
190
+ {
191
+ path: "blocks/hero/block.ts",
192
+ purpose: "an example block: schema beside the component that reads it (loaders: docs/building-blocks/server-loaders.md)"
193
+ },
194
+ {
195
+ path: "blocks/hero/Hero.tsx",
196
+ purpose: "its component - renaming a schema field stops this compiling"
197
+ },
198
+ {
199
+ path: "app/[[...path]]/layout.tsx",
200
+ purpose: "the public root layout; <html lang> comes from the route, so put your global CSS and metadata here"
201
+ },
202
+ {
203
+ path: "app/[[...path]]/page.tsx",
204
+ purpose: "the public catch-all: generateStaticParams plus dynamicParams decide whether this site is cached at all"
205
+ },
206
+ {
207
+ path: "app/cmssy-edit/[[...path]]/layout.tsx",
208
+ purpose: "the editor's root layout - a separate root, so CSS and metadata have to be repeated here"
209
+ },
210
+ {
211
+ path: "app/cmssy-edit/[[...path]]/page.tsx",
212
+ purpose: "the route the proxy rewrites a verified editor request onto; delete it and the editor preview goes blank"
213
+ },
214
+ {
215
+ path: "app/api/draft/route.ts",
216
+ purpose: "draft preview without the editor - enters draft mode for a verified secret"
217
+ }
218
+ ],
219
+ warnings: [
220
+ 'pass process.env values raw into defineCmssyConfig - a `?? ""` fallback turns a missing variable into an empty one and the error surfaces somewhere unrelated',
221
+ "the two root layouts are separate: global CSS or metadata added to one and not the other shows up as an editor preview with no styles",
222
+ "a page without generateStaticParams renders on demand and ignores revalidate; the build's blank Revalidate column is the only warning you get"
179
223
  ]
180
224
  },
181
225
  {
@@ -185,15 +229,46 @@ var FRAMEWORKS = [
185
229
  detect: ["astro"],
186
230
  dependencies: ["@cmssy/astro", "@cmssy/react", "@cmssy/core"],
187
231
  files: [
188
- "src/cmssy.config.ts",
189
- "src/middleware.ts",
190
- "src/cmssy/blocks.ts",
191
- "src/cmssy/editor.tsx",
192
- "src/cmssy/layout-slot.tsx",
193
- "src/cmssy/hero.tsx",
194
- "src/components/Blocks.tsx",
195
- "src/pages/[...path].astro",
196
- "src/pages/cmssy-edit/[...path].astro"
232
+ {
233
+ path: "src/cmssy.config.ts",
234
+ purpose: "reads env and validates it at startup; pass the values raw so a missing one is named here"
235
+ },
236
+ {
237
+ path: "src/middleware.ts",
238
+ purpose: "the whole adapter: resolves the language, rewrites verified editor requests to /cmssy-edit, applies the framing CSP"
239
+ },
240
+ {
241
+ path: "src/cmssy/blocks.ts",
242
+ purpose: "every block the app renders"
243
+ },
244
+ {
245
+ path: "src/cmssy/editor.tsx",
246
+ purpose: "the edit bridge as a client island - the postMessage protocol lives in @cmssy/core, which is why it works outside React frameworks"
247
+ },
248
+ {
249
+ path: "src/cmssy/layout-slot.tsx",
250
+ purpose: "header and footer: server-rendered for visitors, through the edit bridge on the edit route; resolvedContent is what stops a relation field showing raw ids"
251
+ },
252
+ {
253
+ path: "src/cmssy/hero.tsx",
254
+ purpose: "an example block - schema beside the component that reads it"
255
+ },
256
+ {
257
+ path: "src/components/Blocks.tsx",
258
+ purpose: "renders React blocks on the server with no client JS - the visitor gets HTML"
259
+ },
260
+ {
261
+ path: "src/pages/[...path].astro",
262
+ purpose: "the public catch-all; its `blocks` prop is what lets the loader resolve the editor's layout data"
263
+ },
264
+ {
265
+ path: "src/pages/cmssy-edit/[...path].astro",
266
+ purpose: "where the editor iframe lands - must stay dynamic, because a prerendered page never sees the query string"
267
+ }
268
+ ],
269
+ warnings: [
270
+ 'pass env values raw into defineCmssyConfig - a `?? ""` fallback hides a missing variable until it breaks somewhere unrelated',
271
+ "the `blocks` prop on both page routes is not decoration: without it the site still renders and the editor can select a header it cannot fill"
197
272
  ]
198
273
  },
199
274
  {
@@ -203,14 +278,42 @@ var FRAMEWORKS = [
203
278
  detect: ["react-router", "@react-router/dev", "@remix-run/react"],
204
279
  dependencies: ["@cmssy/remix", "@cmssy/react", "@cmssy/core"],
205
280
  files: [
206
- "cmssy.config.ts",
207
- "app/root.tsx",
208
- "app/routes.ts",
209
- "app/cmssy/blocks.ts",
210
- "app/cmssy/editor.tsx",
211
- "app/cmssy/layout-slot.tsx",
212
- "app/cmssy/hero.tsx",
213
- "app/routes/page.tsx"
281
+ {
282
+ path: "cmssy.config.ts",
283
+ purpose: "reads env and validates it at startup; pass the values raw so a missing one is named here"
284
+ },
285
+ {
286
+ path: "app/root.tsx",
287
+ purpose: "the root layout - <html lang> comes from useCmssyLocale(), so a prefixed URL declares the language it renders"
288
+ },
289
+ {
290
+ path: "app/routes.ts",
291
+ purpose: 'mounts the page module twice: the splat does not match "/", so the homepage needs its own entry'
292
+ },
293
+ {
294
+ path: "app/cmssy/blocks.ts",
295
+ purpose: "every block the app renders"
296
+ },
297
+ {
298
+ path: "app/cmssy/editor.tsx",
299
+ purpose: "the edit bridge - the postMessage protocol lives in @cmssy/core, not in the framework"
300
+ },
301
+ {
302
+ path: "app/cmssy/layout-slot.tsx",
303
+ purpose: "header and footer: server-rendered for visitors, through the edit bridge for the editor; resolvedContent is what stops a relation field showing raw ids"
304
+ },
305
+ {
306
+ path: "app/cmssy/hero.tsx",
307
+ purpose: "an example block - schema beside the component that reads it"
308
+ },
309
+ {
310
+ path: "app/routes/page.tsx",
311
+ purpose: "one route for both modes - a React Router page always sees its query string, so there is no separate edit route; its headers are what let the admin frame the site"
312
+ }
313
+ ],
314
+ warnings: [
315
+ 'pass env values raw into defineCmssyConfig - a `?? ""` fallback hides a missing variable until it breaks somewhere unrelated',
316
+ "the route's `blocks` prop and its CSP headers are load-bearing: drop either and the site renders while the editor shows an empty box with no error"
214
317
  ]
215
318
  }
216
319
  ];
@@ -511,10 +614,15 @@ var CLI_PACKAGE_JSON = fileURLToPath(
511
614
  function frameworkFiles(framework, root) {
512
615
  const srcPrefix = framework.name === "next" ? nextSrcPrefix(root) : "";
513
616
  const files = [
514
- { asset: "env.example", target: ".env.example" },
515
- ...framework.files.map((path) => ({
617
+ {
618
+ asset: "env.example",
619
+ target: ".env.example",
620
+ purpose: "the variables the config reads - copy it to .env.local and fill them in"
621
+ },
622
+ ...framework.files.map(({ path, purpose }) => ({
516
623
  asset: path,
517
- target: framework.name === "next" ? `${srcPrefix}${path}` : path
624
+ target: framework.name === "next" ? `${srcPrefix}${path}` : path,
625
+ purpose
518
626
  }))
519
627
  ];
520
628
  if (framework.name === "next" && existingFile(root, `${srcPrefix}app/layout`)) {
@@ -648,6 +756,7 @@ function runInit(options, deps) {
648
756
  copyFileSync(join3(ASSETS_DIR, framework.name, file.asset), target);
649
757
  written.push(file.target);
650
758
  log(formatResult({ status: "ok", message: `wrote ${file.target}` }));
759
+ log(` ${file.purpose}`);
651
760
  }
652
761
  const added = addDependencies(root, pkg, framework);
653
762
  if (added.length > 0) {
@@ -666,6 +775,10 @@ function runInit(options, deps) {
666
775
  `${written.length} file${written.length === 1 ? "" : "s"} written, ${skipped.length} skipped.`
667
776
  );
668
777
  log("");
778
+ log("What not to break:");
779
+ for (const warning of framework.warnings) log(` - ${warning}`);
780
+ log(" full wiring, and what each file is doing: docs/wiring.md");
781
+ log("");
669
782
  log("Next steps:");
670
783
  let step = 1;
671
784
  if (added.length > 0) log(` ${step++}. ${detectInstallCommand(root)}`);
@@ -1237,8 +1350,6 @@ function generateOperationsFile() {
1237
1350
  ""
1238
1351
  ].join("\n");
1239
1352
  const documents = CMSSY_DELIVERY_OPERATIONS.map(
1240
- // A newline in `purpose` would emit bare prose into the document and break
1241
- // parsing for every consumer.
1242
1353
  (operation) => `# ${operation.purpose.replace(/\s+/g, " ").trim()}
1243
1354
  ${operation.document}`
1244
1355
  ).join("\n\n");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmssy/cli",
3
- "version": "11.6.0",
3
+ "version": "11.8.0",
4
4
  "description": "The cmssy CLI: `cmssy init` wires cmssy into an existing Next.js, Astro or React Router app; `cmssy link` connects it to a workspace and verifies the editor wiring.",
5
5
  "keywords": [
6
6
  "cmssy",
@@ -27,7 +27,7 @@
27
27
  "assets"
28
28
  ],
29
29
  "dependencies": {
30
- "@cmssy/core": "11.6.0"
30
+ "@cmssy/core": "11.8.0"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/node": "^20.0.0",