@cmssy/cli 0.22.4 → 1.0.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.
Files changed (260) hide show
  1. package/README.md +49 -650
  2. package/dist/cli.js +706 -328
  3. package/package.json +26 -73
  4. package/templates/block/Component.module.css.tpl +12 -0
  5. package/templates/block/Component.tsx.tpl +15 -0
  6. package/templates/block/block.ts.tpl +11 -0
  7. package/templates/init/app/[[...path]]/page.tsx +19 -0
  8. package/templates/init/app/api/draft/route.ts +5 -0
  9. package/templates/init/blocks/hero/Hero.module.css +131 -0
  10. package/templates/init/blocks/hero/Hero.tsx +100 -0
  11. package/templates/init/blocks/hero/block.ts +21 -0
  12. package/templates/init/cmssy/blocks.ts +5 -0
  13. package/templates/init/cmssy/editor.tsx +10 -0
  14. package/templates/init/cmssy.config.ts +8 -0
  15. package/templates/init/env.example +4 -0
  16. package/templates/init/next.config.mjs +8 -0
  17. package/templates/init/proxy.ts +32 -0
  18. package/config.d.ts +0 -3
  19. package/config.js +0 -4
  20. package/dist/cli.d.ts +0 -3
  21. package/dist/cli.d.ts.map +0 -1
  22. package/dist/cli.js.map +0 -1
  23. package/dist/commands/add-source.d.ts +0 -13
  24. package/dist/commands/add-source.d.ts.map +0 -1
  25. package/dist/commands/add-source.js +0 -273
  26. package/dist/commands/add-source.js.map +0 -1
  27. package/dist/commands/build.d.ts +0 -7
  28. package/dist/commands/build.d.ts.map +0 -1
  29. package/dist/commands/build.js +0 -127
  30. package/dist/commands/build.js.map +0 -1
  31. package/dist/commands/codegen.d.ts +0 -7
  32. package/dist/commands/codegen.d.ts.map +0 -1
  33. package/dist/commands/codegen.js +0 -150
  34. package/dist/commands/codegen.js.map +0 -1
  35. package/dist/commands/configure.d.ts +0 -6
  36. package/dist/commands/configure.d.ts.map +0 -1
  37. package/dist/commands/configure.js +0 -39
  38. package/dist/commands/configure.js.map +0 -1
  39. package/dist/commands/create.d.ts +0 -18
  40. package/dist/commands/create.d.ts.map +0 -1
  41. package/dist/commands/create.js +0 -403
  42. package/dist/commands/create.js.map +0 -1
  43. package/dist/commands/dev.d.ts +0 -6
  44. package/dist/commands/dev.d.ts.map +0 -1
  45. package/dist/commands/dev.js +0 -144
  46. package/dist/commands/dev.js.map +0 -1
  47. package/dist/commands/doctor.d.ts +0 -2
  48. package/dist/commands/doctor.d.ts.map +0 -1
  49. package/dist/commands/doctor.js +0 -230
  50. package/dist/commands/doctor.js.map +0 -1
  51. package/dist/commands/init.d.ts +0 -6
  52. package/dist/commands/init.d.ts.map +0 -1
  53. package/dist/commands/init.js +0 -414
  54. package/dist/commands/init.js.map +0 -1
  55. package/dist/commands/lib.d.ts +0 -17
  56. package/dist/commands/lib.d.ts.map +0 -1
  57. package/dist/commands/lib.js +0 -132
  58. package/dist/commands/lib.js.map +0 -1
  59. package/dist/commands/link.d.ts +0 -8
  60. package/dist/commands/link.d.ts.map +0 -1
  61. package/dist/commands/link.js +0 -116
  62. package/dist/commands/link.js.map +0 -1
  63. package/dist/commands/migrate.d.ts +0 -2
  64. package/dist/commands/migrate.d.ts.map +0 -1
  65. package/dist/commands/migrate.js +0 -231
  66. package/dist/commands/migrate.js.map +0 -1
  67. package/dist/commands/package.d.ts +0 -14
  68. package/dist/commands/package.d.ts.map +0 -1
  69. package/dist/commands/package.js +0 -137
  70. package/dist/commands/package.js.map +0 -1
  71. package/dist/commands/publish-block-buildtime.d.ts +0 -8
  72. package/dist/commands/publish-block-buildtime.d.ts.map +0 -1
  73. package/dist/commands/publish-block-buildtime.js +0 -290
  74. package/dist/commands/publish-block-buildtime.js.map +0 -1
  75. package/dist/commands/publish-template.d.ts +0 -22
  76. package/dist/commands/publish-template.d.ts.map +0 -1
  77. package/dist/commands/publish-template.js +0 -345
  78. package/dist/commands/publish-template.js.map +0 -1
  79. package/dist/commands/publish.d.ts +0 -15
  80. package/dist/commands/publish.d.ts.map +0 -1
  81. package/dist/commands/publish.js +0 -1003
  82. package/dist/commands/publish.js.map +0 -1
  83. package/dist/commands/skills.d.ts +0 -11
  84. package/dist/commands/skills.d.ts.map +0 -1
  85. package/dist/commands/skills.js +0 -178
  86. package/dist/commands/skills.js.map +0 -1
  87. package/dist/commands/sync.d.ts +0 -6
  88. package/dist/commands/sync.d.ts.map +0 -1
  89. package/dist/commands/sync.js +0 -208
  90. package/dist/commands/sync.js.map +0 -1
  91. package/dist/commands/test.d.ts +0 -8
  92. package/dist/commands/test.d.ts.map +0 -1
  93. package/dist/commands/test.js +0 -60
  94. package/dist/commands/test.js.map +0 -1
  95. package/dist/commands/upload.d.ts +0 -11
  96. package/dist/commands/upload.d.ts.map +0 -1
  97. package/dist/commands/upload.js +0 -128
  98. package/dist/commands/upload.js.map +0 -1
  99. package/dist/commands/workspaces.d.ts +0 -2
  100. package/dist/commands/workspaces.d.ts.map +0 -1
  101. package/dist/commands/workspaces.js +0 -62
  102. package/dist/commands/workspaces.js.map +0 -1
  103. package/dist/dev-ui/app.js +0 -1284
  104. package/dist/dev-ui/index.html +0 -1511
  105. package/dist/dev-ui-react/App.tsx +0 -164
  106. package/dist/dev-ui-react/__tests__/previewData.test.ts +0 -193
  107. package/dist/dev-ui-react/components/BlocksList.tsx +0 -232
  108. package/dist/dev-ui-react/components/Editor.tsx +0 -469
  109. package/dist/dev-ui-react/components/Preview.tsx +0 -146
  110. package/dist/dev-ui-react/hooks/useBlocks.ts +0 -80
  111. package/dist/dev-ui-react/index.html +0 -13
  112. package/dist/dev-ui-react/main.tsx +0 -8
  113. package/dist/dev-ui-react/styles.css +0 -856
  114. package/dist/dev-ui-react/types.ts +0 -45
  115. package/dist/skills/claude/cmssy-block.md +0 -454
  116. package/dist/skills/claude/cmssy-mcp-content.md +0 -235
  117. package/dist/test-helpers/index.d.ts +0 -23
  118. package/dist/test-helpers/index.d.ts.map +0 -1
  119. package/dist/test-helpers/index.js +0 -50
  120. package/dist/test-helpers/index.js.map +0 -1
  121. package/dist/types/block-config.d.ts +0 -6
  122. package/dist/types/block-config.d.ts.map +0 -1
  123. package/dist/types/block-config.js +0 -12
  124. package/dist/types/block-config.js.map +0 -1
  125. package/dist/types/publish-job.d.ts +0 -61
  126. package/dist/types/publish-job.d.ts.map +0 -1
  127. package/dist/types/publish-job.js +0 -13
  128. package/dist/types/publish-job.js.map +0 -1
  129. package/dist/utils/api-error.d.ts +0 -10
  130. package/dist/utils/api-error.d.ts.map +0 -1
  131. package/dist/utils/api-error.js +0 -55
  132. package/dist/utils/api-error.js.map +0 -1
  133. package/dist/utils/block-config.d.ts +0 -87
  134. package/dist/utils/block-config.d.ts.map +0 -1
  135. package/dist/utils/block-config.js +0 -214
  136. package/dist/utils/block-config.js.map +0 -1
  137. package/dist/utils/blocks-meta-cache.d.ts +0 -28
  138. package/dist/utils/blocks-meta-cache.d.ts.map +0 -1
  139. package/dist/utils/blocks-meta-cache.js +0 -72
  140. package/dist/utils/blocks-meta-cache.js.map +0 -1
  141. package/dist/utils/builder.d.ts +0 -34
  142. package/dist/utils/builder.d.ts.map +0 -1
  143. package/dist/utils/builder.js +0 -140
  144. package/dist/utils/builder.js.map +0 -1
  145. package/dist/utils/cmssy-config.d.ts +0 -16
  146. package/dist/utils/cmssy-config.d.ts.map +0 -1
  147. package/dist/utils/cmssy-config.js +0 -19
  148. package/dist/utils/cmssy-config.js.map +0 -1
  149. package/dist/utils/config.d.ts +0 -9
  150. package/dist/utils/config.d.ts.map +0 -1
  151. package/dist/utils/config.js +0 -51
  152. package/dist/utils/config.js.map +0 -1
  153. package/dist/utils/dependency-check.d.ts +0 -21
  154. package/dist/utils/dependency-check.d.ts.map +0 -1
  155. package/dist/utils/dependency-check.js +0 -56
  156. package/dist/utils/dependency-check.js.map +0 -1
  157. package/dist/utils/dev-generator/api-routes/blocks.d.ts +0 -3
  158. package/dist/utils/dev-generator/api-routes/blocks.d.ts.map +0 -1
  159. package/dist/utils/dev-generator/api-routes/blocks.js +0 -194
  160. package/dist/utils/dev-generator/api-routes/blocks.js.map +0 -1
  161. package/dist/utils/dev-generator/api-routes/config.d.ts +0 -2
  162. package/dist/utils/dev-generator/api-routes/config.d.ts.map +0 -1
  163. package/dist/utils/dev-generator/api-routes/config.js +0 -107
  164. package/dist/utils/dev-generator/api-routes/config.js.map +0 -1
  165. package/dist/utils/dev-generator/api-routes/preview.d.ts +0 -2
  166. package/dist/utils/dev-generator/api-routes/preview.d.ts.map +0 -1
  167. package/dist/utils/dev-generator/api-routes/preview.js +0 -50
  168. package/dist/utils/dev-generator/api-routes/preview.js.map +0 -1
  169. package/dist/utils/dev-generator/api-routes/workspaces.d.ts +0 -2
  170. package/dist/utils/dev-generator/api-routes/workspaces.d.ts.map +0 -1
  171. package/dist/utils/dev-generator/api-routes/workspaces.js +0 -128
  172. package/dist/utils/dev-generator/api-routes/workspaces.js.map +0 -1
  173. package/dist/utils/dev-generator/helpers.d.ts +0 -23
  174. package/dist/utils/dev-generator/helpers.d.ts.map +0 -1
  175. package/dist/utils/dev-generator/helpers.js +0 -128
  176. package/dist/utils/dev-generator/helpers.js.map +0 -1
  177. package/dist/utils/dev-generator/home-page.d.ts +0 -2
  178. package/dist/utils/dev-generator/home-page.d.ts.map +0 -1
  179. package/dist/utils/dev-generator/home-page.js +0 -643
  180. package/dist/utils/dev-generator/home-page.js.map +0 -1
  181. package/dist/utils/dev-generator/index.d.ts +0 -11
  182. package/dist/utils/dev-generator/index.d.ts.map +0 -1
  183. package/dist/utils/dev-generator/index.js +0 -85
  184. package/dist/utils/dev-generator/index.js.map +0 -1
  185. package/dist/utils/dev-generator/layout.d.ts +0 -3
  186. package/dist/utils/dev-generator/layout.d.ts.map +0 -1
  187. package/dist/utils/dev-generator/layout.js +0 -63
  188. package/dist/utils/dev-generator/layout.js.map +0 -1
  189. package/dist/utils/dev-generator/next-config.d.ts +0 -3
  190. package/dist/utils/dev-generator/next-config.d.ts.map +0 -1
  191. package/dist/utils/dev-generator/next-config.js +0 -111
  192. package/dist/utils/dev-generator/next-config.js.map +0 -1
  193. package/dist/utils/dev-generator/preview-pages.d.ts +0 -3
  194. package/dist/utils/dev-generator/preview-pages.d.ts.map +0 -1
  195. package/dist/utils/dev-generator/preview-pages.js +0 -833
  196. package/dist/utils/dev-generator/preview-pages.js.map +0 -1
  197. package/dist/utils/dev-generator.d.ts +0 -12
  198. package/dist/utils/dev-generator.d.ts.map +0 -1
  199. package/dist/utils/dev-generator.js +0 -1752
  200. package/dist/utils/dev-generator.js.map +0 -1
  201. package/dist/utils/field-schema.d.ts +0 -13
  202. package/dist/utils/field-schema.d.ts.map +0 -1
  203. package/dist/utils/field-schema.js +0 -182
  204. package/dist/utils/field-schema.js.map +0 -1
  205. package/dist/utils/graphql.d.ts +0 -39
  206. package/dist/utils/graphql.d.ts.map +0 -1
  207. package/dist/utils/graphql.js +0 -138
  208. package/dist/utils/graphql.js.map +0 -1
  209. package/dist/utils/lib-manifest.d.ts +0 -7
  210. package/dist/utils/lib-manifest.d.ts.map +0 -1
  211. package/dist/utils/lib-manifest.js +0 -59
  212. package/dist/utils/lib-manifest.js.map +0 -1
  213. package/dist/utils/publish-helpers.d.ts +0 -43
  214. package/dist/utils/publish-helpers.d.ts.map +0 -1
  215. package/dist/utils/publish-helpers.js +0 -215
  216. package/dist/utils/publish-helpers.js.map +0 -1
  217. package/dist/utils/publish-helpers.test.d.ts +0 -2
  218. package/dist/utils/publish-helpers.test.d.ts.map +0 -1
  219. package/dist/utils/publish-helpers.test.js +0 -24
  220. package/dist/utils/publish-helpers.test.js.map +0 -1
  221. package/dist/utils/resolve-workspace.d.ts +0 -6
  222. package/dist/utils/resolve-workspace.d.ts.map +0 -1
  223. package/dist/utils/resolve-workspace.js +0 -32
  224. package/dist/utils/resolve-workspace.js.map +0 -1
  225. package/dist/utils/scanner.d.ts +0 -42
  226. package/dist/utils/scanner.d.ts.map +0 -1
  227. package/dist/utils/scanner.js +0 -156
  228. package/dist/utils/scanner.js.map +0 -1
  229. package/dist/utils/schema-diff.d.ts +0 -22
  230. package/dist/utils/schema-diff.d.ts.map +0 -1
  231. package/dist/utils/schema-diff.js +0 -107
  232. package/dist/utils/schema-diff.js.map +0 -1
  233. package/dist/utils/source-collector.d.ts +0 -19
  234. package/dist/utils/source-collector.d.ts.map +0 -1
  235. package/dist/utils/source-collector.js +0 -565
  236. package/dist/utils/source-collector.js.map +0 -1
  237. package/dist/utils/theme-builder.d.ts +0 -12
  238. package/dist/utils/theme-builder.d.ts.map +0 -1
  239. package/dist/utils/theme-builder.js +0 -117
  240. package/dist/utils/theme-builder.js.map +0 -1
  241. package/dist/utils/type-generator.d.ts +0 -9
  242. package/dist/utils/type-generator.d.ts.map +0 -1
  243. package/dist/utils/type-generator.js +0 -86
  244. package/dist/utils/type-generator.js.map +0 -1
  245. package/dist/utils/version.d.ts +0 -10
  246. package/dist/utils/version.d.ts.map +0 -1
  247. package/dist/utils/version.js +0 -30
  248. package/dist/utils/version.js.map +0 -1
  249. package/dist/utils/versions.d.ts.map +0 -1
  250. package/dist/utils/versions.js.map +0 -1
  251. package/src/dev-app/app/api/blocks/[name]/config/route.ts +0 -140
  252. package/src/dev-app/app/api/blocks/route.ts +0 -61
  253. package/src/dev-app/app/api/config/route.ts +0 -98
  254. package/src/dev-app/app/api/context/route.ts +0 -126
  255. package/src/dev-app/app/api/preview/[name]/route.ts +0 -137
  256. package/src/dev-app/app/api/workspaces/route.ts +0 -124
  257. package/src/dev-app/app/layout.tsx +0 -18
  258. package/src/dev-app/app/page.tsx +0 -1785
  259. package/test.d.ts +0 -1
  260. package/test.js +0 -2
@@ -1,833 +0,0 @@
1
- import fs from "fs-extra";
2
- import path from "path";
3
- import { convertBlockTypeToSimple, convertConfigToPagesData, loadTemplateConfigSync, toPascalCase, } from "./helpers.js";
4
- export function generatePreviewPages(devRoot, projectRoot, resources) {
5
- for (const resource of resources) {
6
- if (resource.type === "template") {
7
- generateTemplatePreviewPage(devRoot, projectRoot, resource);
8
- }
9
- else {
10
- generateBlockPreviewPage(devRoot, projectRoot, resource);
11
- }
12
- }
13
- }
14
- function generateBlockPreviewPage(devRoot, projectRoot, resource) {
15
- const pageDir = path.join(devRoot, "app/preview", resource.name);
16
- fs.mkdirSync(pageDir, { recursive: true });
17
- const blockSrcDir = path.join(projectRoot, "blocks", resource.name, "src");
18
- const hasIndex = fs.existsSync(path.join(blockSrcDir, "index.tsx")) ||
19
- fs.existsSync(path.join(blockSrcDir, "index.ts"));
20
- const hasCss = fs.existsSync(path.join(blockSrcDir, "index.css"));
21
- if (!hasIndex)
22
- return;
23
- const finalBlockImport = `@blocks/${resource.name}/src/index`;
24
- const finalCssImport = `@blocks/${resource.name}/src/index.css`;
25
- const pageContent = `"use client";
26
-
27
- import { useState, useEffect } from "react";
28
- import BlockComponent from "${finalBlockImport}";
29
- ${hasCss ? `import "${finalCssImport}";` : ""}
30
-
31
- export default function ${toPascalCase(resource.name)}Preview() {
32
- const [data, setData] = useState<Record<string, unknown>>({});
33
- const [context, setContext] = useState<Record<string, unknown>>({});
34
-
35
- useEffect(() => {
36
- fetch("/api/preview/${resource.name}")
37
- .then((r) => r.json())
38
- .then((res) => setData(res.data || res))
39
- .catch(console.error);
40
- fetch("/api/context")
41
- .then((r) => r.json())
42
- .then(setContext)
43
- .catch(() => {});
44
- }, []);
45
-
46
- useEffect(() => {
47
- function handleMessage(e: MessageEvent) {
48
- if (e.data?.type === "UPDATE_PROPS") {
49
- setData(e.data.props);
50
- if (e.data.context) setContext(e.data.context);
51
- }
52
- }
53
- window.addEventListener("message", handleMessage);
54
- return () => window.removeEventListener("message", handleMessage);
55
- }, []);
56
-
57
- return (
58
- <div>
59
- <div style={{
60
- position: "fixed", top: 0, left: 0, right: 0,
61
- background: "white", borderBottom: "1px solid #e0e0e0",
62
- padding: "1rem 2rem", zIndex: 1000,
63
- display: "flex", justifyContent: "space-between", alignItems: "center",
64
- }}>
65
- <div style={{ fontSize: "1.25rem", fontWeight: 600 }}>${resource.displayName || resource.name}</div>
66
- <a href="/" style={{ color: "#667eea", textDecoration: "none", fontWeight: 500 }} target="_parent">
67
- &larr; Back to Home
68
- </a>
69
- </div>
70
- <div style={{ marginTop: "60px", minHeight: "calc(100vh - 60px)" }}>
71
- <BlockComponent content={data} context={context} />
72
- </div>
73
- </div>
74
- );
75
- }
76
- `;
77
- fs.writeFileSync(path.join(pageDir, "page.tsx"), pageContent);
78
- }
79
- function generateTemplatePreviewPage(devRoot, projectRoot, resource) {
80
- // Read pages data from pages.json or fall back to config.ts
81
- const templateDir = path.join(projectRoot, "templates", resource.name);
82
- const pagesJsonPath = path.join(templateDir, "pages.json");
83
- let pagesData;
84
- if (fs.existsSync(pagesJsonPath)) {
85
- pagesData = JSON.parse(fs.readFileSync(pagesJsonPath, "utf-8"));
86
- }
87
- else {
88
- // No pages.json — load from config.ts and convert format
89
- const config = loadTemplateConfigSync(templateDir, projectRoot);
90
- if (!config || (!config.pages && !config.layoutPositions))
91
- return;
92
- pagesData = convertConfigToPagesData(config);
93
- }
94
- const pages = pagesData.pages || [];
95
- const layoutPositions = pagesData.layoutPositions || {};
96
- // Collect all unique block types across all pages + layoutPositions
97
- const blockTypesSet = new Set();
98
- for (const page of pages) {
99
- for (const block of page.blocks || []) {
100
- blockTypesSet.add(convertBlockTypeToSimple(block.type));
101
- }
102
- }
103
- for (const data of Object.values(layoutPositions)) {
104
- blockTypesSet.add(convertBlockTypeToSimple(data.type));
105
- }
106
- // Check which blocks exist in blocks/ dir with src/index.tsx
107
- const blockImports = [];
108
- const cssImports = [];
109
- const componentMapEntries = [];
110
- for (const blockName of Array.from(blockTypesSet)) {
111
- const blockSrcDir = path.join(projectRoot, "blocks", blockName, "src");
112
- const hasIndex = fs.existsSync(path.join(blockSrcDir, "index.tsx")) ||
113
- fs.existsSync(path.join(blockSrcDir, "index.ts"));
114
- const hasCss = fs.existsSync(path.join(blockSrcDir, "index.css"));
115
- if (!hasIndex)
116
- continue;
117
- const pascalName = toPascalCase(blockName);
118
- blockImports.push(`import ${pascalName} from "@blocks/${blockName}/src/index";`);
119
- if (hasCss) {
120
- cssImports.push(`import "@blocks/${blockName}/src/index.css";`);
121
- }
122
- componentMapEntries.push(` "${blockName}": ${pascalName},`);
123
- }
124
- // Generate [[...slug]] catch-all route
125
- const pageDir = path.join(devRoot, "app/preview", resource.name, "[[...slug]]");
126
- fs.mkdirSync(pageDir, { recursive: true });
127
- const pageContent = `"use client";
128
-
129
- import { useState, useEffect, useRef } from "react";
130
- import { useParams, useRouter } from "next/navigation";
131
- ${blockImports.join("\n")}
132
- ${cssImports.join("\n")}
133
-
134
- const BLOCK_COMPONENTS: Record<string, React.ComponentType<any>> = {
135
- ${componentMapEntries.join("\n")}
136
- };
137
-
138
- interface BlockData {
139
- type: string;
140
- content: Record<string, any>;
141
- }
142
-
143
- interface PageData {
144
- name: string;
145
- slug: string;
146
- blocks: BlockData[];
147
- }
148
-
149
- interface PagesJson {
150
- layoutPositions?: Record<string, BlockData>;
151
- pages: PageData[];
152
- }
153
-
154
- function convertBlockType(type: string): string {
155
- let simple = type;
156
- if (simple.includes("/")) simple = simple.split("/").pop()!;
157
- if (simple.startsWith("blocks.")) simple = simple.substring(7);
158
- else if (simple.startsWith("templates.")) simple = simple.substring(10);
159
- return simple;
160
- }
161
-
162
- export default function ${toPascalCase(resource.name)}TemplatePreview() {
163
- const params = useParams();
164
- const router = useRouter();
165
- const slugParts = (params.slug as string[]) || [];
166
- const currentSlug = slugParts.length > 0 ? "/" + slugParts.join("/") : "/";
167
- const tabsRef = useRef<HTMLDivElement>(null);
168
- const activeTabRef = useRef<HTMLButtonElement>(null);
169
-
170
- const [pagesData, setPagesData] = useState<PagesJson | null>(null);
171
- const [contentOverrides, setContentOverrides] = useState<Record<string, BlockData[]>>({});
172
- const [navVisible, setNavVisible] = useState(true);
173
- const [paletteOpen, setPaletteOpen] = useState(false);
174
- const [paletteQuery, setPaletteQuery] = useState("");
175
- const [navCollapsed, setNavCollapsed] = useState(false);
176
- const paletteInputRef = useRef<HTMLInputElement>(null);
177
-
178
- // Fetch pages data from config API
179
- useEffect(() => {
180
- fetch("/api/blocks/${resource.name}/config")
181
- .then((r) => r.json())
182
- .then((config) => {
183
- if (config.pagesData) {
184
- setPagesData(config.pagesData);
185
- }
186
- })
187
- .catch(console.error);
188
- }, []);
189
-
190
- // Listen for live content updates from editor
191
- useEffect(() => {
192
- function handleMessage(e: MessageEvent) {
193
- if (e.data?.type === "UPDATE_TEMPLATE_CONTENT") {
194
- setContentOverrides((prev) => ({
195
- ...prev,
196
- [e.data.pageSlug]: e.data.blocks,
197
- }));
198
- }
199
- }
200
- window.addEventListener("message", handleMessage);
201
- return () => window.removeEventListener("message", handleMessage);
202
- }, []);
203
-
204
- // Scroll active tab into view
205
- useEffect(() => {
206
- if (activeTabRef.current && tabsRef.current) {
207
- activeTabRef.current.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "center" });
208
- }
209
- }, [currentSlug, pagesData]);
210
-
211
- // Ctrl+K / Cmd+K to open palette
212
- useEffect(() => {
213
- function onKeyDown(e: KeyboardEvent) {
214
- if ((e.metaKey || e.ctrlKey) && e.key === "k") {
215
- e.preventDefault();
216
- setPaletteOpen((v) => !v);
217
- setPaletteQuery("");
218
- }
219
- if (e.key === "Escape") {
220
- setPaletteOpen(false);
221
- }
222
- }
223
- window.addEventListener("keydown", onKeyDown);
224
- return () => window.removeEventListener("keydown", onKeyDown);
225
- }, []);
226
-
227
- // Focus input when palette opens
228
- useEffect(() => {
229
- if (paletteOpen && paletteInputRef.current) {
230
- paletteInputRef.current.focus();
231
- }
232
- }, [paletteOpen]);
233
-
234
- // Auto-hide nav on scroll down, show on scroll up
235
- useEffect(() => {
236
- let lastY = 0;
237
- function onScroll() {
238
- const y = window.scrollY;
239
- if (y > 80 && y > lastY) setNavVisible(false);
240
- else setNavVisible(true);
241
- lastY = y;
242
- }
243
- window.addEventListener("scroll", onScroll, { passive: true });
244
- return () => window.removeEventListener("scroll", onScroll);
245
- }, []);
246
-
247
- if (!pagesData) {
248
- return (
249
- <div style={{
250
- height: "100vh",
251
- display: "flex",
252
- alignItems: "center",
253
- justifyContent: "center",
254
- background: "#0a0a0a",
255
- }}>
256
- <div style={{ textAlign: "center" }}>
257
- <div style={{
258
- width: "24px",
259
- height: "24px",
260
- border: "2px solid rgba(255,255,255,0.1)",
261
- borderTopColor: "#667eea",
262
- borderRadius: "50%",
263
- animation: "spin 0.8s linear infinite",
264
- margin: "0 auto 16px",
265
- }} />
266
- <div style={{ color: "rgba(255,255,255,0.4)", fontSize: "13px", letterSpacing: "0.05em" }}>
267
- Loading template...
268
- </div>
269
- </div>
270
- <style>{\`@keyframes spin { to { transform: rotate(360deg) } }\`}</style>
271
- </div>
272
- );
273
- }
274
-
275
- // Find current page
276
- const currentPage = pagesData.pages.find((p) => p.slug === currentSlug);
277
- if (!currentPage) {
278
- return (
279
- <div style={{
280
- height: "100vh",
281
- display: "flex",
282
- alignItems: "center",
283
- justifyContent: "center",
284
- background: "#fafafa",
285
- }}>
286
- <div style={{ textAlign: "center" }}>
287
- <div style={{ fontSize: "48px", marginBottom: "12px", opacity: 0.3 }}>404</div>
288
- <div style={{ color: "#666", fontSize: "14px" }}>Page not found: {currentSlug}</div>
289
- <button
290
- onClick={() => router.push("/preview/${resource.name}")}
291
- style={{
292
- marginTop: "20px",
293
- padding: "8px 20px",
294
- background: "#667eea",
295
- color: "#fff",
296
- border: "none",
297
- borderRadius: "6px",
298
- fontSize: "13px",
299
- cursor: "pointer",
300
- }}
301
- >Go to homepage</button>
302
- </div>
303
- </div>
304
- );
305
- }
306
-
307
- // Use overrides if available, otherwise use pages.json content
308
- const pageBlocks = contentOverrides[currentSlug] || currentPage.blocks || [];
309
-
310
- // Render header layout position
311
- const headerData = pagesData.layoutPositions?.header;
312
- const headerType = headerData ? convertBlockType(headerData.type) : null;
313
- const HeaderComponent = headerType ? BLOCK_COMPONENTS[headerType] : null;
314
-
315
- // Render footer layout position
316
- const footerData = pagesData.layoutPositions?.footer;
317
- const footerType = footerData ? convertBlockType(footerData.type) : null;
318
- const FooterComponent = footerType ? BLOCK_COMPONENTS[footerType] : null;
319
-
320
- // Render sidebar_left layout position
321
- const sidebarLeftData = pagesData.layoutPositions?.sidebar_left;
322
- const sidebarLeftType = sidebarLeftData ? convertBlockType(sidebarLeftData.type) : null;
323
- const SidebarLeftComponent = sidebarLeftType ? BLOCK_COMPONENTS[sidebarLeftType] : null;
324
-
325
- const navigateTo = (slug: string) => {
326
- const path = slug === "/" ? "/preview/${resource.name}" : \`/preview/${resource.name}\${slug}\`;
327
- router.push(path);
328
- };
329
-
330
- return (
331
- <>
332
- <style>{\`
333
- @import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600&display=swap');
334
-
335
- .cmssy-nav {
336
- position: fixed;
337
- top: 0;
338
- left: 0;
339
- right: 0;
340
- z-index: 9999;
341
- transform: translateY(\${navVisible ? "0" : "-100%"});
342
- transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
343
- }
344
- .cmssy-nav-inner {
345
- margin: 10px;
346
- background: rgba(10, 10, 10, 0.92);
347
- backdrop-filter: blur(20px) saturate(180%);
348
- -webkit-backdrop-filter: blur(20px) saturate(180%);
349
- border-radius: 14px;
350
- border: 1px solid rgba(255, 255, 255, 0.08);
351
- box-shadow:
352
- 0 8px 32px rgba(0, 0, 0, 0.4),
353
- 0 0 0 1px rgba(255, 255, 255, 0.05) inset;
354
- padding: 8px 12px;
355
- display: flex;
356
- align-items: center;
357
- gap: 8px;
358
- font-family: 'DM Sans', -apple-system, sans-serif;
359
- }
360
- .cmssy-back-btn {
361
- display: flex;
362
- align-items: center;
363
- gap: 6px;
364
- padding: 6px 12px;
365
- background: rgba(255, 255, 255, 0.06);
366
- border: 1px solid rgba(255, 255, 255, 0.08);
367
- border-radius: 8px;
368
- color: rgba(255, 255, 255, 0.7);
369
- font-size: 12px;
370
- font-weight: 500;
371
- cursor: pointer;
372
- text-decoration: none;
373
- white-space: nowrap;
374
- transition: all 0.15s ease;
375
- font-family: inherit;
376
- letter-spacing: 0.01em;
377
- }
378
- .cmssy-back-btn:hover {
379
- background: rgba(255, 255, 255, 0.12);
380
- color: #fff;
381
- }
382
- .cmssy-divider {
383
- width: 1px;
384
- height: 24px;
385
- background: rgba(255, 255, 255, 0.1);
386
- flex-shrink: 0;
387
- }
388
- .cmssy-template-name {
389
- font-size: 13px;
390
- font-weight: 600;
391
- color: #fff;
392
- white-space: nowrap;
393
- letter-spacing: -0.01em;
394
- flex-shrink: 0;
395
- }
396
- .cmssy-tabs-wrapper {
397
- flex: 1;
398
- overflow-x: auto;
399
- overflow-y: hidden;
400
- scrollbar-width: none;
401
- -ms-overflow-style: none;
402
- mask-image: linear-gradient(to right, transparent, black 20px, black calc(100% - 20px), transparent);
403
- -webkit-mask-image: linear-gradient(to right, transparent, black 20px, black calc(100% - 20px), transparent);
404
- }
405
- .cmssy-tabs-wrapper::-webkit-scrollbar {
406
- display: none;
407
- }
408
- .cmssy-tabs {
409
- display: flex;
410
- gap: 2px;
411
- padding: 0 8px;
412
- }
413
- .cmssy-tab {
414
- padding: 5px 12px;
415
- font-size: 12px;
416
- font-weight: 500;
417
- border: none;
418
- border-radius: 7px;
419
- cursor: pointer;
420
- white-space: nowrap;
421
- transition: all 0.15s ease;
422
- font-family: inherit;
423
- letter-spacing: 0.01em;
424
- }
425
- .cmssy-tab-inactive {
426
- background: transparent;
427
- color: rgba(255, 255, 255, 0.45);
428
- }
429
- .cmssy-tab-inactive:hover {
430
- background: rgba(255, 255, 255, 0.08);
431
- color: rgba(255, 255, 255, 0.8);
432
- }
433
- .cmssy-tab-active {
434
- background: #667eea;
435
- color: #fff;
436
- box-shadow: 0 2px 8px rgba(102, 126, 234, 0.35);
437
- }
438
- .cmssy-page-count {
439
- font-size: 11px;
440
- font-weight: 500;
441
- color: rgba(255, 255, 255, 0.3);
442
- white-space: nowrap;
443
- flex-shrink: 0;
444
- padding-right: 4px;
445
- }
446
- .cmssy-collapse-btn {
447
- display: flex;
448
- align-items: center;
449
- justify-content: center;
450
- width: 28px;
451
- height: 28px;
452
- background: rgba(255, 255, 255, 0.06);
453
- border: 1px solid rgba(255, 255, 255, 0.08);
454
- border-radius: 7px;
455
- color: rgba(255, 255, 255, 0.5);
456
- cursor: pointer;
457
- flex-shrink: 0;
458
- transition: all 0.15s ease;
459
- font-family: inherit;
460
- }
461
- .cmssy-collapse-btn:hover {
462
- background: rgba(255, 255, 255, 0.12);
463
- color: #fff;
464
- }
465
- .cmssy-nav-collapsed .cmssy-nav-inner {
466
- padding: 6px 8px;
467
- width: fit-content;
468
- }
469
- .cmssy-nav-pill {
470
- display: flex;
471
- align-items: center;
472
- gap: 8px;
473
- font-family: 'DM Sans', -apple-system, sans-serif;
474
- }
475
- .cmssy-nav-pill-page {
476
- font-size: 12px;
477
- font-weight: 500;
478
- color: rgba(255, 255, 255, 0.7);
479
- white-space: nowrap;
480
- }
481
- .cmssy-search-btn {
482
- display: flex;
483
- align-items: center;
484
- gap: 6px;
485
- padding: 5px 10px;
486
- background: rgba(255, 255, 255, 0.06);
487
- border: 1px solid rgba(255, 255, 255, 0.08);
488
- border-radius: 7px;
489
- color: rgba(255, 255, 255, 0.5);
490
- font-size: 11px;
491
- font-family: inherit;
492
- cursor: pointer;
493
- white-space: nowrap;
494
- transition: all 0.15s ease;
495
- flex-shrink: 0;
496
- }
497
- .cmssy-search-btn:hover {
498
- background: rgba(255, 255, 255, 0.1);
499
- color: rgba(255, 255, 255, 0.8);
500
- }
501
- .cmssy-search-kbd {
502
- font-size: 10px;
503
- padding: 1px 5px;
504
- border-radius: 4px;
505
- background: rgba(255, 255, 255, 0.08);
506
- color: rgba(255, 255, 255, 0.35);
507
- font-family: inherit;
508
- }
509
- .cmssy-palette-overlay {
510
- position: fixed;
511
- inset: 0;
512
- z-index: 10000;
513
- background: rgba(0, 0, 0, 0.5);
514
- backdrop-filter: blur(4px);
515
- display: flex;
516
- align-items: flex-start;
517
- justify-content: center;
518
- padding-top: 20vh;
519
- animation: cmssyFadeIn 0.15s ease;
520
- }
521
- .cmssy-palette {
522
- width: 480px;
523
- max-width: calc(100vw - 32px);
524
- background: #1a1a1a;
525
- border: 1px solid rgba(255, 255, 255, 0.1);
526
- border-radius: 14px;
527
- box-shadow: 0 24px 64px rgba(0, 0, 0, 0.6);
528
- overflow: hidden;
529
- animation: cmssySlideUp 0.15s ease;
530
- font-family: 'DM Sans', -apple-system, sans-serif;
531
- }
532
- .cmssy-palette-input-wrapper {
533
- display: flex;
534
- align-items: center;
535
- padding: 14px 16px;
536
- border-bottom: 1px solid rgba(255, 255, 255, 0.08);
537
- gap: 10px;
538
- }
539
- .cmssy-palette-input {
540
- flex: 1;
541
- background: none;
542
- border: none;
543
- outline: none;
544
- color: #fff;
545
- font-size: 15px;
546
- font-family: inherit;
547
- letter-spacing: -0.01em;
548
- }
549
- .cmssy-palette-input::placeholder {
550
- color: rgba(255, 255, 255, 0.3);
551
- }
552
- .cmssy-palette-list {
553
- max-height: 340px;
554
- overflow-y: auto;
555
- padding: 6px;
556
- scrollbar-width: thin;
557
- scrollbar-color: rgba(255,255,255,0.1) transparent;
558
- }
559
- .cmssy-palette-item {
560
- display: flex;
561
- align-items: center;
562
- justify-content: space-between;
563
- padding: 10px 12px;
564
- border-radius: 8px;
565
- cursor: pointer;
566
- transition: background 0.1s ease;
567
- border: none;
568
- background: none;
569
- width: 100%;
570
- text-align: left;
571
- font-family: inherit;
572
- }
573
- .cmssy-palette-item:hover {
574
- background: rgba(255, 255, 255, 0.06);
575
- }
576
- .cmssy-palette-item-active {
577
- background: rgba(102, 126, 234, 0.15);
578
- }
579
- .cmssy-palette-item-active:hover {
580
- background: rgba(102, 126, 234, 0.2);
581
- }
582
- .cmssy-palette-item-name {
583
- font-size: 14px;
584
- font-weight: 500;
585
- color: #fff;
586
- }
587
- .cmssy-palette-item-slug {
588
- font-size: 12px;
589
- color: rgba(255, 255, 255, 0.3);
590
- font-family: monospace;
591
- }
592
- .cmssy-palette-item-current {
593
- font-size: 10px;
594
- padding: 2px 8px;
595
- border-radius: 4px;
596
- background: #667eea;
597
- color: #fff;
598
- font-weight: 600;
599
- letter-spacing: 0.03em;
600
- }
601
- .cmssy-palette-empty {
602
- padding: 24px 16px;
603
- text-align: center;
604
- color: rgba(255, 255, 255, 0.3);
605
- font-size: 13px;
606
- }
607
- @keyframes cmssyFadeIn {
608
- from { opacity: 0; }
609
- to { opacity: 1; }
610
- }
611
- @keyframes cmssySlideUp {
612
- from { opacity: 0; transform: translateY(8px) scale(0.98); }
613
- to { opacity: 1; transform: translateY(0) scale(1); }
614
- }
615
- \`}</style>
616
-
617
- {/* Floating Navigation Bar */}
618
- <div className={\`cmssy-nav \${navCollapsed ? "cmssy-nav-collapsed" : ""}\`}>
619
- <div className="cmssy-nav-inner">
620
- {navCollapsed ? (
621
- <div className="cmssy-nav-pill">
622
- <a href="/" className="cmssy-back-btn">
623
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
624
- <polyline points="15 18 9 12 15 6" />
625
- </svg>
626
- Dev
627
- </a>
628
- <div className="cmssy-divider" />
629
- <div className="cmssy-nav-pill-page">{currentPage?.name || "—"}</div>
630
- <button
631
- className="cmssy-search-btn"
632
- onClick={() => { setPaletteOpen(true); setPaletteQuery(""); }}
633
- title="Search pages (Ctrl+K)"
634
- >
635
- <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
636
- <circle cx="11" cy="11" r="8" />
637
- <line x1="21" y1="21" x2="16.65" y2="16.65" />
638
- </svg>
639
- <span className="cmssy-search-kbd">\u2318K</span>
640
- </button>
641
- <button
642
- className="cmssy-collapse-btn"
643
- onClick={() => setNavCollapsed(false)}
644
- title="Expand navigation"
645
- >
646
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
647
- <polyline points="6 9 12 15 18 9" />
648
- </svg>
649
- </button>
650
- </div>
651
- ) : (
652
- <>
653
- <a href="/" className="cmssy-back-btn">
654
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
655
- <polyline points="15 18 9 12 15 6" />
656
- </svg>
657
- Dev
658
- </a>
659
-
660
- <div className="cmssy-divider" />
661
-
662
- <div className="cmssy-template-name">${resource.displayName || resource.name}</div>
663
-
664
- <div className="cmssy-divider" />
665
-
666
- <div className="cmssy-tabs-wrapper" ref={tabsRef}>
667
- <div className="cmssy-tabs">
668
- {pagesData.pages.map((page) => (
669
- <button
670
- key={page.slug}
671
- ref={page.slug === currentSlug ? activeTabRef : undefined}
672
- onClick={() => navigateTo(page.slug)}
673
- className={\`cmssy-tab \${page.slug === currentSlug ? "cmssy-tab-active" : "cmssy-tab-inactive"}\`}
674
- >
675
- {page.name}
676
- </button>
677
- ))}
678
- </div>
679
- </div>
680
-
681
- <button
682
- className="cmssy-search-btn"
683
- onClick={() => { setPaletteOpen(true); setPaletteQuery(""); }}
684
- title="Search pages (Ctrl+K)"
685
- >
686
- <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
687
- <circle cx="11" cy="11" r="8" />
688
- <line x1="21" y1="21" x2="16.65" y2="16.65" />
689
- </svg>
690
- <span className="cmssy-search-kbd">\u2318K</span>
691
- </button>
692
-
693
- <button
694
- className="cmssy-collapse-btn"
695
- onClick={() => setNavCollapsed(true)}
696
- title="Collapse navigation"
697
- >
698
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
699
- <polyline points="18 15 12 9 6 15" />
700
- </svg>
701
- </button>
702
- </>
703
- )}
704
- </div>
705
- </div>
706
-
707
- {/* Command Palette */}
708
- {paletteOpen && (
709
- <div className="cmssy-palette-overlay" onClick={() => setPaletteOpen(false)}>
710
- <div className="cmssy-palette" onClick={(e) => e.stopPropagation()}>
711
- <div className="cmssy-palette-input-wrapper">
712
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.4)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
713
- <circle cx="11" cy="11" r="8" />
714
- <line x1="21" y1="21" x2="16.65" y2="16.65" />
715
- </svg>
716
- <input
717
- ref={paletteInputRef}
718
- type="text"
719
- className="cmssy-palette-input"
720
- placeholder="Search pages..."
721
- value={paletteQuery}
722
- onChange={(e) => setPaletteQuery(e.target.value)}
723
- onKeyDown={(e) => {
724
- if (e.key === "Enter") {
725
- const q = paletteQuery.toLowerCase();
726
- const match = pagesData.pages.find(
727
- (p) => p.name.toLowerCase().includes(q) || p.slug.toLowerCase().includes(q)
728
- );
729
- if (match) {
730
- navigateTo(match.slug);
731
- setPaletteOpen(false);
732
- }
733
- }
734
- }}
735
- />
736
- </div>
737
- <div className="cmssy-palette-list">
738
- {(() => {
739
- const q = paletteQuery.toLowerCase();
740
- const filtered = pagesData.pages.filter(
741
- (p) => p.name.toLowerCase().includes(q) || p.slug.toLowerCase().includes(q)
742
- );
743
- if (filtered.length === 0) {
744
- return <div className="cmssy-palette-empty">No pages match &ldquo;{paletteQuery}&rdquo;</div>;
745
- }
746
- return filtered.map((page) => (
747
- <button
748
- key={page.slug}
749
- className={\`cmssy-palette-item \${page.slug === currentSlug ? "cmssy-palette-item-active" : ""}\`}
750
- onClick={() => {
751
- navigateTo(page.slug);
752
- setPaletteOpen(false);
753
- }}
754
- >
755
- <div>
756
- <div className="cmssy-palette-item-name">{page.name}</div>
757
- <div className="cmssy-palette-item-slug">{page.slug}</div>
758
- </div>
759
- {page.slug === currentSlug && (
760
- <span className="cmssy-palette-item-current">Current</span>
761
- )}
762
- </button>
763
- ));
764
- })()}
765
- </div>
766
- </div>
767
- </div>
768
- )}
769
-
770
- {/* Page Content */}
771
- <div>
772
- {HeaderComponent && headerData && (
773
- <HeaderComponent content={headerData.content || {}} />
774
- )}
775
- {SidebarLeftComponent && sidebarLeftData ? (
776
- <div style={{ display: "grid", gridTemplateColumns: "280px 1fr", minHeight: "100vh" }}>
777
- <SidebarLeftComponent content={sidebarLeftData.content || {}} />
778
- <main>
779
- {pageBlocks.map((block, idx) => {
780
- const blockType = convertBlockType(block.type);
781
- const Component = BLOCK_COMPONENTS[blockType];
782
- if (!Component) {
783
- return (
784
- <div key={idx} style={{
785
- padding: "2rem",
786
- background: "#fff3cd",
787
- textAlign: "center",
788
- margin: "1rem",
789
- borderRadius: "8px",
790
- fontSize: "14px",
791
- color: "#856404",
792
- }}>
793
- Missing block component: {blockType}
794
- </div>
795
- );
796
- }
797
- return <Component key={idx} content={block.content || {}} />;
798
- })}
799
- </main>
800
- </div>
801
- ) : (
802
- pageBlocks.map((block, idx) => {
803
- const blockType = convertBlockType(block.type);
804
- const Component = BLOCK_COMPONENTS[blockType];
805
- if (!Component) {
806
- return (
807
- <div key={idx} style={{
808
- padding: "2rem",
809
- background: "#fff3cd",
810
- textAlign: "center",
811
- margin: "1rem",
812
- borderRadius: "8px",
813
- fontSize: "14px",
814
- color: "#856404",
815
- }}>
816
- Missing block component: {blockType}
817
- </div>
818
- );
819
- }
820
- return <Component key={idx} content={block.content || {}} />;
821
- })
822
- )}
823
- {FooterComponent && footerData && (
824
- <FooterComponent content={footerData.content || {}} />
825
- )}
826
- </div>
827
- </>
828
- );
829
- }
830
- `;
831
- fs.writeFileSync(path.join(pageDir, "page.tsx"), pageContent);
832
- }
833
- //# sourceMappingURL=preview-pages.js.map