@cmssy/cli 1.0.0 → 9.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js DELETED
@@ -1,718 +0,0 @@
1
- #!/usr/bin/env node
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";
35
- import { dirname, join } from "path";
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
- }
379
-
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
- }
440
-
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";
445
-
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
- }
466
-
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
- }
502
-
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
- }
568
-
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
- }
659
-
660
- // src/cli.ts
661
- var HELP = `
662
- ${pc.bold("cmssy")} - wire a Next.js app to a headless cmssy workspace
663
-
664
- ${pc.bold("Usage")}
665
- cmssy <command> [options]
666
-
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
672
-
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
- });