@cmssy/cli 9.8.0 → 9.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +368 -42
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -3,16 +3,9 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { createInterface } from "readline/promises";
|
|
5
5
|
|
|
6
|
-
// src/
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
existsSync,
|
|
10
|
-
mkdirSync,
|
|
11
|
-
readFileSync,
|
|
12
|
-
writeFileSync
|
|
13
|
-
} from "fs";
|
|
14
|
-
import { dirname, join, resolve } from "path";
|
|
15
|
-
import { fileURLToPath } from "url";
|
|
6
|
+
// src/add-block.ts
|
|
7
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
8
|
+
import { dirname, join as join2, resolve } from "path";
|
|
16
9
|
|
|
17
10
|
// src/admin-client.ts
|
|
18
11
|
var DEFAULT_ADMIN_API_URL = "https://api.cmssy.io/graphql";
|
|
@@ -159,11 +152,9 @@ Preview drafts without the editor (the /api/draft path also works on your local
|
|
|
159
152
|
`;
|
|
160
153
|
}
|
|
161
154
|
|
|
162
|
-
// src/
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
new URL("../package.json", import.meta.url)
|
|
166
|
-
);
|
|
155
|
+
// src/framework.ts
|
|
156
|
+
import { existsSync, readFileSync } from "fs";
|
|
157
|
+
import { join } from "path";
|
|
167
158
|
var FRAMEWORKS = [
|
|
168
159
|
{
|
|
169
160
|
name: "next",
|
|
@@ -253,8 +244,272 @@ function detectFramework(pkg) {
|
|
|
253
244
|
}
|
|
254
245
|
return match;
|
|
255
246
|
}
|
|
247
|
+
function nextSrcPrefix(root) {
|
|
248
|
+
return existsSync(join(root, "src", "app")) ? "src/" : "";
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// src/add-block.ts
|
|
252
|
+
var BLOCK_NAME_PATTERN = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
253
|
+
function capitalize(word) {
|
|
254
|
+
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
255
|
+
}
|
|
256
|
+
function blockNames(name) {
|
|
257
|
+
const words = name.split("-");
|
|
258
|
+
const pascal = words.map(capitalize).join("");
|
|
259
|
+
const camel = pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
260
|
+
return {
|
|
261
|
+
type: name,
|
|
262
|
+
label: words.map(capitalize).join(" "),
|
|
263
|
+
pascal,
|
|
264
|
+
camel,
|
|
265
|
+
block: `${camel}Block`,
|
|
266
|
+
props: `${camel}Props`
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
function nextBlockDefinition(names) {
|
|
270
|
+
return `import { defineBlock } from "@cmssy/react";
|
|
271
|
+
import ${names.pascal}, { ${names.props} } from "./${names.pascal}";
|
|
272
|
+
|
|
273
|
+
export const ${names.block} = defineBlock({
|
|
274
|
+
type: "${names.type}",
|
|
275
|
+
label: "${names.label}",
|
|
276
|
+
component: ${names.pascal},
|
|
277
|
+
props: ${names.props},
|
|
278
|
+
});
|
|
279
|
+
`;
|
|
280
|
+
}
|
|
281
|
+
function nextComponent(names) {
|
|
282
|
+
return `import { fields, type BlockProps } from "@cmssy/react";
|
|
283
|
+
|
|
284
|
+
export const ${names.props} = {
|
|
285
|
+
heading: fields.text({ label: "Heading", required: true }),
|
|
286
|
+
text: fields.textarea({ label: "Text" }),
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
export default function ${names.pascal}({ content }: BlockProps<typeof ${names.props}>) {
|
|
290
|
+
return (
|
|
291
|
+
<section>
|
|
292
|
+
<h2>{content.heading}</h2>
|
|
293
|
+
{content.text ? <p>{content.text}</p> : null}
|
|
294
|
+
</section>
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
`;
|
|
298
|
+
}
|
|
299
|
+
function singleFileComponent(names) {
|
|
300
|
+
return `import { fields, type BlockProps } from "@cmssy/react";
|
|
301
|
+
|
|
302
|
+
export const ${names.props} = {
|
|
303
|
+
heading: fields.text({ label: "Heading", required: true }),
|
|
304
|
+
text: fields.textarea({ label: "Text" }),
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
export function ${names.pascal}({ content }: BlockProps<typeof ${names.props}>) {
|
|
308
|
+
return (
|
|
309
|
+
<section>
|
|
310
|
+
<h2>{content.heading}</h2>
|
|
311
|
+
{content.text ? <p>{content.text}</p> : null}
|
|
312
|
+
</section>
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
`;
|
|
316
|
+
}
|
|
317
|
+
function insertAfterImports(source, line) {
|
|
318
|
+
const imports = [...source.matchAll(/^import .*$/gm)];
|
|
319
|
+
const last = imports[imports.length - 1];
|
|
320
|
+
if (last === void 0) return `${line}
|
|
321
|
+
${source}`;
|
|
322
|
+
const end = (last.index ?? 0) + last[0].length;
|
|
323
|
+
return `${source.slice(0, end)}
|
|
324
|
+
${line}${source.slice(end)}`;
|
|
325
|
+
}
|
|
326
|
+
function appendToBlocksArray(source, identifier) {
|
|
327
|
+
const match = source.match(/export const blocks = \[([\s\S]*?)\]/);
|
|
328
|
+
if (!match) {
|
|
329
|
+
throw new CliError(
|
|
330
|
+
"could not find `export const blocks = [...]` in the block registry",
|
|
331
|
+
`add the block yourself: import it and append ${identifier} to the blocks array`
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
const inner = match[1] ?? "";
|
|
335
|
+
let updated;
|
|
336
|
+
if (inner.trim() === "") {
|
|
337
|
+
updated = identifier;
|
|
338
|
+
} else if (inner.includes("\n")) {
|
|
339
|
+
const body = inner.replace(/[\s,]*$/, "");
|
|
340
|
+
const indent = /\n(\s*)\S/.exec(inner)?.[1] ?? " ";
|
|
341
|
+
updated = `${body},
|
|
342
|
+
${indent}${identifier},
|
|
343
|
+
`;
|
|
344
|
+
} else {
|
|
345
|
+
updated = `${inner.trim().replace(/,$/, "")}, ${identifier}`;
|
|
346
|
+
}
|
|
347
|
+
return source.replace(match[0], `export const blocks = [${updated}]`);
|
|
348
|
+
}
|
|
349
|
+
function frameworkLayout(framework, root, names) {
|
|
350
|
+
if (framework.name === "next") {
|
|
351
|
+
const prefix = nextSrcPrefix(root);
|
|
352
|
+
const componentPath2 = `${prefix}blocks/${names.type}/${names.pascal}.tsx`;
|
|
353
|
+
return {
|
|
354
|
+
registryPath: `${prefix}cmssy/blocks.ts`,
|
|
355
|
+
componentPath: componentPath2,
|
|
356
|
+
files: [
|
|
357
|
+
{
|
|
358
|
+
path: `${prefix}blocks/${names.type}/block.ts`,
|
|
359
|
+
content: nextBlockDefinition(names)
|
|
360
|
+
},
|
|
361
|
+
{ path: componentPath2, content: nextComponent(names) }
|
|
362
|
+
],
|
|
363
|
+
registerInRegistry: (source) => appendToBlocksArray(
|
|
364
|
+
insertAfterImports(
|
|
365
|
+
source,
|
|
366
|
+
`import { ${names.block} } from "@/blocks/${names.type}/block";`
|
|
367
|
+
),
|
|
368
|
+
names.block
|
|
369
|
+
)
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
const base = framework.name === "astro" ? "src/cmssy" : "app/cmssy";
|
|
373
|
+
const componentPath = `${base}/${names.type}.tsx`;
|
|
374
|
+
return {
|
|
375
|
+
registryPath: `${base}/blocks.ts`,
|
|
376
|
+
componentPath,
|
|
377
|
+
files: [{ path: componentPath, content: singleFileComponent(names) }],
|
|
378
|
+
registerInRegistry: (source) => {
|
|
379
|
+
let updated = insertAfterImports(
|
|
380
|
+
source,
|
|
381
|
+
`import { ${names.pascal}, ${names.props} } from "./${names.type}";`
|
|
382
|
+
);
|
|
383
|
+
if (!/import \{[^}]*defineBlock[^}]*\} from "@cmssy\/react"/.test(updated)) {
|
|
384
|
+
updated = insertAfterImports(
|
|
385
|
+
updated,
|
|
386
|
+
`import { defineBlock } from "@cmssy/react";`
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
const definition = `export const ${names.block} = defineBlock({
|
|
390
|
+
type: "${names.type}",
|
|
391
|
+
label: "${names.label}",
|
|
392
|
+
component: ${names.pascal},
|
|
393
|
+
props: ${names.props},
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
`;
|
|
397
|
+
const anchor = updated.indexOf("export const blocks =");
|
|
398
|
+
if (anchor === -1) {
|
|
399
|
+
throw new CliError(
|
|
400
|
+
"could not find `export const blocks = [...]` in the block registry",
|
|
401
|
+
`add the block yourself: define ${names.block} with defineBlock and append it to the blocks array`
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
updated = `${updated.slice(0, anchor)}${definition}${updated.slice(anchor)}`;
|
|
405
|
+
return appendToBlocksArray(updated, names.block);
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
function runAddBlock(options, deps) {
|
|
410
|
+
const { log } = deps;
|
|
411
|
+
try {
|
|
412
|
+
if (!BLOCK_NAME_PATTERN.test(options.name)) {
|
|
413
|
+
throw new CliError(
|
|
414
|
+
`"${options.name}" is not a valid block name`,
|
|
415
|
+
"use kebab-case: cmssy add block pricing-table"
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
const root = resolve(deps.cwd, options.dir ?? ".");
|
|
419
|
+
if (!existsSync2(root)) {
|
|
420
|
+
throw new CliError(
|
|
421
|
+
`${root} does not exist`,
|
|
422
|
+
"pass --dir with the app's directory, or run cmssy add block inside it"
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
const framework = detectFramework(readPackageJson(root));
|
|
426
|
+
const names = blockNames(options.name);
|
|
427
|
+
const layout = frameworkLayout(framework, root, names);
|
|
428
|
+
const registryFile = join2(root, layout.registryPath);
|
|
429
|
+
if (!existsSync2(registryFile)) {
|
|
430
|
+
throw new CliError(
|
|
431
|
+
`no block registry at ${layout.registryPath}`,
|
|
432
|
+
"run cmssy init first - it wires the registry the editor loads blocks from"
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
const registry = readFileSync2(registryFile, "utf8");
|
|
436
|
+
if (registry.includes(names.block) || registry.includes(`type: "${names.type}"`)) {
|
|
437
|
+
throw new CliError(
|
|
438
|
+
`block "${names.type}" is already registered in ${layout.registryPath}`
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
for (const file of layout.files) {
|
|
442
|
+
if (existsSync2(join2(root, file.path))) {
|
|
443
|
+
throw new CliError(
|
|
444
|
+
`${file.path} already exists`,
|
|
445
|
+
"pick another name or remove the existing block first"
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
const updatedRegistry = layout.registerInRegistry(registry, names);
|
|
450
|
+
log(
|
|
451
|
+
formatResult({
|
|
452
|
+
status: "ok",
|
|
453
|
+
message: `detected ${framework.label} - scaffolding block "${names.type}"`
|
|
454
|
+
})
|
|
455
|
+
);
|
|
456
|
+
for (const file of layout.files) {
|
|
457
|
+
const target = join2(root, file.path);
|
|
458
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
459
|
+
writeFileSync(target, file.content);
|
|
460
|
+
log(formatResult({ status: "ok", message: `wrote ${file.path}` }));
|
|
461
|
+
}
|
|
462
|
+
writeFileSync(registryFile, updatedRegistry);
|
|
463
|
+
log(
|
|
464
|
+
formatResult({
|
|
465
|
+
status: "ok",
|
|
466
|
+
message: `registered ${names.block} in ${layout.registryPath}`
|
|
467
|
+
})
|
|
468
|
+
);
|
|
469
|
+
log("");
|
|
470
|
+
log("Next steps:");
|
|
471
|
+
log(` 1. edit ${layout.componentPath} - define the fields and markup`);
|
|
472
|
+
log(
|
|
473
|
+
` 2. restart your dev server - the editor picks up "${names.type}" from the next handshake`
|
|
474
|
+
);
|
|
475
|
+
return 0;
|
|
476
|
+
} catch (error) {
|
|
477
|
+
if (error instanceof CliError) {
|
|
478
|
+
log(
|
|
479
|
+
formatResult({
|
|
480
|
+
status: "fail",
|
|
481
|
+
message: error.message,
|
|
482
|
+
fix: error.fix
|
|
483
|
+
})
|
|
484
|
+
);
|
|
485
|
+
return 1;
|
|
486
|
+
}
|
|
487
|
+
log(
|
|
488
|
+
formatResult({
|
|
489
|
+
status: "fail",
|
|
490
|
+
message: error instanceof Error ? error.message : String(error)
|
|
491
|
+
})
|
|
492
|
+
);
|
|
493
|
+
return 1;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// src/init.ts
|
|
498
|
+
import {
|
|
499
|
+
copyFileSync,
|
|
500
|
+
existsSync as existsSync3,
|
|
501
|
+
mkdirSync as mkdirSync2,
|
|
502
|
+
readFileSync as readFileSync3,
|
|
503
|
+
writeFileSync as writeFileSync2
|
|
504
|
+
} from "fs";
|
|
505
|
+
import { dirname as dirname2, join as join3, resolve as resolve2 } from "path";
|
|
506
|
+
import { fileURLToPath } from "url";
|
|
507
|
+
var ASSETS_DIR = fileURLToPath(new URL("../assets/init", import.meta.url));
|
|
508
|
+
var CLI_PACKAGE_JSON = fileURLToPath(
|
|
509
|
+
new URL("../package.json", import.meta.url)
|
|
510
|
+
);
|
|
256
511
|
function frameworkFiles(framework, root) {
|
|
257
|
-
const srcPrefix = framework.name === "next"
|
|
512
|
+
const srcPrefix = framework.name === "next" ? nextSrcPrefix(root) : "";
|
|
258
513
|
return [
|
|
259
514
|
{ asset: "env.example", target: ".env.example" },
|
|
260
515
|
...framework.files.map((path) => ({
|
|
@@ -264,7 +519,7 @@ function frameworkFiles(framework, root) {
|
|
|
264
519
|
];
|
|
265
520
|
}
|
|
266
521
|
function cliVersion() {
|
|
267
|
-
const pkg = JSON.parse(
|
|
522
|
+
const pkg = JSON.parse(readFileSync3(CLI_PACKAGE_JSON, "utf8"));
|
|
268
523
|
return pkg.version;
|
|
269
524
|
}
|
|
270
525
|
function addDependencies(root, pkg, framework) {
|
|
@@ -279,19 +534,19 @@ function addDependencies(root, pkg, framework) {
|
|
|
279
534
|
pkg.dependencies = Object.fromEntries(
|
|
280
535
|
Object.entries(dependencies).sort(([a], [b]) => a.localeCompare(b))
|
|
281
536
|
);
|
|
282
|
-
|
|
283
|
-
|
|
537
|
+
writeFileSync2(
|
|
538
|
+
join3(root, "package.json"),
|
|
284
539
|
`${JSON.stringify(pkg, null, 2)}
|
|
285
540
|
`
|
|
286
541
|
);
|
|
287
542
|
return missing.map((name) => `${name}@${range}`);
|
|
288
543
|
}
|
|
289
544
|
function detectInstallCommand(root) {
|
|
290
|
-
if (
|
|
545
|
+
if (existsSync3(join3(root, "pnpm-lock.yaml")) || existsSync3(join3(root, "pnpm-workspace.yaml"))) {
|
|
291
546
|
return "pnpm install";
|
|
292
547
|
}
|
|
293
|
-
if (
|
|
294
|
-
if (
|
|
548
|
+
if (existsSync3(join3(root, "yarn.lock"))) return "yarn";
|
|
549
|
+
if (existsSync3(join3(root, "bun.lock")) || existsSync3(join3(root, "bun.lockb"))) {
|
|
295
550
|
return "bun install";
|
|
296
551
|
}
|
|
297
552
|
return "npm install";
|
|
@@ -299,9 +554,8 @@ function detectInstallCommand(root) {
|
|
|
299
554
|
function frameworkNotes(framework, root, skipped) {
|
|
300
555
|
const notes = [];
|
|
301
556
|
if (framework.name === "next") {
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
if (existsSync(join(root, home))) {
|
|
557
|
+
const home = `${nextSrcPrefix(root)}app/page.tsx`;
|
|
558
|
+
if (existsSync3(join3(root, home))) {
|
|
305
559
|
notes.push({
|
|
306
560
|
status: "unknown",
|
|
307
561
|
message: `${home} conflicts with the cmssy catch-all route - delete it and the cmssy page serves /`
|
|
@@ -313,7 +567,7 @@ function frameworkNotes(framework, root, skipped) {
|
|
|
313
567
|
status: "unknown",
|
|
314
568
|
message: "the cmssy wiring needs the React integration and a server adapter - run: npx astro add react node"
|
|
315
569
|
});
|
|
316
|
-
if (
|
|
570
|
+
if (existsSync3(join3(root, "src/pages/index.astro"))) {
|
|
317
571
|
notes.push({
|
|
318
572
|
status: "unknown",
|
|
319
573
|
message: "src/pages/index.astro shadows the cmssy catch-all for / - delete it and the cmssy page serves /"
|
|
@@ -331,8 +585,8 @@ function frameworkNotes(framework, root, skipped) {
|
|
|
331
585
|
function runInit(options, deps) {
|
|
332
586
|
const { log } = deps;
|
|
333
587
|
try {
|
|
334
|
-
const root =
|
|
335
|
-
if (!
|
|
588
|
+
const root = resolve2(deps.cwd, options.dir ?? ".");
|
|
589
|
+
if (!existsSync3(root)) {
|
|
336
590
|
throw new CliError(
|
|
337
591
|
`${root} does not exist`,
|
|
338
592
|
"pass --dir with the app's directory, or run cmssy init inside it"
|
|
@@ -349,8 +603,8 @@ function runInit(options, deps) {
|
|
|
349
603
|
const written = [];
|
|
350
604
|
const skipped = [];
|
|
351
605
|
for (const file of frameworkFiles(framework, root)) {
|
|
352
|
-
const target =
|
|
353
|
-
if (
|
|
606
|
+
const target = join3(root, file.target);
|
|
607
|
+
if (existsSync3(target) && !options.force) {
|
|
354
608
|
skipped.push(file.target);
|
|
355
609
|
log(
|
|
356
610
|
formatResult({
|
|
@@ -360,8 +614,8 @@ function runInit(options, deps) {
|
|
|
360
614
|
);
|
|
361
615
|
continue;
|
|
362
616
|
}
|
|
363
|
-
|
|
364
|
-
copyFileSync(
|
|
617
|
+
mkdirSync2(dirname2(target), { recursive: true });
|
|
618
|
+
copyFileSync(join3(ASSETS_DIR, framework.name, file.asset), target);
|
|
365
619
|
written.push(file.target);
|
|
366
620
|
log(formatResult({ status: "ok", message: `wrote ${file.target}` }));
|
|
367
621
|
}
|
|
@@ -413,8 +667,8 @@ function runInit(options, deps) {
|
|
|
413
667
|
}
|
|
414
668
|
|
|
415
669
|
// src/link.ts
|
|
416
|
-
import { existsSync as
|
|
417
|
-
import { join as
|
|
670
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
671
|
+
import { join as join4 } from "path";
|
|
418
672
|
import {
|
|
419
673
|
buildEditorUrl,
|
|
420
674
|
checkDraftSecret,
|
|
@@ -484,9 +738,9 @@ function mergeEnvContent(existing, updates) {
|
|
|
484
738
|
var ENV_FILES = [".env.local", ".env"];
|
|
485
739
|
function loadEnvFiles(cwd, env) {
|
|
486
740
|
for (const file of ENV_FILES) {
|
|
487
|
-
const path =
|
|
488
|
-
if (!
|
|
489
|
-
applyEnv(parseEnvFile(
|
|
741
|
+
const path = join4(cwd, file);
|
|
742
|
+
if (!existsSync4(path)) continue;
|
|
743
|
+
applyEnv(parseEnvFile(readFileSync4(path, "utf8")), env);
|
|
490
744
|
}
|
|
491
745
|
}
|
|
492
746
|
function resolveToken(options, deps) {
|
|
@@ -521,6 +775,54 @@ function buildDraftPreviewUrls(previewUrl, draftSecret) {
|
|
|
521
775
|
exitUrl: `${base}?disable=1`
|
|
522
776
|
};
|
|
523
777
|
}
|
|
778
|
+
function draftRouteFix(cwd) {
|
|
779
|
+
const path = `${nextSrcPrefix(cwd)}app/api/draft/route.ts`;
|
|
780
|
+
return [
|
|
781
|
+
`add ${path} so Preview can enter draft mode:`,
|
|
782
|
+
' import { createDraftRoute } from "@cmssy/next/server";',
|
|
783
|
+
' import { cmssy } from "@/cmssy.config";',
|
|
784
|
+
" export const GET = createDraftRoute(cmssy);"
|
|
785
|
+
].join("\n");
|
|
786
|
+
}
|
|
787
|
+
async function checkDraftRouteMounted(previewUrl, fetchImpl, cwd) {
|
|
788
|
+
const base = draftRouteBase(previewUrl);
|
|
789
|
+
if (!base) {
|
|
790
|
+
return {
|
|
791
|
+
status: "fail",
|
|
792
|
+
message: `the workspace preview URL ${previewUrl} is not a valid URL`,
|
|
793
|
+
fix: "set a valid deployed origin as the preview URL: cmssy link --preview-url https://your-site.com"
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
let status;
|
|
797
|
+
try {
|
|
798
|
+
status = (await fetchImpl(base, {
|
|
799
|
+
method: "HEAD",
|
|
800
|
+
signal: AbortSignal.timeout(5e3)
|
|
801
|
+
})).status;
|
|
802
|
+
} catch {
|
|
803
|
+
return {
|
|
804
|
+
status: "unknown",
|
|
805
|
+
message: `could not reach ${base} to check the /api/draft route`
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
if (status === 404) {
|
|
809
|
+
return {
|
|
810
|
+
status: "fail",
|
|
811
|
+
message: `the /api/draft route is not mounted at ${previewUrl} - Preview will 404`,
|
|
812
|
+
fix: draftRouteFix(cwd)
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
if (status === 500) {
|
|
816
|
+
return {
|
|
817
|
+
status: "fail",
|
|
818
|
+
message: `the /api/draft route is mounted but misconfigured at ${previewUrl} - the draft secret must be at least 16 characters`
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
return {
|
|
822
|
+
status: "ok",
|
|
823
|
+
message: `the /api/draft route is mounted at ${previewUrl}`
|
|
824
|
+
};
|
|
825
|
+
}
|
|
524
826
|
async function selectWorkspace(workspaces, options, deps) {
|
|
525
827
|
if (workspaces.length === 0) {
|
|
526
828
|
throw new CliError(
|
|
@@ -587,9 +889,9 @@ function resolvePreviewUrl(options) {
|
|
|
587
889
|
return origin;
|
|
588
890
|
}
|
|
589
891
|
function writeEnvLocal(cwd, updates) {
|
|
590
|
-
const path =
|
|
591
|
-
const existing =
|
|
592
|
-
|
|
892
|
+
const path = join4(cwd, ".env.local");
|
|
893
|
+
const existing = existsSync4(path) ? readFileSync4(path, "utf8") : null;
|
|
894
|
+
writeFileSync3(path, mergeEnvContent(existing, updates));
|
|
593
895
|
}
|
|
594
896
|
async function runLink(options, deps) {
|
|
595
897
|
const { cwd, env, log } = deps;
|
|
@@ -660,8 +962,17 @@ async function runLink(options, deps) {
|
|
|
660
962
|
log(formatResult(reachable));
|
|
661
963
|
const secretResult = await checkDraftSecret(preflight);
|
|
662
964
|
log(formatResult(secretResult));
|
|
965
|
+
let draftRouteResult = null;
|
|
966
|
+
if (reachable.previewUrl) {
|
|
967
|
+
draftRouteResult = await checkDraftRouteMounted(
|
|
968
|
+
reachable.previewUrl,
|
|
969
|
+
deps.fetch,
|
|
970
|
+
cwd
|
|
971
|
+
);
|
|
972
|
+
log(formatResult(draftRouteResult));
|
|
973
|
+
}
|
|
663
974
|
log(formatEditorLink(buildEditorUrl(preflight)));
|
|
664
|
-
if (reachable.previewUrl && secretResult.status !== "fail") {
|
|
975
|
+
if (reachable.previewUrl && secretResult.status !== "fail" && draftRouteResult?.status !== "fail") {
|
|
665
976
|
const draftUrls = buildDraftPreviewUrls(
|
|
666
977
|
reachable.previewUrl,
|
|
667
978
|
draftSecret
|
|
@@ -670,7 +981,8 @@ async function runLink(options, deps) {
|
|
|
670
981
|
log(formatDraftPreviewLink(draftUrls.draftUrl, draftUrls.exitUrl));
|
|
671
982
|
}
|
|
672
983
|
}
|
|
673
|
-
|
|
984
|
+
const failed = reachable.status === "fail" || secretResult.status === "fail" || draftRouteResult?.status === "fail";
|
|
985
|
+
return failed ? 1 : 0;
|
|
674
986
|
} catch (error) {
|
|
675
987
|
if (error instanceof CliError) {
|
|
676
988
|
log(
|
|
@@ -696,6 +1008,7 @@ async function runLink(options, deps) {
|
|
|
696
1008
|
var USAGE = [
|
|
697
1009
|
"usage: cmssy <command>",
|
|
698
1010
|
" cmssy init [--dir <path>] [--force]",
|
|
1011
|
+
" cmssy add block <name> [--dir <path>]",
|
|
699
1012
|
" cmssy link [--token <cs_...>] [--workspace <slug>] [--preview-url <url>]"
|
|
700
1013
|
].join("\n");
|
|
701
1014
|
function flagValue(args, name) {
|
|
@@ -737,6 +1050,19 @@ async function main() {
|
|
|
737
1050
|
);
|
|
738
1051
|
return;
|
|
739
1052
|
}
|
|
1053
|
+
if (command === "add") {
|
|
1054
|
+
const [kind, name, ...rest] = args;
|
|
1055
|
+
if (kind !== "block" || name === void 0 || name.startsWith("--")) {
|
|
1056
|
+
console.error(USAGE);
|
|
1057
|
+
process.exitCode = 1;
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
process.exitCode = runAddBlock(
|
|
1061
|
+
{ name, dir: flagValue(rest, "--dir") },
|
|
1062
|
+
{ cwd: process.cwd(), log: (line) => console.log(line) }
|
|
1063
|
+
);
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
740
1066
|
if (command === "link") {
|
|
741
1067
|
process.exitCode = await runLinkCommand(args);
|
|
742
1068
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cmssy/cli",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.10.0",
|
|
4
4
|
"description": "The cmssy CLI: `cmssy init` wires cmssy into an existing Next.js, Astro or React Router app; `cmssy link` connects it to a workspace and verifies the editor wiring.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cmssy",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"assets"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@cmssy/core": "9.
|
|
30
|
+
"@cmssy/core": "9.10.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^20.0.0",
|