@cmssy/cli 10.6.1 → 10.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.
- package/assets/init/astro/src/cmssy/layout-slot.tsx +75 -0
- package/assets/init/astro/src/pages/[...path].astro +16 -1
- package/assets/init/astro/src/pages/cmssy-edit/[...path].astro +20 -1
- package/assets/init/remix/app/cmssy/layout-slot.tsx +72 -0
- package/assets/init/remix/app/routes/page.tsx +47 -22
- package/dist/index.js +31 -2
- package/package.json +2 -2
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CmssyBlock,
|
|
3
|
+
buildBlockContext,
|
|
4
|
+
buildBlockMap,
|
|
5
|
+
type CmssyLayoutGroup,
|
|
6
|
+
} from "@cmssy/react";
|
|
7
|
+
import { CmssyLazyLayout } from "@cmssy/react/client";
|
|
8
|
+
import { blocks } from "./blocks";
|
|
9
|
+
|
|
10
|
+
export interface LayoutSlotProps {
|
|
11
|
+
groups: CmssyLayoutGroup[];
|
|
12
|
+
position: "header" | "footer";
|
|
13
|
+
locale: string;
|
|
14
|
+
defaultLocale: string;
|
|
15
|
+
enabledLocales: string[];
|
|
16
|
+
/** Set on a VERIFIED editor request; the loader reports it as `isEdit`. */
|
|
17
|
+
edit?: { editorOrigin: string | string[] };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The header and footer, which are layout blocks rather than markup you own.
|
|
22
|
+
*
|
|
23
|
+
* A React island: Astro renders it on the server for visitors (no client JS),
|
|
24
|
+
* and with `client:load` on the edit route, where the bridge needs the browser.
|
|
25
|
+
*
|
|
26
|
+
* Two renders, one per mode, and the difference is the point: server-rendered
|
|
27
|
+
* for visitors, and through the edit bridge for the editor. A header still
|
|
28
|
+
* server-rendered in edit mode is one the editor can select and cannot fill.
|
|
29
|
+
*/
|
|
30
|
+
export default function LayoutSlot({
|
|
31
|
+
groups,
|
|
32
|
+
position,
|
|
33
|
+
locale,
|
|
34
|
+
defaultLocale,
|
|
35
|
+
enabledLocales,
|
|
36
|
+
edit,
|
|
37
|
+
}: LayoutSlotProps) {
|
|
38
|
+
if (edit) {
|
|
39
|
+
return (
|
|
40
|
+
<CmssyLazyLayout
|
|
41
|
+
groups={groups}
|
|
42
|
+
position={position}
|
|
43
|
+
locale={locale}
|
|
44
|
+
defaultLocale={defaultLocale}
|
|
45
|
+
enabledLocales={enabledLocales}
|
|
46
|
+
edit={edit}
|
|
47
|
+
load={() => import("./blocks")}
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const group = groups.find((candidate) => candidate.position === position);
|
|
53
|
+
if (!group) return null;
|
|
54
|
+
|
|
55
|
+
const blockMap = buildBlockMap(blocks);
|
|
56
|
+
const context = buildBlockContext(locale, defaultLocale, enabledLocales);
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<>
|
|
60
|
+
{[...group.blocks]
|
|
61
|
+
.filter((block) => block.isActive !== false)
|
|
62
|
+
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
|
|
63
|
+
.map((block) => (
|
|
64
|
+
<CmssyBlock
|
|
65
|
+
key={block.id}
|
|
66
|
+
block={block}
|
|
67
|
+
blockMap={blockMap}
|
|
68
|
+
locale={locale}
|
|
69
|
+
defaultLocale={defaultLocale}
|
|
70
|
+
context={context}
|
|
71
|
+
/>
|
|
72
|
+
))}
|
|
73
|
+
</>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
import { loadCmssyPage } from "@cmssy/astro";
|
|
3
3
|
import { cmssy } from "../cmssy.config";
|
|
4
4
|
import { Blocks } from "../components/Blocks";
|
|
5
|
+
import LayoutSlot from "../cmssy/layout-slot";
|
|
5
6
|
|
|
6
7
|
export const prerender = false;
|
|
7
8
|
|
|
8
|
-
const { page, locale, defaultLocale, enabledLocales } = await loadCmssyPage(
|
|
9
|
+
const { page, layouts, locale, defaultLocale, enabledLocales } = await loadCmssyPage(
|
|
9
10
|
cmssy,
|
|
10
11
|
Astro.request,
|
|
11
12
|
Astro.url,
|
|
@@ -22,6 +23,13 @@ if (!page) {
|
|
|
22
23
|
<title>{page?.seoTitle?.[locale] ?? page?.displayName?.[locale] ?? "cmssy on Astro"}</title>
|
|
23
24
|
</head>
|
|
24
25
|
<body>
|
|
26
|
+
<LayoutSlot
|
|
27
|
+
groups={layouts}
|
|
28
|
+
position="header"
|
|
29
|
+
locale={locale}
|
|
30
|
+
defaultLocale={defaultLocale}
|
|
31
|
+
enabledLocales={enabledLocales}
|
|
32
|
+
/>
|
|
25
33
|
{
|
|
26
34
|
page ? (
|
|
27
35
|
<Blocks
|
|
@@ -34,5 +42,12 @@ if (!page) {
|
|
|
34
42
|
<main><h1>Not found</h1></main>
|
|
35
43
|
)
|
|
36
44
|
}
|
|
45
|
+
<LayoutSlot
|
|
46
|
+
groups={layouts}
|
|
47
|
+
position="footer"
|
|
48
|
+
locale={locale}
|
|
49
|
+
defaultLocale={defaultLocale}
|
|
50
|
+
enabledLocales={enabledLocales}
|
|
51
|
+
/>
|
|
37
52
|
</body>
|
|
38
53
|
</html>
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
import { loadCmssyPage } from "@cmssy/astro";
|
|
3
3
|
import { cmssy } from "../../cmssy.config";
|
|
4
4
|
import CmssyEditor from "../../cmssy/editor";
|
|
5
|
+
import LayoutSlot from "../../cmssy/layout-slot";
|
|
5
6
|
|
|
6
7
|
// The editor iframe lands here (the middleware rewrites verified requests). It
|
|
7
8
|
// must be dynamic: a prerendered page never sees the query string.
|
|
8
9
|
export const prerender = false;
|
|
9
10
|
|
|
10
|
-
const { page, locale, defaultLocale, enabledLocales } = await loadCmssyPage(
|
|
11
|
+
const { page, layouts, locale, defaultLocale, enabledLocales } = await loadCmssyPage(
|
|
11
12
|
cmssy,
|
|
12
13
|
Astro.request,
|
|
13
14
|
Astro.url,
|
|
@@ -21,6 +22,15 @@ const editorOrigin = cmssy.editorOrigin ?? "*";
|
|
|
21
22
|
<title>cmssy editor</title>
|
|
22
23
|
</head>
|
|
23
24
|
<body>
|
|
25
|
+
<LayoutSlot
|
|
26
|
+
client:load
|
|
27
|
+
groups={layouts}
|
|
28
|
+
position="header"
|
|
29
|
+
locale={locale}
|
|
30
|
+
defaultLocale={defaultLocale}
|
|
31
|
+
enabledLocales={enabledLocales}
|
|
32
|
+
edit={{ editorOrigin }}
|
|
33
|
+
/>
|
|
24
34
|
<CmssyEditor
|
|
25
35
|
client:load
|
|
26
36
|
page={page}
|
|
@@ -29,5 +39,14 @@ const editorOrigin = cmssy.editorOrigin ?? "*";
|
|
|
29
39
|
enabledLocales={enabledLocales}
|
|
30
40
|
edit={{ editorOrigin }}
|
|
31
41
|
/>
|
|
42
|
+
<LayoutSlot
|
|
43
|
+
client:load
|
|
44
|
+
groups={layouts}
|
|
45
|
+
position="footer"
|
|
46
|
+
locale={locale}
|
|
47
|
+
defaultLocale={defaultLocale}
|
|
48
|
+
enabledLocales={enabledLocales}
|
|
49
|
+
edit={{ editorOrigin }}
|
|
50
|
+
/>
|
|
32
51
|
</body>
|
|
33
52
|
</html>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CmssyBlock,
|
|
3
|
+
buildBlockContext,
|
|
4
|
+
buildBlockMap,
|
|
5
|
+
type CmssyLayoutGroup,
|
|
6
|
+
} from "@cmssy/react";
|
|
7
|
+
import { CmssyLazyLayout } from "@cmssy/react/client";
|
|
8
|
+
import { blocks } from "./blocks";
|
|
9
|
+
|
|
10
|
+
export interface LayoutSlotProps {
|
|
11
|
+
groups: CmssyLayoutGroup[];
|
|
12
|
+
position: "header" | "footer";
|
|
13
|
+
locale: string;
|
|
14
|
+
defaultLocale: string;
|
|
15
|
+
enabledLocales: string[];
|
|
16
|
+
/** Set on a VERIFIED editor request; the loader reports it as `isEdit`. */
|
|
17
|
+
edit?: { editorOrigin: string | string[] };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The header and footer, which are layout blocks rather than markup you own.
|
|
22
|
+
*
|
|
23
|
+
* Two renders, one per mode, and the difference is the point: server-rendered
|
|
24
|
+
* for visitors, and through the edit bridge for the editor. A header still
|
|
25
|
+
* server-rendered in edit mode is one the editor can select and cannot fill.
|
|
26
|
+
*/
|
|
27
|
+
export function LayoutSlot({
|
|
28
|
+
groups,
|
|
29
|
+
position,
|
|
30
|
+
locale,
|
|
31
|
+
defaultLocale,
|
|
32
|
+
enabledLocales,
|
|
33
|
+
edit,
|
|
34
|
+
}: LayoutSlotProps) {
|
|
35
|
+
if (edit) {
|
|
36
|
+
return (
|
|
37
|
+
<CmssyLazyLayout
|
|
38
|
+
groups={groups}
|
|
39
|
+
position={position}
|
|
40
|
+
locale={locale}
|
|
41
|
+
defaultLocale={defaultLocale}
|
|
42
|
+
enabledLocales={enabledLocales}
|
|
43
|
+
edit={edit}
|
|
44
|
+
load={() => import("./blocks")}
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const group = groups.find((candidate) => candidate.position === position);
|
|
50
|
+
if (!group) return null;
|
|
51
|
+
|
|
52
|
+
const blockMap = buildBlockMap(blocks);
|
|
53
|
+
const context = buildBlockContext(locale, defaultLocale, enabledLocales);
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<>
|
|
57
|
+
{[...group.blocks]
|
|
58
|
+
.filter((block) => block.isActive !== false)
|
|
59
|
+
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
|
|
60
|
+
.map((block) => (
|
|
61
|
+
<CmssyBlock
|
|
62
|
+
key={block.id}
|
|
63
|
+
block={block}
|
|
64
|
+
blockMap={blockMap}
|
|
65
|
+
locale={locale}
|
|
66
|
+
defaultLocale={defaultLocale}
|
|
67
|
+
context={context}
|
|
68
|
+
/>
|
|
69
|
+
))}
|
|
70
|
+
</>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
@@ -3,6 +3,7 @@ import { createCmssyHeaders, createCmssyLoader } from "@cmssy/remix";
|
|
|
3
3
|
import { cmssy } from "../../cmssy.config";
|
|
4
4
|
import { blocks } from "../cmssy/blocks";
|
|
5
5
|
import { CmssyEditor } from "../cmssy/editor";
|
|
6
|
+
import { LayoutSlot } from "../cmssy/layout-slot";
|
|
6
7
|
import type { Route } from "./+types/page";
|
|
7
8
|
|
|
8
9
|
export const loader = createCmssyLoader(cmssy);
|
|
@@ -14,6 +15,7 @@ export const headers = createCmssyHeaders(cmssy);
|
|
|
14
15
|
export default function CmssyPage({ loaderData }: Route.ComponentProps) {
|
|
15
16
|
const {
|
|
16
17
|
page,
|
|
18
|
+
layouts,
|
|
17
19
|
locale,
|
|
18
20
|
defaultLocale,
|
|
19
21
|
enabledLocales,
|
|
@@ -28,40 +30,63 @@ export default function CmssyPage({ loaderData }: Route.ComponentProps) {
|
|
|
28
30
|
|
|
29
31
|
// A verified editor request renders the same page through the edit bridge.
|
|
30
32
|
// No separate route: a React Router page always sees its query string.
|
|
33
|
+
const slot = (position: "header" | "footer") => (
|
|
34
|
+
<LayoutSlot
|
|
35
|
+
groups={layouts}
|
|
36
|
+
position={position}
|
|
37
|
+
locale={locale}
|
|
38
|
+
defaultLocale={defaultLocale}
|
|
39
|
+
enabledLocales={enabledLocales}
|
|
40
|
+
edit={isEdit ? { editorOrigin } : undefined}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
|
|
31
44
|
if (isEdit) {
|
|
32
45
|
return (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
46
|
+
<>
|
|
47
|
+
{slot("header")}
|
|
48
|
+
<CmssyEditor
|
|
49
|
+
page={page}
|
|
50
|
+
locale={locale}
|
|
51
|
+
defaultLocale={defaultLocale}
|
|
52
|
+
enabledLocales={enabledLocales}
|
|
53
|
+
edit={{ editorOrigin }}
|
|
54
|
+
/>
|
|
55
|
+
{slot("footer")}
|
|
56
|
+
</>
|
|
40
57
|
);
|
|
41
58
|
}
|
|
42
59
|
|
|
43
60
|
if (!page)
|
|
44
61
|
return (
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
62
|
+
<>
|
|
63
|
+
{slot("header")}
|
|
64
|
+
<main>
|
|
65
|
+
<h1>Not found</h1>
|
|
66
|
+
</main>
|
|
67
|
+
{slot("footer")}
|
|
68
|
+
</>
|
|
48
69
|
);
|
|
49
70
|
|
|
50
71
|
const blockMap = buildBlockMap(blocks);
|
|
51
72
|
const context = buildBlockContext(locale, defaultLocale, enabledLocales);
|
|
52
73
|
|
|
53
74
|
return (
|
|
54
|
-
|
|
55
|
-
{(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
75
|
+
<>
|
|
76
|
+
{slot("header")}
|
|
77
|
+
<main>
|
|
78
|
+
{(page.blocks ?? []).map((block) => (
|
|
79
|
+
<CmssyBlock
|
|
80
|
+
key={block.id}
|
|
81
|
+
block={block}
|
|
82
|
+
blockMap={blockMap}
|
|
83
|
+
locale={locale}
|
|
84
|
+
defaultLocale={defaultLocale}
|
|
85
|
+
context={context}
|
|
86
|
+
/>
|
|
87
|
+
))}
|
|
88
|
+
</main>
|
|
89
|
+
{slot("footer")}
|
|
90
|
+
</>
|
|
66
91
|
);
|
|
67
92
|
}
|
package/dist/index.js
CHANGED
|
@@ -187,6 +187,7 @@ var FRAMEWORKS = [
|
|
|
187
187
|
"src/middleware.ts",
|
|
188
188
|
"src/cmssy/blocks.ts",
|
|
189
189
|
"src/cmssy/editor.tsx",
|
|
190
|
+
"src/cmssy/layout-slot.tsx",
|
|
190
191
|
"src/cmssy/hero.tsx",
|
|
191
192
|
"src/components/Blocks.tsx",
|
|
192
193
|
"src/pages/[...path].astro",
|
|
@@ -204,6 +205,7 @@ var FRAMEWORKS = [
|
|
|
204
205
|
"app/routes.ts",
|
|
205
206
|
"app/cmssy/blocks.ts",
|
|
206
207
|
"app/cmssy/editor.tsx",
|
|
208
|
+
"app/cmssy/layout-slot.tsx",
|
|
207
209
|
"app/cmssy/hero.tsx",
|
|
208
210
|
"app/routes/page.tsx"
|
|
209
211
|
]
|
|
@@ -1244,6 +1246,22 @@ async function request(deps, endpoint, query, variables) {
|
|
|
1244
1246
|
if (!body.data) throw new CliError("the delivery API returned no data");
|
|
1245
1247
|
return body.data;
|
|
1246
1248
|
}
|
|
1249
|
+
function describeDrift(previous, next) {
|
|
1250
|
+
const declared = (source, pattern) => new Set([...source.matchAll(pattern)].map((match) => match[1] ?? ""));
|
|
1251
|
+
const body = (source) => source.split("/** Every model in the")[0] ?? source;
|
|
1252
|
+
const models = /export interface (\w+)Data/g;
|
|
1253
|
+
const fields = /^\s{2}(\w+)\??:/gm;
|
|
1254
|
+
const lines = [];
|
|
1255
|
+
const report = (label, before, after) => {
|
|
1256
|
+
const added = [...after].filter((name) => !before.has(name));
|
|
1257
|
+
const removed = [...before].filter((name) => !after.has(name));
|
|
1258
|
+
if (added.length) lines.push(`+ ${label}: ${added.join(", ")}`);
|
|
1259
|
+
if (removed.length) lines.push(`- ${label}: ${removed.join(", ")}`);
|
|
1260
|
+
};
|
|
1261
|
+
report("models", declared(previous, models), declared(next, models));
|
|
1262
|
+
report("fields", declared(body(previous), fields), declared(body(next), fields));
|
|
1263
|
+
return lines.length ? lines : ["the generated output differs"];
|
|
1264
|
+
}
|
|
1247
1265
|
function resolve3(value, fromEnv, variable) {
|
|
1248
1266
|
const resolved = value?.trim() || fromEnv?.trim();
|
|
1249
1267
|
if (!resolved) {
|
|
@@ -1295,6 +1313,16 @@ async function runTypes(options, deps) {
|
|
|
1295
1313
|
deps.log(`cmssy: ${shown} is up to date`);
|
|
1296
1314
|
return 0;
|
|
1297
1315
|
}
|
|
1316
|
+
if (options.check) {
|
|
1317
|
+
deps.log(
|
|
1318
|
+
previous === null ? `cmssy: ${shown} is missing - run \`cmssy types\` and commit it` : `cmssy: ${shown} is out of date with the "${workspace}" workspace`
|
|
1319
|
+
);
|
|
1320
|
+
if (previous !== null) {
|
|
1321
|
+
for (const line of describeDrift(previous, source)) deps.log(` ${line}`);
|
|
1322
|
+
}
|
|
1323
|
+
deps.log(" run `cmssy types` and commit the result");
|
|
1324
|
+
return 1;
|
|
1325
|
+
}
|
|
1298
1326
|
mkdirSync3(dirname3(outPath), { recursive: true });
|
|
1299
1327
|
writeFileSync4(outPath, source);
|
|
1300
1328
|
const fieldCount = models.reduce(
|
|
@@ -1324,7 +1352,7 @@ var USAGE = [
|
|
|
1324
1352
|
" cmssy init [--dir <path>] [--force]",
|
|
1325
1353
|
" cmssy add block <name> [--dir <path>]",
|
|
1326
1354
|
" cmssy link [--token <cs_...>] [--workspace <slug>] [--preview-url <url>]",
|
|
1327
|
-
" cmssy types [--out <path>] [--org <slug>] [--workspace <slug>]"
|
|
1355
|
+
" cmssy types [--out <path>] [--check] [--org <slug>] [--workspace <slug>]"
|
|
1328
1356
|
].join("\n");
|
|
1329
1357
|
function flagValue(args, name) {
|
|
1330
1358
|
const index = args.findIndex((arg) => arg === name);
|
|
@@ -1387,7 +1415,8 @@ async function main() {
|
|
|
1387
1415
|
{
|
|
1388
1416
|
out: flagValue(args, "--out"),
|
|
1389
1417
|
org: flagValue(args, "--org"),
|
|
1390
|
-
workspace: flagValue(args, "--workspace")
|
|
1418
|
+
workspace: flagValue(args, "--workspace"),
|
|
1419
|
+
check: hasFlag(args, "--check")
|
|
1391
1420
|
},
|
|
1392
1421
|
{
|
|
1393
1422
|
cwd: process.cwd(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cmssy/cli",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.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": "10.
|
|
30
|
+
"@cmssy/core": "10.8.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^20.0.0",
|