@cmssy/cli 9.7.2 → 9.9.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 +342 -40
- 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";
|
|
@@ -151,12 +144,17 @@ Edit this site visually:
|
|
|
151
144
|
${paint(url, CYAN, colored)}
|
|
152
145
|
`;
|
|
153
146
|
}
|
|
147
|
+
function formatDraftPreviewLink(draftUrl, exitUrl, colored = useColor()) {
|
|
148
|
+
return `
|
|
149
|
+
Preview drafts without the editor (the /api/draft path also works on your local dev server):
|
|
150
|
+
${paint(draftUrl, CYAN, colored)}
|
|
151
|
+
exit draft mode: ${paint(exitUrl, CYAN, colored)}
|
|
152
|
+
`;
|
|
153
|
+
}
|
|
154
154
|
|
|
155
|
-
// src/
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
new URL("../package.json", import.meta.url)
|
|
159
|
-
);
|
|
155
|
+
// src/framework.ts
|
|
156
|
+
import { existsSync, readFileSync } from "fs";
|
|
157
|
+
import { join } from "path";
|
|
160
158
|
var FRAMEWORKS = [
|
|
161
159
|
{
|
|
162
160
|
name: "next",
|
|
@@ -246,8 +244,272 @@ function detectFramework(pkg) {
|
|
|
246
244
|
}
|
|
247
245
|
return match;
|
|
248
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
|
+
);
|
|
249
511
|
function frameworkFiles(framework, root) {
|
|
250
|
-
const srcPrefix = framework.name === "next"
|
|
512
|
+
const srcPrefix = framework.name === "next" ? nextSrcPrefix(root) : "";
|
|
251
513
|
return [
|
|
252
514
|
{ asset: "env.example", target: ".env.example" },
|
|
253
515
|
...framework.files.map((path) => ({
|
|
@@ -257,7 +519,7 @@ function frameworkFiles(framework, root) {
|
|
|
257
519
|
];
|
|
258
520
|
}
|
|
259
521
|
function cliVersion() {
|
|
260
|
-
const pkg = JSON.parse(
|
|
522
|
+
const pkg = JSON.parse(readFileSync3(CLI_PACKAGE_JSON, "utf8"));
|
|
261
523
|
return pkg.version;
|
|
262
524
|
}
|
|
263
525
|
function addDependencies(root, pkg, framework) {
|
|
@@ -272,19 +534,19 @@ function addDependencies(root, pkg, framework) {
|
|
|
272
534
|
pkg.dependencies = Object.fromEntries(
|
|
273
535
|
Object.entries(dependencies).sort(([a], [b]) => a.localeCompare(b))
|
|
274
536
|
);
|
|
275
|
-
|
|
276
|
-
|
|
537
|
+
writeFileSync2(
|
|
538
|
+
join3(root, "package.json"),
|
|
277
539
|
`${JSON.stringify(pkg, null, 2)}
|
|
278
540
|
`
|
|
279
541
|
);
|
|
280
542
|
return missing.map((name) => `${name}@${range}`);
|
|
281
543
|
}
|
|
282
544
|
function detectInstallCommand(root) {
|
|
283
|
-
if (
|
|
545
|
+
if (existsSync3(join3(root, "pnpm-lock.yaml")) || existsSync3(join3(root, "pnpm-workspace.yaml"))) {
|
|
284
546
|
return "pnpm install";
|
|
285
547
|
}
|
|
286
|
-
if (
|
|
287
|
-
if (
|
|
548
|
+
if (existsSync3(join3(root, "yarn.lock"))) return "yarn";
|
|
549
|
+
if (existsSync3(join3(root, "bun.lock")) || existsSync3(join3(root, "bun.lockb"))) {
|
|
288
550
|
return "bun install";
|
|
289
551
|
}
|
|
290
552
|
return "npm install";
|
|
@@ -292,9 +554,8 @@ function detectInstallCommand(root) {
|
|
|
292
554
|
function frameworkNotes(framework, root, skipped) {
|
|
293
555
|
const notes = [];
|
|
294
556
|
if (framework.name === "next") {
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
if (existsSync(join(root, home))) {
|
|
557
|
+
const home = `${nextSrcPrefix(root)}app/page.tsx`;
|
|
558
|
+
if (existsSync3(join3(root, home))) {
|
|
298
559
|
notes.push({
|
|
299
560
|
status: "unknown",
|
|
300
561
|
message: `${home} conflicts with the cmssy catch-all route - delete it and the cmssy page serves /`
|
|
@@ -306,7 +567,7 @@ function frameworkNotes(framework, root, skipped) {
|
|
|
306
567
|
status: "unknown",
|
|
307
568
|
message: "the cmssy wiring needs the React integration and a server adapter - run: npx astro add react node"
|
|
308
569
|
});
|
|
309
|
-
if (
|
|
570
|
+
if (existsSync3(join3(root, "src/pages/index.astro"))) {
|
|
310
571
|
notes.push({
|
|
311
572
|
status: "unknown",
|
|
312
573
|
message: "src/pages/index.astro shadows the cmssy catch-all for / - delete it and the cmssy page serves /"
|
|
@@ -324,8 +585,8 @@ function frameworkNotes(framework, root, skipped) {
|
|
|
324
585
|
function runInit(options, deps) {
|
|
325
586
|
const { log } = deps;
|
|
326
587
|
try {
|
|
327
|
-
const root =
|
|
328
|
-
if (!
|
|
588
|
+
const root = resolve2(deps.cwd, options.dir ?? ".");
|
|
589
|
+
if (!existsSync3(root)) {
|
|
329
590
|
throw new CliError(
|
|
330
591
|
`${root} does not exist`,
|
|
331
592
|
"pass --dir with the app's directory, or run cmssy init inside it"
|
|
@@ -342,8 +603,8 @@ function runInit(options, deps) {
|
|
|
342
603
|
const written = [];
|
|
343
604
|
const skipped = [];
|
|
344
605
|
for (const file of frameworkFiles(framework, root)) {
|
|
345
|
-
const target =
|
|
346
|
-
if (
|
|
606
|
+
const target = join3(root, file.target);
|
|
607
|
+
if (existsSync3(target) && !options.force) {
|
|
347
608
|
skipped.push(file.target);
|
|
348
609
|
log(
|
|
349
610
|
formatResult({
|
|
@@ -353,8 +614,8 @@ function runInit(options, deps) {
|
|
|
353
614
|
);
|
|
354
615
|
continue;
|
|
355
616
|
}
|
|
356
|
-
|
|
357
|
-
copyFileSync(
|
|
617
|
+
mkdirSync2(dirname2(target), { recursive: true });
|
|
618
|
+
copyFileSync(join3(ASSETS_DIR, framework.name, file.asset), target);
|
|
358
619
|
written.push(file.target);
|
|
359
620
|
log(formatResult({ status: "ok", message: `wrote ${file.target}` }));
|
|
360
621
|
}
|
|
@@ -406,8 +667,8 @@ function runInit(options, deps) {
|
|
|
406
667
|
}
|
|
407
668
|
|
|
408
669
|
// src/link.ts
|
|
409
|
-
import { existsSync as
|
|
410
|
-
import { join as
|
|
670
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
671
|
+
import { join as join4 } from "path";
|
|
411
672
|
import {
|
|
412
673
|
buildEditorUrl,
|
|
413
674
|
checkDraftSecret,
|
|
@@ -477,9 +738,9 @@ function mergeEnvContent(existing, updates) {
|
|
|
477
738
|
var ENV_FILES = [".env.local", ".env"];
|
|
478
739
|
function loadEnvFiles(cwd, env) {
|
|
479
740
|
for (const file of ENV_FILES) {
|
|
480
|
-
const path =
|
|
481
|
-
if (!
|
|
482
|
-
applyEnv(parseEnvFile(
|
|
741
|
+
const path = join4(cwd, file);
|
|
742
|
+
if (!existsSync4(path)) continue;
|
|
743
|
+
applyEnv(parseEnvFile(readFileSync4(path, "utf8")), env);
|
|
483
744
|
}
|
|
484
745
|
}
|
|
485
746
|
function resolveToken(options, deps) {
|
|
@@ -496,6 +757,24 @@ function describeWorkspace(workspace) {
|
|
|
496
757
|
const org = workspace.organizationSlug ?? "?";
|
|
497
758
|
return `${workspace.name} (${org}/${workspace.slug})`;
|
|
498
759
|
}
|
|
760
|
+
function draftRouteBase(previewUrl) {
|
|
761
|
+
try {
|
|
762
|
+
const base = new URL(previewUrl);
|
|
763
|
+
const basePath = base.pathname.replace(/\/+$/, "");
|
|
764
|
+
return `${base.origin}${basePath}/api/draft`;
|
|
765
|
+
} catch {
|
|
766
|
+
return null;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
function buildDraftPreviewUrls(previewUrl, draftSecret) {
|
|
770
|
+
const base = draftRouteBase(previewUrl);
|
|
771
|
+
if (!base) return null;
|
|
772
|
+
const params = new URLSearchParams({ secret: draftSecret, redirect: "/" });
|
|
773
|
+
return {
|
|
774
|
+
draftUrl: `${base}?${params.toString()}`,
|
|
775
|
+
exitUrl: `${base}?disable=1`
|
|
776
|
+
};
|
|
777
|
+
}
|
|
499
778
|
async function selectWorkspace(workspaces, options, deps) {
|
|
500
779
|
if (workspaces.length === 0) {
|
|
501
780
|
throw new CliError(
|
|
@@ -562,9 +841,9 @@ function resolvePreviewUrl(options) {
|
|
|
562
841
|
return origin;
|
|
563
842
|
}
|
|
564
843
|
function writeEnvLocal(cwd, updates) {
|
|
565
|
-
const path =
|
|
566
|
-
const existing =
|
|
567
|
-
|
|
844
|
+
const path = join4(cwd, ".env.local");
|
|
845
|
+
const existing = existsSync4(path) ? readFileSync4(path, "utf8") : null;
|
|
846
|
+
writeFileSync3(path, mergeEnvContent(existing, updates));
|
|
568
847
|
}
|
|
569
848
|
async function runLink(options, deps) {
|
|
570
849
|
const { cwd, env, log } = deps;
|
|
@@ -636,6 +915,15 @@ async function runLink(options, deps) {
|
|
|
636
915
|
const secretResult = await checkDraftSecret(preflight);
|
|
637
916
|
log(formatResult(secretResult));
|
|
638
917
|
log(formatEditorLink(buildEditorUrl(preflight)));
|
|
918
|
+
if (reachable.previewUrl && secretResult.status !== "fail") {
|
|
919
|
+
const draftUrls = buildDraftPreviewUrls(
|
|
920
|
+
reachable.previewUrl,
|
|
921
|
+
draftSecret
|
|
922
|
+
);
|
|
923
|
+
if (draftUrls) {
|
|
924
|
+
log(formatDraftPreviewLink(draftUrls.draftUrl, draftUrls.exitUrl));
|
|
925
|
+
}
|
|
926
|
+
}
|
|
639
927
|
return reachable.status === "fail" || secretResult.status === "fail" ? 1 : 0;
|
|
640
928
|
} catch (error) {
|
|
641
929
|
if (error instanceof CliError) {
|
|
@@ -662,6 +950,7 @@ async function runLink(options, deps) {
|
|
|
662
950
|
var USAGE = [
|
|
663
951
|
"usage: cmssy <command>",
|
|
664
952
|
" cmssy init [--dir <path>] [--force]",
|
|
953
|
+
" cmssy add block <name> [--dir <path>]",
|
|
665
954
|
" cmssy link [--token <cs_...>] [--workspace <slug>] [--preview-url <url>]"
|
|
666
955
|
].join("\n");
|
|
667
956
|
function flagValue(args, name) {
|
|
@@ -703,6 +992,19 @@ async function main() {
|
|
|
703
992
|
);
|
|
704
993
|
return;
|
|
705
994
|
}
|
|
995
|
+
if (command === "add") {
|
|
996
|
+
const [kind, name, ...rest] = args;
|
|
997
|
+
if (kind !== "block" || name === void 0 || name.startsWith("--")) {
|
|
998
|
+
console.error(USAGE);
|
|
999
|
+
process.exitCode = 1;
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
process.exitCode = runAddBlock(
|
|
1003
|
+
{ name, dir: flagValue(rest, "--dir") },
|
|
1004
|
+
{ cwd: process.cwd(), log: (line) => console.log(line) }
|
|
1005
|
+
);
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
706
1008
|
if (command === "link") {
|
|
707
1009
|
process.exitCode = await runLinkCommand(args);
|
|
708
1010
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cmssy/cli",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.9.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.9.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^20.0.0",
|