@cmssy/cli 0.22.3 → 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 -231
  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
package/dist/cli.js CHANGED
@@ -1,340 +1,718 @@
1
1
  #!/usr/bin/env node
2
- import chalk from "chalk";
3
- import { Command, Option } from "commander";
4
- import { buildCommand } from "./commands/build.js";
5
- import { codegenCommand } from "./commands/codegen.js";
6
- import { configureCommand } from "./commands/configure.js";
7
- import { linkCommand } from "./commands/link.js";
8
- import { createCommand } from "./commands/create.js";
9
- import { devCommand } from "./commands/dev.js";
10
- import { doctorCommand } from "./commands/doctor.js";
11
- import { initCommand } from "./commands/init.js";
12
- import { testCommand } from "./commands/test.js";
13
- import { syncCommand } from "./commands/sync.js";
14
- import { migrateCommand } from "./commands/migrate.js";
15
- import { publishBlockBuildtimeCommand } from "./commands/publish-block-buildtime.js";
16
- import { publishTemplateCommand } from "./commands/publish-template.js";
17
- import { libInstallCommand, libSyncCommand } from "./commands/lib.js";
18
- import { skillsInstallCommand, skillsListCommand } from "./commands/skills.js";
19
- import { workspacesCommand } from "./commands/workspaces.js";
20
- import { readFileSync } from "fs";
21
- import { fileURLToPath } from "url";
2
+
3
+
4
+ // src/utils/args.ts
5
+ function parseArgs(argv) {
6
+ const positionals = [];
7
+ const flags = {};
8
+ for (let i = 0; i < argv.length; i++) {
9
+ const arg = argv[i];
10
+ if (arg.startsWith("--")) {
11
+ const key = arg.slice(2);
12
+ const eq = key.indexOf("=");
13
+ if (eq !== -1) {
14
+ flags[key.slice(0, eq)] = key.slice(eq + 1);
15
+ } else {
16
+ const next = argv[i + 1];
17
+ if (next !== void 0 && !next.startsWith("-")) {
18
+ flags[key] = next;
19
+ i++;
20
+ } else {
21
+ flags[key] = true;
22
+ }
23
+ }
24
+ } else if (arg.startsWith("-") && arg.length > 1) {
25
+ flags[arg.slice(1)] = true;
26
+ } else {
27
+ positionals.push(arg);
28
+ }
29
+ }
30
+ return { positionals, flags };
31
+ }
32
+
33
+ // src/utils/ui.ts
34
+ import { existsSync, readFileSync } from "fs";
22
35
  import { dirname, join } from "path";
23
- const __filename = fileURLToPath(import.meta.url);
24
- const __dirname = dirname(__filename);
25
- const packageJson = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
26
- const program = new Command();
27
- program
28
- .name("cmssy")
29
- .description("Unified CLI for building and publishing blocks to Cmssy design library")
30
- .version(packageJson.version)
31
- .addHelpText("after", `
32
- Examples:
33
- $ cmssy init my-blocks Create new project (uses create-next-app)
34
- $ cmssy init Add Cmssy to existing Next.js project
35
- $ cmssy create block hero Add a new block to your project
36
- $ cmssy dev Start dev server with hot reload
37
- $ cmssy build Build all blocks for production
38
- $ cmssy publish-block hero -w abc Publish a single block to workspace
36
+ import { fileURLToPath } from "url";
37
+ import pc from "picocolors";
38
+ function getVersion() {
39
+ try {
40
+ let dir = dirname(fileURLToPath(import.meta.url));
41
+ for (let i = 0; i < 6; i++) {
42
+ const p = join(dir, "package.json");
43
+ if (existsSync(p)) {
44
+ const pkg = JSON.parse(readFileSync(p, "utf8"));
45
+ return pkg.version ?? "0.0.0";
46
+ }
47
+ dir = dirname(dir);
48
+ }
49
+ } catch {
50
+ }
51
+ return "0.0.0";
52
+ }
53
+ var ui = {
54
+ info: (msg) => console.log(msg),
55
+ dim: (msg) => console.log(pc.dim(msg)),
56
+ success: (msg) => console.log(pc.green(msg)),
57
+ warn: (msg) => console.log(pc.yellow(msg)),
58
+ error: (msg) => console.error(pc.red(msg))
59
+ };
60
+
61
+ // src/commands/init.ts
62
+ import { join as join7, resolve } from "path";
63
+ import { intro, log as log2, note, outro } from "@clack/prompts";
64
+
65
+ // src/utils/constants.ts
66
+ var CMSSY_DEPS = {
67
+ "@cmssy/next": "^0.5.6",
68
+ "@cmssy/react": "^0.5.6"
69
+ };
70
+ var DOCS_URL = "https://www.cmssy.com/docs";
71
+ var HEADLESS_SETTINGS_HINT = "cmssy dashboard -> Settings -> Headless";
72
+
73
+ // src/utils/overlay.ts
74
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
75
+ import { join as join3 } from "path";
76
+
77
+ // src/utils/files.ts
78
+ import { existsSync as existsSync2 } from "fs";
79
+ import { mkdir, readFile, writeFile } from "fs/promises";
80
+ import { dirname as dirname2 } from "path";
81
+ function pathExists(p) {
82
+ return existsSync2(p);
83
+ }
84
+ async function ensureDir(dir) {
85
+ await mkdir(dir, { recursive: true });
86
+ }
87
+ async function writeFileSafe(dest, content, opts = {}) {
88
+ if (pathExists(dest)) {
89
+ const current = await readFile(dest, "utf8");
90
+ if (current === content) return "unchanged";
91
+ if (!opts.force) return "skipped";
92
+ }
93
+ await ensureDir(dirname2(dest));
94
+ await writeFile(dest, content, "utf8");
95
+ return "written";
96
+ }
97
+
98
+ // src/utils/templates.ts
99
+ import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
100
+ import { dirname as dirname3, join as join2, relative } from "path";
101
+ import { fileURLToPath as fileURLToPath2 } from "url";
102
+ function templatesRoot() {
103
+ let dir = dirname3(fileURLToPath2(import.meta.url));
104
+ for (let i = 0; i < 6; i++) {
105
+ const candidate = join2(dir, "templates");
106
+ if (existsSync3(join2(candidate, "init"))) return candidate;
107
+ dir = dirname3(dir);
108
+ }
109
+ throw new Error("cmssy CLI templates not found");
110
+ }
111
+ function readTemplate(...segments) {
112
+ return readFileSync2(join2(templatesRoot(), ...segments), "utf8");
113
+ }
114
+ function renderTemplate(content, vars) {
115
+ return content.replace(
116
+ /\{\{(\w+)\}\}/g,
117
+ (match, key) => key in vars ? vars[key] : match
118
+ );
119
+ }
120
+ function collectFiles(subtree) {
121
+ const root = join2(templatesRoot(), subtree);
122
+ const out = [];
123
+ const walk = (dir) => {
124
+ for (const entry of readdirSync(dir)) {
125
+ const abs = join2(dir, entry);
126
+ if (statSync(abs).isDirectory()) {
127
+ walk(abs);
128
+ } else {
129
+ out.push({ rel: relative(root, abs).split("\\").join("/"), abs });
130
+ }
131
+ }
132
+ };
133
+ walk(root);
134
+ return out;
135
+ }
136
+
137
+ // src/utils/overlay.ts
138
+ var ROOT_ONLY = /* @__PURE__ */ new Set([".env.example", "next.config.mjs"]);
139
+ var OTHER_NEXT_CONFIGS = [
140
+ "next.config.js",
141
+ "next.config.ts",
142
+ "next.config.cjs"
143
+ ];
144
+ function destFor(rel) {
145
+ return rel === "env.example" ? ".env.example" : rel;
146
+ }
147
+ async function applyOverlay(targetDir, srcDir = false) {
148
+ const report = { written: [], skipped: [], unchanged: [] };
149
+ for (const file of collectFiles("init")) {
150
+ const dest = destFor(file.rel);
151
+ if (dest === "next.config.mjs" && OTHER_NEXT_CONFIGS.some((f) => existsSync4(join3(targetDir, f)))) {
152
+ report.skipped.push(dest);
153
+ continue;
154
+ }
155
+ const content = readFileSync3(file.abs, "utf8");
156
+ const full = srcDir && !ROOT_ONLY.has(dest) ? join3(targetDir, "src", dest) : join3(targetDir, dest);
157
+ const result = await writeFileSafe(full, content);
158
+ report[result].push(dest);
159
+ }
160
+ return report;
161
+ }
162
+
163
+ // src/utils/project.ts
164
+ import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
165
+ import { join as join4 } from "path";
166
+ var IGNORED_ENTRIES = /* @__PURE__ */ new Set([".git", ".DS_Store", "Thumbs.db"]);
167
+ function readPackageJson(cwd) {
168
+ const p = join4(cwd, "package.json");
169
+ if (!existsSync5(p)) return null;
170
+ try {
171
+ return JSON.parse(readFileSync4(p, "utf8"));
172
+ } catch {
173
+ return null;
174
+ }
175
+ }
176
+ function hasDep(pkg, name) {
177
+ if (!pkg) return false;
178
+ return Boolean(pkg.dependencies?.[name] ?? pkg.devDependencies?.[name]);
179
+ }
180
+ function detectProject(cwd) {
181
+ const pkg = readPackageJson(cwd);
182
+ const entries = existsSync5(cwd) ? readdirSync2(cwd).filter((e) => !IGNORED_ENTRIES.has(e)) : [];
183
+ const appDir = existsSync5(join4(cwd, "app")) ? join4(cwd, "app") : existsSync5(join4(cwd, "src", "app")) ? join4(cwd, "src", "app") : null;
184
+ const hasNext = hasDep(pkg, "next");
185
+ return {
186
+ cwd,
187
+ hasPackageJson: pkg !== null,
188
+ pkg,
189
+ isEmpty: entries.length === 0,
190
+ hasNext,
191
+ appDir,
192
+ isNextAppRouter: hasNext && appDir !== null
193
+ };
194
+ }
195
+
196
+ // src/utils/pkg.ts
197
+ import { spawn } from "child_process";
198
+ import { existsSync as existsSync6 } from "fs";
199
+ import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
200
+ import { join as join5 } from "path";
201
+ function detectPackageManager(cwd) {
202
+ if (existsSync6(join5(cwd, "pnpm-lock.yaml"))) return "pnpm";
203
+ if (existsSync6(join5(cwd, "yarn.lock"))) return "yarn";
204
+ if (existsSync6(join5(cwd, "bun.lockb")) || existsSync6(join5(cwd, "bun.lock"))) {
205
+ return "bun";
206
+ }
207
+ return "npm";
208
+ }
209
+ async function ensureDependencies(cwd, deps) {
210
+ const p = join5(cwd, "package.json");
211
+ const pkg = JSON.parse(await readFile2(p, "utf8"));
212
+ pkg.dependencies ??= {};
213
+ const added = [];
214
+ for (const [name, version] of Object.entries(deps)) {
215
+ if (!pkg.dependencies[name] && !pkg.devDependencies?.[name]) {
216
+ pkg.dependencies[name] = version;
217
+ added.push(name);
218
+ }
219
+ }
220
+ if (added.length) {
221
+ await writeFile2(p, JSON.stringify(pkg, null, 2) + "\n", "utf8");
222
+ }
223
+ return added;
224
+ }
225
+ function run(command, args, cwd) {
226
+ return new Promise((resolve2, reject) => {
227
+ const child = spawn(command, args, { cwd, stdio: "inherit", shell: false });
228
+ child.on("error", reject);
229
+ child.on("close", (code) => {
230
+ if (code === 0) resolve2();
231
+ else
232
+ reject(new Error(`${command} ${args.join(" ")} exited with ${code}`));
233
+ });
234
+ });
235
+ }
236
+ function installArgs(pm) {
237
+ return pm === "yarn" ? [] : ["install"];
238
+ }
239
+
240
+ // src/commands/link.ts
241
+ import { join as join6 } from "path";
242
+ import { cancel, isCancel, log, password, spinner, text } from "@clack/prompts";
243
+
244
+ // src/utils/delivery.ts
245
+ var DEFAULT_API_URL = "https://api.cmssy.io/graphql";
246
+ var SITE_CONFIG_QUERY = `query PublicSiteConfig($workspaceSlug: String!) {
247
+ publicSiteConfig(workspaceSlug: $workspaceSlug) {
248
+ workspaceId
249
+ siteName
250
+ defaultLanguage
251
+ enabledLanguages
252
+ }
253
+ }`;
254
+ function pickName(value, defaultLanguage) {
255
+ if (typeof value === "string") return value;
256
+ if (value && typeof value === "object") {
257
+ return value[defaultLanguage ?? "en"] ?? Object.values(value)[0] ?? null;
258
+ }
259
+ return null;
260
+ }
261
+ async function resolveWorkspace(slug, apiUrl = DEFAULT_API_URL) {
262
+ try {
263
+ const res = await fetch(apiUrl, {
264
+ method: "POST",
265
+ headers: { "content-type": "application/json" },
266
+ body: JSON.stringify({
267
+ query: SITE_CONFIG_QUERY,
268
+ variables: { workspaceSlug: slug }
269
+ })
270
+ });
271
+ if (!res.ok) {
272
+ return {
273
+ status: "error",
274
+ message: `delivery API returned ${res.status}`
275
+ };
276
+ }
277
+ const json = await res.json();
278
+ const config = json.data?.publicSiteConfig;
279
+ if (!config) return { status: "not-found" };
280
+ return {
281
+ status: "found",
282
+ siteName: pickName(config.siteName, config.defaultLanguage)
283
+ };
284
+ } catch (err) {
285
+ return {
286
+ status: "error",
287
+ message: err instanceof Error ? err.message : String(err)
288
+ };
289
+ }
290
+ }
291
+
292
+ // src/utils/env.ts
293
+ import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
294
+ var KEY_LINE = /^([A-Z0-9_]+)=(.*)$/;
295
+ async function setEnvVars(filePath, vars, opts = {}) {
296
+ const existing = pathExists(filePath) ? await readFile3(filePath, "utf8") : "";
297
+ const lines = existing.length ? existing.replace(/\n$/, "").split("\n") : [];
298
+ const remaining = new Set(Object.keys(vars));
299
+ const next = lines.map((line) => {
300
+ const m = KEY_LINE.exec(line);
301
+ if (!m) return line;
302
+ const key = m[1];
303
+ if (!(key in vars)) return line;
304
+ remaining.delete(key);
305
+ const raw = m[2];
306
+ const comment = /\s+#.*$/.exec(raw);
307
+ const value = comment ? raw.slice(0, comment.index) : raw;
308
+ if (value.trim().length > 0 && !opts.overwrite) return line;
309
+ return `${key}=${vars[key]}${comment ? comment[0] : ""}`;
310
+ });
311
+ for (const key of remaining) {
312
+ next.push(`${key}=${vars[key]}`);
313
+ }
314
+ await writeFile3(filePath, next.join("\n") + "\n", "utf8");
315
+ }
316
+
317
+ // src/commands/link.ts
318
+ function flagString(value) {
319
+ return typeof value === "string" ? value : void 0;
320
+ }
321
+ function bail() {
322
+ cancel("Cancelled.");
323
+ process.exit(0);
324
+ }
325
+ async function runLink(cwd, flags) {
326
+ let slug = flagString(flags.slug);
327
+ if (!slug) {
328
+ const answer = await text({
329
+ message: "Workspace slug",
330
+ placeholder: "my-workspace",
331
+ validate: (v) => v?.trim() ? void 0 : "Required"
332
+ });
333
+ if (isCancel(answer)) bail();
334
+ slug = answer.trim();
335
+ }
336
+ let secret = flagString(flags.secret);
337
+ if (!secret) {
338
+ const answer = await password({
339
+ message: `Draft secret (${HEADLESS_SETTINGS_HINT})`,
340
+ validate: (v) => v?.trim() ? void 0 : "Required"
341
+ });
342
+ if (isCancel(answer)) bail();
343
+ secret = answer.trim();
344
+ }
345
+ const apiUrl = flagString(flags["api-url"]);
346
+ const s = spinner();
347
+ s.start("Checking workspace");
348
+ const lookup = await resolveWorkspace(slug, apiUrl);
349
+ if (lookup.status === "found") {
350
+ s.stop(`Workspace found${lookup.siteName ? `: ${lookup.siteName}` : ""}`);
351
+ } else if (lookup.status === "not-found") {
352
+ s.stop(pc.yellow(`No published workspace "${slug}" yet - saving anyway`));
353
+ } else {
354
+ s.stop(pc.yellow(`Could not verify workspace (${lookup.message})`));
355
+ }
356
+ await setEnvVars(
357
+ join6(cwd, ".env"),
358
+ { CMSSY_WORKSPACE_SLUG: slug, CMSSY_DRAFT_SECRET: secret },
359
+ { overwrite: true }
360
+ );
361
+ await setEnvVars(
362
+ join6(cwd, ".env.example"),
363
+ { CMSSY_WORKSPACE_SLUG: "", CMSSY_DRAFT_SECRET: "" },
364
+ { overwrite: false }
365
+ );
366
+ log.success("Wrote .env");
367
+ }
368
+ async function linkCommand(args) {
369
+ const cwd = process.cwd();
370
+ const hasConfig = pathExists(join6(cwd, "cmssy.config.ts")) || pathExists(join6(cwd, "src", "cmssy.config.ts"));
371
+ if (!hasConfig) {
372
+ log.warn("No cmssy.config.ts here - run `cmssy init` first.");
373
+ }
374
+ const { intro: intro4, outro: outro4 } = await import("@clack/prompts");
375
+ intro4(pc.bold("cmssy link"));
376
+ await runLink(cwd, args.flags);
377
+ outro4("Linked.");
378
+ }
39
379
 
40
- Workflow:
41
- 1. init → Create project with example block
42
- 2. link → Connect to your workspace
43
- 3. create → Add more blocks/templates
44
- 4. dev → Develop with live preview
45
- 5. test → Run block tests
46
- 6. build → Bundle for production
47
- 7. publish-block Deploy a block via the sandbox build pipeline
380
+ // src/commands/init.ts
381
+ var PMS = /* @__PURE__ */ new Set(["npm", "pnpm", "yarn", "bun"]);
382
+ function choosePm(flags, targetDir) {
383
+ const flag = typeof flags.pm === "string" ? flags.pm : void 0;
384
+ if (flag && PMS.has(flag)) return flag;
385
+ return detectPackageManager(targetDir);
386
+ }
387
+ async function initCommand(args) {
388
+ const cwd = process.cwd();
389
+ const dirArg = args.positionals[0];
390
+ const targetDir = dirArg ? resolve(cwd, dirArg) : cwd;
391
+ const { flags } = args;
392
+ intro(pc.bold("cmssy init"));
393
+ const info = detectProject(targetDir);
394
+ if (!info.isNextAppRouter) {
395
+ log2.error("No Next.js App Router project found here.");
396
+ ui.dim(
397
+ "cmssy init wires an existing Next.js App Router app. Create one first:"
398
+ );
399
+ ui.dim(" npx create-next-app@latest");
400
+ ui.dim("then run cmssy init inside it.");
401
+ process.exitCode = 1;
402
+ return;
403
+ }
404
+ log2.info("Adding cmssy wiring to your Next.js app.");
405
+ const pm = choosePm(flags, targetDir);
406
+ const srcDir = info.appDir === join7(targetDir, "src", "app");
407
+ const report = await applyOverlay(targetDir, srcDir);
408
+ if (report.written.length) {
409
+ log2.success(`Added ${report.written.length} file(s)`);
410
+ }
411
+ if (report.skipped.length) {
412
+ log2.warn(`Skipped existing: ${report.skipped.join(", ")}`);
413
+ }
414
+ const added = await ensureDependencies(targetDir, CMSSY_DEPS);
415
+ if (added.length) log2.success(`Added deps: ${added.join(", ")}`);
416
+ if (!flags["no-link"]) {
417
+ await runLink(targetDir, flags);
418
+ }
419
+ const skipInstall = Boolean(flags["skip-install"]);
420
+ if (!skipInstall) {
421
+ log2.step(`Installing dependencies (${pm})`);
422
+ await run(pm, installArgs(pm), targetDir);
423
+ }
424
+ const steps = [
425
+ ...dirArg ? [`cd ${dirArg}`] : [],
426
+ ...skipInstall ? [`${pm} install`] : [],
427
+ `${pm === "npm" ? "npm run" : pm} dev`,
428
+ "Open the site in the cmssy editor to edit visually."
429
+ ];
430
+ if (report.skipped.includes("next.config.mjs")) {
431
+ steps.push(
432
+ pc.yellow(
433
+ "Add images.remotePatterns for assets.cmssy.io to your next.config so cmssy media renders."
434
+ )
435
+ );
436
+ }
437
+ note(steps.join("\n"), "Next steps");
438
+ outro(`Done. Docs: ${DOCS_URL}`);
439
+ }
48
440
 
49
- Documentation: https://cmssy.io/docs/cli
50
- `);
51
- // cmssy init
52
- program
53
- .command("init")
54
- .description("Initialize a new Cmssy project or add Cmssy to existing one")
55
- .argument("[name]", "Project name (creates new directory with create-next-app)")
56
- .option("-y, --yes", "Skip prompts and use defaults")
57
- .addHelpText("after", `
58
- Examples:
59
- $ cmssy init my-blocks New project (runs create-next-app)
60
- $ cmssy init -y my-blocks New project with defaults (no prompts)
61
- $ cmssy init Add Cmssy to existing Next.js project
62
- `)
63
- .action((name, options) => initCommand(name, options));
64
- // cmssy create
65
- const create = program
66
- .command("create")
67
- .description("Create a new block or template")
68
- .addHelpText("after", `
69
- Examples:
70
- $ cmssy create block hero
71
- $ cmssy create block pricing -c marketing -t "pricing,plans"
72
- $ cmssy create template landing-page
73
- `);
74
- create
75
- .command("block")
76
- .description("Create a new block with scaffold files")
77
- .argument("<name>", "Block name (kebab-case recommended)")
78
- .option("-y, --yes", "Skip prompts and use defaults")
79
- .option("-d, --description <description>", "Block description")
80
- .option("-c, --category <category>", "Category: marketing, typography, media, layout, forms, navigation, other")
81
- .option("-t, --tags <tags>", "Comma-separated tags")
82
- .addHelpText("after", `
83
- Creates:
84
- blocks/<name>/
85
- ├── config.ts Type-safe configuration
86
- ├── package.json Name and version
87
- ├── preview.json Preview data for dev server
88
- └── src/
89
- ├── index.tsx Entry point (mount/unmount)
90
- ├── Block.tsx React component
91
- ├── block.d.ts Auto-generated types
92
- └── index.css Styles
93
- `)
94
- .action(createCommand.block);
95
- create
96
- .command("template")
97
- .description("Create a new page template")
98
- .argument("<name>", "Template name (kebab-case recommended)")
99
- .option("-y, --yes", "Skip prompts and use defaults")
100
- .option("-d, --description <description>", "Template description")
101
- .action(createCommand.page);
102
- // cmssy build
103
- program
104
- .command("build")
105
- .description("Build blocks and templates for production")
106
- .option("--block <names...>", "Build only specific blocks/templates")
107
- .option("--framework <framework>", "Override framework from config")
108
- .addHelpText("after", `
109
- Examples:
110
- $ cmssy build Build all blocks and templates
111
- $ cmssy build --block hero pricing Build only hero and pricing blocks
441
+ // src/commands/add-block.ts
442
+ import { existsSync as existsSync7 } from "fs";
443
+ import { join as join8 } from "path";
444
+ import { cancel as cancel2, intro as intro2, isCancel as isCancel2, log as log3, outro as outro2, text as text2 } from "@clack/prompts";
112
445
 
113
- Output:
114
- public/@vendor/blocks.<name>/<version>/
115
- ├── index.js Bundled JavaScript
116
- ├── index.css Styles
117
- └── package.json Metadata for design library
118
- `)
119
- .action(buildCommand);
120
- // cmssy dev
121
- program
122
- .command("dev")
123
- .description("Start development server with hot reload")
124
- .option("-p, --port <port>", "Port number", "3000")
125
- .addHelpText("after", `
126
- Examples:
127
- $ cmssy dev Start on default port 3000
128
- $ cmssy dev -p 8080 Start on port 8080
446
+ // src/utils/names.ts
447
+ function words(input) {
448
+ return input.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[\s_-]+/).map((w) => w.replace(/[^a-zA-Z0-9]/g, "").toLowerCase()).filter(Boolean);
449
+ }
450
+ function blockNames(input) {
451
+ const parts = words(input);
452
+ if (parts.length === 0) {
453
+ throw new Error("Block name must contain at least one letter or digit.");
454
+ }
455
+ if (!/^[a-z]/.test(parts[0])) {
456
+ throw new Error(
457
+ "Block name must start with a letter (it becomes a TypeScript identifier)."
458
+ );
459
+ }
460
+ const type = parts.join("-");
461
+ const Pascal = parts.map((w) => w[0].toUpperCase() + w.slice(1)).join("");
462
+ const camel = Pascal[0].toLowerCase() + Pascal.slice(1);
463
+ const Label = parts.map((w) => w[0].toUpperCase() + w.slice(1)).join(" ");
464
+ return { type, camel, Pascal, Label };
465
+ }
129
466
 
130
- Features:
131
- Live preview of all blocks/templates
132
- Hot reload on file changes
133
- Auto-regenerates types on config.ts changes
134
- `)
135
- .action(devCommand);
136
- // cmssy test
137
- program
138
- .command("test")
139
- .description("Run block tests with vitest")
140
- .option("--block <names...>", "Test specific blocks")
141
- .option("--watch", "Watch mode")
142
- .option("--coverage", "Generate coverage report")
143
- .addHelpText("after", `
144
- Examples:
145
- $ cmssy test Run all block tests
146
- $ cmssy test --block hero Test specific block
147
- $ cmssy test --watch Watch mode
148
- $ cmssy test --coverage With coverage
467
+ // src/utils/registry.ts
468
+ import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
469
+ async function registerBlock(blocksFile, camel, type) {
470
+ let content = await readFile4(blocksFile, "utf8");
471
+ const token = `${camel}Block`;
472
+ const tokenRe = new RegExp(`\\b${token}\\b`);
473
+ const arrayRe = /export const blocks\s*=\s*\[([\s\S]*?)\]/;
474
+ const arrayMatch = arrayRe.exec(content);
475
+ if (!arrayMatch) {
476
+ throw new Error(
477
+ `Could not find \`export const blocks = [...]\` in ${blocksFile} - add ${token} manually.`
478
+ );
479
+ }
480
+ if (tokenRe.test(arrayMatch[1])) return false;
481
+ const lines = content.split("\n");
482
+ const alreadyImported = lines.some(
483
+ (line) => line.startsWith("import ") && tokenRe.test(line)
484
+ );
485
+ if (!alreadyImported) {
486
+ const importLine = `import { ${token} } from "@/blocks/${type}/block";`;
487
+ let lastImport = -1;
488
+ lines.forEach((line, i) => {
489
+ if (line.startsWith("import ")) lastImport = i;
490
+ });
491
+ lines.splice(lastImport + 1, 0, importLine);
492
+ content = lines.join("\n");
493
+ }
494
+ content = content.replace(arrayRe, (_match, inner) => {
495
+ const body = inner.replace(/\s+$/, "").replace(/,\s*$/, "");
496
+ const next = body.trim() ? `${body}, ${token}` : token;
497
+ return `export const blocks = [${next}]`;
498
+ });
499
+ await writeFile4(blocksFile, content, "utf8");
500
+ return true;
501
+ }
149
502
 
150
- Test files: blocks/*/src/**/*.{test,spec}.{ts,tsx}
151
- templates/*/src/**/*.{test,spec}.{ts,tsx}
152
- `)
153
- .action(testCommand);
154
- // cmssy link
155
- program
156
- .command("link")
157
- .description("Connect project to a Cmssy workspace")
158
- .option("--api-url <url>", "Cmssy API URL (default: existing or https://api.cmssy.io/graphql)")
159
- .option("--token <token>", "API token (skip interactive prompt)")
160
- .option("-w, --workspace <id>", "Workspace ID or slug (skip interactive picker)")
161
- .addHelpText("after", `
162
- Examples:
163
- $ cmssy link Interactive setup
164
- $ cmssy link --token cs_xxx --workspace abc123 Non-interactive (CI)
503
+ // src/commands/add-block.ts
504
+ async function addBlockCommand(args) {
505
+ const cwd = process.cwd();
506
+ const base = existsSync7(join8(cwd, "src", "cmssy", "blocks.ts")) ? join8(cwd, "src") : cwd;
507
+ const blocksFile = join8(base, "cmssy", "blocks.ts");
508
+ intro2(pc.bold("cmssy add block"));
509
+ if (!existsSync7(blocksFile)) {
510
+ log3.error("No cmssy/blocks.ts found - run `cmssy init` first.");
511
+ process.exitCode = 1;
512
+ return;
513
+ }
514
+ let input = args.positionals[0];
515
+ if (!input) {
516
+ const answer = await text2({
517
+ message: "Block name",
518
+ placeholder: "feature-grid",
519
+ validate: (v) => v?.trim() ? void 0 : "Required"
520
+ });
521
+ if (isCancel2(answer)) {
522
+ cancel2("Cancelled.");
523
+ process.exit(0);
524
+ }
525
+ input = answer.trim();
526
+ }
527
+ const names = blockNames(input);
528
+ const vars = {
529
+ type: names.type,
530
+ camel: names.camel,
531
+ Pascal: names.Pascal,
532
+ Label: names.Label
533
+ };
534
+ const dir = join8(base, "blocks", names.type);
535
+ const blockTs = renderTemplate(readTemplate("block", "block.ts.tpl"), vars);
536
+ const componentTsx = renderTemplate(
537
+ readTemplate("block", "Component.tsx.tpl"),
538
+ vars
539
+ );
540
+ const componentCss = renderTemplate(
541
+ readTemplate("block", "Component.module.css.tpl"),
542
+ vars
543
+ );
544
+ const results = [
545
+ await writeFileSafe(join8(dir, "block.ts"), blockTs),
546
+ await writeFileSafe(join8(dir, `${names.Pascal}.tsx`), componentTsx),
547
+ await writeFileSafe(join8(dir, `${names.Pascal}.module.css`), componentCss)
548
+ ];
549
+ if (results.includes("skipped")) {
550
+ log3.warn(
551
+ `Block "${names.type}" already exists - left existing files alone.`
552
+ );
553
+ } else if (results.includes("written")) {
554
+ log3.success(
555
+ `Created blocks/${names.type}/ (block.ts + ${names.Pascal}.tsx + ${names.Pascal}.module.css)`
556
+ );
557
+ } else {
558
+ log3.info(`Block "${names.type}" already up to date.`);
559
+ }
560
+ const registered = await registerBlock(blocksFile, names.camel, names.type);
561
+ log3.info(
562
+ registered ? `Registered ${names.camel}Block in cmssy/blocks.ts` : `${names.camel}Block already registered`
563
+ );
564
+ outro2(
565
+ `Added "${names.type}". Edit its component, then use it in the editor.`
566
+ );
567
+ }
165
568
 
166
- Saves credentials and workspace ID to .env file.
167
- Get your API token at: https://cmssy.io/settings/tokens
168
- `)
169
- .action(linkCommand);
170
- // cmssy configure (hidden - deprecated, use `cmssy link`)
171
- program.addCommand(new Command("configure")
172
- .description("Configure Cmssy API credentials (deprecated: use `cmssy link`)")
173
- .option("--api-url <url>", "Cmssy API URL")
174
- .action((options) => {
175
- console.log(chalk.yellow("\n⚠ `cmssy configure` is deprecated. Use `cmssy link` instead.\n"));
176
- return configureCommand(options);
177
- }), { hidden: true });
178
- // cmssy doctor
179
- program
180
- .command("doctor")
181
- .description("Check project setup, API connection, and block health")
182
- .addHelpText("after", `
183
- Runs diagnostic checks:
184
- Environment (Node.js, npm, next, react)
185
- Configuration (.env, API token, workspace ID)
186
- API connection and workspace access
187
- • Block structure validation
188
- Dependency versions
189
- `)
190
- .action(doctorCommand);
191
- program
192
- .command("publish-block")
193
- .description("Publish a single block via the build pipeline (Vercel Sandbox + Inngest).\n\n" +
194
- " Sends the block source tree to the backend, which packs it as tar.gz,\n" +
195
- " uploads to Vercel Blob, then enqueues an Inngest job that bundles in\n" +
196
- " a sandbox and stores artifacts back to Blob.")
197
- .argument("<name>", "Block directory name under blocks/")
198
- .option("-w, --workspace [id]", "Workspace id (defaults to .env)")
199
- .option("--entry <path>", "Entry path inside block dir (default: src/index.tsx)")
200
- .option("--dry-run", "Collect files and print plan without uploading")
201
- .addHelpText("after", `
202
- Examples:
203
- $ cmssy publish-block hero Publish blocks/hero
204
- $ cmssy publish-block hero --dry-run Show what would be sent
205
- $ cmssy publish-block faq -w 65f... Override workspace
569
+ // src/commands/doctor.ts
570
+ import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
571
+ import { join as join9 } from "path";
572
+ import { intro as intro3, outro as outro3 } from "@clack/prompts";
573
+ var MARK = {
574
+ pass: pc.green("\u2713"),
575
+ warn: pc.yellow("!"),
576
+ fail: pc.red("\u2717")
577
+ };
578
+ async function doctorCommand(_args) {
579
+ const cwd = process.cwd();
580
+ const base = existsSync8(join9(cwd, "src", "cmssy", "blocks.ts")) ? join9(cwd, "src") : cwd;
581
+ intro3(pc.bold("cmssy doctor"));
582
+ const checks = [];
583
+ const required = [
584
+ ["cmssy.config.ts", join9(cwd, "cmssy.config.ts")],
585
+ ["proxy.ts", join9(cwd, "proxy.ts")],
586
+ ["app/[[...path]]/page.tsx", join9(base, "app", "[[...path]]", "page.tsx")],
587
+ ["app/api/draft/route.ts", join9(base, "app", "api", "draft", "route.ts")],
588
+ ["cmssy/blocks.ts", join9(base, "cmssy", "blocks.ts")],
589
+ ["cmssy/editor.tsx", join9(base, "cmssy", "editor.tsx")]
590
+ ];
591
+ for (const [label, p] of required) {
592
+ checks.push(
593
+ existsSync8(p) ? { label, status: "pass" } : { label, status: "fail", hint: "missing - run `cmssy init`" }
594
+ );
595
+ }
596
+ const pkg = readPackageJson(cwd);
597
+ const next = pkg?.dependencies?.["@cmssy/next"] ?? pkg?.devDependencies?.["@cmssy/next"];
598
+ const react = pkg?.dependencies?.["@cmssy/react"] ?? pkg?.devDependencies?.["@cmssy/react"];
599
+ if (!next || !react) {
600
+ checks.push({
601
+ label: "@cmssy/next + @cmssy/react installed",
602
+ status: "fail",
603
+ hint: "missing - add @cmssy/next and @cmssy/react"
604
+ });
605
+ } else if (next !== react) {
606
+ checks.push({
607
+ label: "@cmssy/* versions aligned",
608
+ status: "warn",
609
+ hint: `@cmssy/next ${next} vs @cmssy/react ${react}`
610
+ });
611
+ } else {
612
+ checks.push({ label: `@cmssy/* ${next}`, status: "pass" });
613
+ }
614
+ const envPath = join9(cwd, ".env");
615
+ const env = existsSync8(envPath) ? readFileSync5(envPath, "utf8") : "";
616
+ for (const key of ["CMSSY_WORKSPACE_SLUG", "CMSSY_DRAFT_SECRET"]) {
617
+ const set = new RegExp(`^${key}=.+$`, "m").test(env);
618
+ checks.push(
619
+ set ? { label: key, status: "pass" } : {
620
+ label: key,
621
+ status: "warn",
622
+ hint: "not set in .env - run `cmssy link`"
623
+ }
624
+ );
625
+ }
626
+ const blocksFile = join9(base, "cmssy", "blocks.ts");
627
+ if (existsSync8(blocksFile)) {
628
+ const content = readFileSync5(blocksFile, "utf8");
629
+ const refs = [...content.matchAll(/@\/blocks\/([^/]+)\/block/g)].map(
630
+ (m) => m[1]
631
+ );
632
+ const missing = refs.filter(
633
+ (name) => !existsSync8(join9(base, "blocks", name, "block.ts"))
634
+ );
635
+ checks.push(
636
+ missing.length === 0 ? { label: `block registry (${refs.length} block(s))`, status: "pass" } : {
637
+ label: "block registry imports resolve",
638
+ status: "fail",
639
+ hint: `missing block file(s): ${missing.join(", ")}`
640
+ }
641
+ );
642
+ }
643
+ for (const c of checks) {
644
+ console.log(
645
+ ` ${MARK[c.status]} ${c.label}${c.hint ? pc.dim(` - ${c.hint}`) : ""}`
646
+ );
647
+ }
648
+ const failed = checks.filter((c) => c.status === "fail").length;
649
+ const warned = checks.filter((c) => c.status === "warn").length;
650
+ if (failed) {
651
+ process.exitCode = 1;
652
+ outro3(pc.red(`${failed} problem(s), ${warned} warning(s).`));
653
+ } else {
654
+ outro3(
655
+ warned ? pc.yellow(`OK with ${warned} warning(s).`) : pc.green("All good.")
656
+ );
657
+ }
658
+ }
206
659
 
207
- Limits per block: 200 files, 10 MB total.
208
- Build polling: 1.5s interval, 10 minute cap, gives up after 5 consecutive errors.
209
- `)
210
- .action((name, options) => publishBlockBuildtimeCommand(name, options));
211
- // cmssy publish-template
212
- program
213
- .command("publish-template")
214
- .description("Publish a single template (page tree + content) to a workspace.\n\n" +
215
- " Templates are declarative - no sandbox build. Reads config.ts +\n" +
216
- " pages.json and uploads via GraphQL. The backend revalidates the\n" +
217
- " public-site cache so changes appear immediately.")
218
- .argument("<name>", "Template directory name under templates/")
219
- .option("-w, --workspace [id]", "Workspace id (defaults to .env)")
220
- .option("--patch", "Bump patch version (1.0.0 -> 1.0.1) - default")
221
- .option("--minor", "Bump minor version (1.0.0 -> 1.1.0)")
222
- .option("--major", "Bump major version (1.0.0 -> 2.0.0)")
223
- .option("--no-bump", "Publish without version bump")
224
- .option("--dry-run", "Print plan without uploading")
225
- .option("--overwrite-content", "Overwrite existing page content on republish (default: preserve)")
226
- .addHelpText("after", `
227
- Examples:
228
- $ cmssy publish-template marketing-site Publish templates/marketing-site
229
- $ cmssy publish-template marketing-site --dry-run Show the plan
230
- $ cmssy publish-template blog -w 65f... --minor Override workspace + minor bump
660
+ // src/cli.ts
661
+ var HELP = `
662
+ ${pc.bold("cmssy")} - wire a Next.js app to a headless cmssy workspace
231
663
 
232
- Required blocks listed in the template must already exist in the workspace
233
- (publish them via \`cmssy publish-block\` first).
234
- `)
235
- .action((name, options) => publishTemplateCommand(name, options));
236
- const lib = program
237
- .command("lib")
238
- .description("Manage the workspace's npm dependency manifest");
239
- lib
240
- .command("install")
241
- .description("Install npm package(s) locally and sync the manifest")
242
- .argument("<packages...>", "npm package specs (e.g. lodash zod@^4)")
243
- .option("-w, --workspace [id]", "Workspace id (defaults to .env)")
244
- .addOption(new Option("--package-manager <pm>", "Force the package manager").choices([
245
- "npm",
246
- "pnpm",
247
- "yarn",
248
- "bun",
249
- ]))
250
- .option("--skip-install", "Skip local install; only sync from package.json")
251
- .option("--dry-run", "Don't push manifest, just print it")
252
- .action((packages, options) => libInstallCommand(packages, options));
253
- lib
254
- .command("sync")
255
- .description("Push current package.json deps to the workspace manifest")
256
- .option("-w, --workspace [id]", "Workspace id (defaults to .env)")
257
- .option("--dry-run", "Don't push manifest, just print it")
258
- .action((options) => libSyncCommand(options));
259
- // cmssy sync
260
- program
261
- .command("sync")
262
- .description("Pull blocks from design library to local project")
263
- .argument("[package]", "Package slug (e.g., @cmssy/blocks.hero)")
264
- .option("--workspace <id>", "Sync from specific workspace")
265
- .addHelpText("after", `
266
- Examples:
267
- $ cmssy sync @cmssy/blocks.hero Sync from design library
268
- $ cmssy sync --workspace abc123 Sync all from workspace
269
- `)
270
- .action(syncCommand);
271
- // cmssy migrate (hidden - legacy command, all projects already migrated)
272
- program.addCommand(new Command("migrate")
273
- .argument("[block-name]")
274
- .description("Migrate legacy package.json config to config.ts")
275
- .addHelpText("after", `
276
- Examples:
277
- $ cmssy migrate hero Migrate specific block
278
- $ cmssy migrate Migrate all blocks/templates
664
+ ${pc.bold("Usage")}
665
+ cmssy <command> [options]
279
666
 
280
- Converts:
281
- package.json { cmssy: {...} } → config.ts
282
- `)
283
- .action(migrateCommand), { hidden: true });
284
- // cmssy workspaces
285
- program
286
- .command("workspaces")
287
- .description("List your workspaces and their IDs")
288
- .addHelpText("after", `
289
- Use workspace IDs with:
290
- $ cmssy publish-block <name> --workspace <id>
291
- $ cmssy publish-template <name> --workspace <id>
292
- $ cmssy sync --workspace <id>
293
- `)
294
- .action(workspacesCommand);
295
- // cmssy skills
296
- const skills = program
297
- .command("skills")
298
- .description("Manage AI coding-assistant skills for cmssy");
299
- skills
300
- .command("list")
301
- .description("List available skills")
302
- .action(skillsListCommand);
303
- skills
304
- .command("install")
305
- .description("Install a skill into your AI assistant's config directory")
306
- .argument("[skill]", "Skill name: block, mcp-content (omit for interactive)")
307
- .option("--target <editor>", "Editor target: claude (default)", "claude")
308
- .option("--all", "Install every available skill")
309
- .option("--local", "Install into ./.claude/skills (default: ~/.claude/skills)")
310
- .option("--force", "Overwrite existing skill without prompting")
311
- .option("-y, --yes", "Non-interactive mode (fail instead of prompting)")
312
- .addHelpText("after", `
313
- Examples:
314
- $ cmssy skills list Show available skills
315
- $ cmssy skills install Interactive prompt (pick a skill)
316
- $ cmssy skills install block CLI + block dev workflow skill
317
- $ cmssy skills install mcp-content Content editing via @cmssy/mcp-server
318
- $ cmssy skills install --all Install every skill
319
- $ cmssy skills install block --local Install into current project's .claude/
320
- $ cmssy skills install block --force Overwrite existing skill
667
+ ${pc.bold("Commands")}
668
+ init Add cmssy wiring to an existing Next.js App Router app
669
+ link Connect an initialized project to a workspace
670
+ add block <name> Scaffold a new block and register it
671
+ doctor Diagnose a cmssy project's setup
321
672
 
322
- Changed in 0.14.0: the first positional arg is now the skill name (e.g.
323
- 'block', 'mcp-content'), not the editor target. Use --target for editor.
324
- `)
325
- .action((skillName, options) => skillsInstallCommand(skillName, options));
326
- // cmssy codegen (hidden - use `cmssy init --graphql` for setup)
327
- program.addCommand(new Command("codegen")
328
- .description("Generate TypeScript types from workspace public GraphQL schema")
329
- .option("-w, --workspace <slug>", "Workspace slug")
330
- .option("-o, --output <path>", "Output file path", "src/graphql/types.ts")
331
- .option("--init", "Generate codegen.ts config file instead of running codegen")
332
- .addHelpText("after", `
333
- Examples:
334
- $ cmssy codegen --workspace my-workspace
335
- $ cmssy codegen --init
336
- $ cmssy codegen -o src/types/api.ts
337
- `)
338
- .action(codegenCommand), { hidden: true });
339
- program.parse();
340
- //# sourceMappingURL=cli.js.map
673
+ ${pc.bold("Options")}
674
+ -h, --help Show this help
675
+ -v, --version Show version
676
+ `;
677
+ async function main() {
678
+ const { positionals, flags } = parseArgs(process.argv.slice(2));
679
+ const command = positionals[0];
680
+ if (flags.version || flags.v) {
681
+ console.log(getVersion());
682
+ return;
683
+ }
684
+ if (!command || flags.help || flags.h) {
685
+ console.log(HELP);
686
+ return;
687
+ }
688
+ const rest = { positionals: positionals.slice(1), flags };
689
+ switch (command) {
690
+ case "init":
691
+ await initCommand(rest);
692
+ break;
693
+ case "link":
694
+ await linkCommand(rest);
695
+ break;
696
+ case "add":
697
+ if (positionals[1] === "block") {
698
+ await addBlockCommand({ positionals: positionals.slice(2), flags });
699
+ } else {
700
+ ui.error(
701
+ `Unknown add target: ${pc.bold(positionals[1] ?? "")}. Try ${pc.bold("cmssy add block <name>")}.`
702
+ );
703
+ process.exitCode = 1;
704
+ }
705
+ break;
706
+ case "doctor":
707
+ await doctorCommand(rest);
708
+ break;
709
+ default:
710
+ ui.error(`Unknown command: ${pc.bold(command)}`);
711
+ console.log(HELP);
712
+ process.exitCode = 1;
713
+ }
714
+ }
715
+ main().catch((err) => {
716
+ ui.error(err instanceof Error ? err.message : String(err));
717
+ process.exitCode = 1;
718
+ });