@cmssy/cli 14.1.0 → 15.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.
- package/assets/init/astro/src/cmssy/layout-slot.tsx +4 -4
- package/assets/init/next/app/[[...path]]/page.tsx +2 -2
- package/assets/init/next/app/cmssy-edit/[[...path]]/page.tsx +2 -2
- package/assets/init/remix/app/cmssy/layout-slot.tsx +4 -4
- package/assets/init/remix/app/routes/page.tsx +8 -8
- package/dist/index.js +428 -19
- package/package.json +3 -2
|
@@ -12,7 +12,7 @@ import { blocks } from "./blocks";
|
|
|
12
12
|
|
|
13
13
|
export interface LayoutSlotProps {
|
|
14
14
|
groups: CmssyLayoutGroup[];
|
|
15
|
-
|
|
15
|
+
region: CmssyRegion<typeof layout>;
|
|
16
16
|
page: CmssyBlockPage;
|
|
17
17
|
locale: string;
|
|
18
18
|
defaultLocale: string;
|
|
@@ -24,7 +24,7 @@ export interface LayoutSlotProps {
|
|
|
24
24
|
|
|
25
25
|
export default function LayoutSlot({
|
|
26
26
|
groups,
|
|
27
|
-
|
|
27
|
+
region,
|
|
28
28
|
page,
|
|
29
29
|
locale,
|
|
30
30
|
defaultLocale,
|
|
@@ -37,7 +37,7 @@ export default function LayoutSlot({
|
|
|
37
37
|
return (
|
|
38
38
|
<CmssyLazyLayout
|
|
39
39
|
groups={groups}
|
|
40
|
-
|
|
40
|
+
region={region}
|
|
41
41
|
page={page}
|
|
42
42
|
locale={locale}
|
|
43
43
|
defaultLocale={defaultLocale}
|
|
@@ -50,7 +50,7 @@ export default function LayoutSlot({
|
|
|
50
50
|
);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
const group = groups.find((candidate) => candidate.
|
|
53
|
+
const group = groups.find((candidate) => candidate.region === region);
|
|
54
54
|
if (!group) return null;
|
|
55
55
|
|
|
56
56
|
const blockMap = buildBlockMap(blocks);
|
|
@@ -22,11 +22,11 @@ const CmssyPage = createCmssyPage(cmssy, blocks, { editor: CmssyEditor });
|
|
|
22
22
|
|
|
23
23
|
export default async function Page(props: PageProps) {
|
|
24
24
|
const { path } = await props.params;
|
|
25
|
-
const slot = (
|
|
25
|
+
const slot = (region: CmssyRegion<typeof layout>) => (
|
|
26
26
|
<CmssyLayoutSlot
|
|
27
27
|
config={cmssy}
|
|
28
28
|
blocks={blocks}
|
|
29
|
-
|
|
29
|
+
region={region}
|
|
30
30
|
path={path ?? []}
|
|
31
31
|
editMode={false}
|
|
32
32
|
editable={EditableLayout}
|
|
@@ -25,11 +25,11 @@ export default async function EditPage(props: PageProps) {
|
|
|
25
25
|
|
|
26
26
|
const editMode = await isCmssyEditMode();
|
|
27
27
|
|
|
28
|
-
const slot = (
|
|
28
|
+
const slot = (region: CmssyRegion<typeof layout>) => (
|
|
29
29
|
<CmssyLayoutSlot
|
|
30
30
|
config={cmssy}
|
|
31
31
|
blocks={blocks}
|
|
32
|
-
|
|
32
|
+
region={region}
|
|
33
33
|
path={path ?? []}
|
|
34
34
|
editMode={editMode}
|
|
35
35
|
editable={EditableLayout}
|
|
@@ -12,7 +12,7 @@ import { blocks } from "./blocks";
|
|
|
12
12
|
|
|
13
13
|
export interface LayoutSlotProps {
|
|
14
14
|
groups: CmssyLayoutGroup[];
|
|
15
|
-
|
|
15
|
+
region: CmssyRegion<typeof layout>;
|
|
16
16
|
page: CmssyBlockPage;
|
|
17
17
|
locale: string;
|
|
18
18
|
defaultLocale: string;
|
|
@@ -24,7 +24,7 @@ export interface LayoutSlotProps {
|
|
|
24
24
|
|
|
25
25
|
export function LayoutSlot({
|
|
26
26
|
groups,
|
|
27
|
-
|
|
27
|
+
region,
|
|
28
28
|
page,
|
|
29
29
|
locale,
|
|
30
30
|
defaultLocale,
|
|
@@ -37,7 +37,7 @@ export function LayoutSlot({
|
|
|
37
37
|
return (
|
|
38
38
|
<CmssyLazyLayout
|
|
39
39
|
groups={groups}
|
|
40
|
-
|
|
40
|
+
region={region}
|
|
41
41
|
page={page}
|
|
42
42
|
locale={locale}
|
|
43
43
|
defaultLocale={defaultLocale}
|
|
@@ -50,7 +50,7 @@ export function LayoutSlot({
|
|
|
50
50
|
);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
const group = groups.find((candidate) => candidate.
|
|
53
|
+
const group = groups.find((candidate) => candidate.region === region);
|
|
54
54
|
if (!group) return null;
|
|
55
55
|
|
|
56
56
|
const blockMap = buildBlockMap(blocks);
|
|
@@ -20,13 +20,13 @@ import type { Route } from "./+types/page";
|
|
|
20
20
|
|
|
21
21
|
const cmssyLoader = createCmssyLoader(cmssy, { blocks });
|
|
22
22
|
|
|
23
|
-
type
|
|
23
|
+
type Region = CmssyRegion<typeof layout>;
|
|
24
24
|
|
|
25
25
|
async function resolveSlot(
|
|
26
|
-
options: Omit<ResolveLayoutBlockDataOptions, "
|
|
27
|
-
|
|
26
|
+
options: Omit<ResolveLayoutBlockDataOptions, "region">,
|
|
27
|
+
region: Region,
|
|
28
28
|
) {
|
|
29
|
-
const resolved = await resolveEditorLayoutBlockData({ ...options,
|
|
29
|
+
const resolved = await resolveEditorLayoutBlockData({ ...options, region });
|
|
30
30
|
return { data: resolved.data, resolvedContent: resolved.content };
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -90,17 +90,17 @@ export default function CmssyPage({ loaderData }: Route.ComponentProps) {
|
|
|
90
90
|
return <div dangerouslySetInnerHTML={{ __html: diagnostics }} />;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
const slot = (
|
|
93
|
+
const slot = (region: Region) => (
|
|
94
94
|
<LayoutSlot
|
|
95
95
|
groups={layouts}
|
|
96
|
-
|
|
96
|
+
region={region}
|
|
97
97
|
page={pageContext}
|
|
98
98
|
locale={locale}
|
|
99
99
|
defaultLocale={defaultLocale}
|
|
100
100
|
enabledLocales={enabledLocales}
|
|
101
101
|
edit={isEdit ? { editorOrigin } : undefined}
|
|
102
|
-
data={layoutData[
|
|
103
|
-
resolvedContent={layoutData[
|
|
102
|
+
data={layoutData[region]?.data}
|
|
103
|
+
resolvedContent={layoutData[region]?.resolvedContent}
|
|
104
104
|
/>
|
|
105
105
|
);
|
|
106
106
|
|
package/dist/index.js
CHANGED
|
@@ -13,6 +13,10 @@ var BAD_TOKEN_HINT = {
|
|
|
13
13
|
message: "the cmssy API rejected the token",
|
|
14
14
|
fix: "create an API token in the cmssy dashboard (Settings \u2192 API Tokens) and pass it via --token or CMSSY_API_TOKEN"
|
|
15
15
|
};
|
|
16
|
+
var NEEDS_MANIFEST_WRITE_HINT = {
|
|
17
|
+
message: "the token's user cannot write this workspace's block manifest",
|
|
18
|
+
fix: "the token's user needs the PAGES_EDIT permission in the selected workspace"
|
|
19
|
+
};
|
|
16
20
|
var NEEDS_PAGES_EDIT_HINT = {
|
|
17
21
|
message: "the token's user cannot read this workspace's settings",
|
|
18
22
|
fix: "the token's user needs the PAGES_EDIT permission in the selected workspace"
|
|
@@ -68,8 +72,11 @@ async function adminGraphql(query, variables, options) {
|
|
|
68
72
|
`the cmssy API rejected the request - ${errors.map((error) => error.message ?? "GraphQL error").join("; ")}`
|
|
69
73
|
);
|
|
70
74
|
}
|
|
71
|
-
if (!response.ok || envelope?.data
|
|
72
|
-
throw new CliError(
|
|
75
|
+
if (!response.ok || envelope?.data == null) {
|
|
76
|
+
throw new CliError(
|
|
77
|
+
`the cmssy API returned no data (HTTP ${response.status})`,
|
|
78
|
+
"check that CMSSY_API_URL points at the cmssy admin API (https://api.cmssy.io/graphql), not the delivery endpoint"
|
|
79
|
+
);
|
|
73
80
|
}
|
|
74
81
|
return envelope.data;
|
|
75
82
|
}
|
|
@@ -95,6 +102,14 @@ var UPDATE_PREVIEW_URL_MUTATION = `mutation CliSetPreviewUrl($input: UpdateSiteC
|
|
|
95
102
|
}
|
|
96
103
|
}
|
|
97
104
|
}`;
|
|
105
|
+
var SAVE_BLOCK_MANIFEST_MUTATION = `mutation CliSaveBlockManifest($blocks: JSON!, $regions: JSON) {
|
|
106
|
+
blockManifest {
|
|
107
|
+
save(blocks: $blocks, regions: $regions) {
|
|
108
|
+
hash
|
|
109
|
+
updatedAt
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}`;
|
|
98
113
|
async function fetchMyWorkspaces(options) {
|
|
99
114
|
const data = await adminGraphql(WORKSPACES_MINE_QUERY, {}, options);
|
|
100
115
|
return data.workspace.mine;
|
|
@@ -114,6 +129,31 @@ async function setPreviewUrl(previewUrl, options) {
|
|
|
114
129
|
{ ...options, denied: NEEDS_PAGES_EDIT_HINT }
|
|
115
130
|
);
|
|
116
131
|
}
|
|
132
|
+
var BLOCK_MANIFEST_HASH_QUERY = `query CliBlockManifestHash {
|
|
133
|
+
blockManifest {
|
|
134
|
+
get {
|
|
135
|
+
hash
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}`;
|
|
139
|
+
async function fetchBlockManifestHash(options) {
|
|
140
|
+
const data = await adminGraphql(
|
|
141
|
+
BLOCK_MANIFEST_HASH_QUERY,
|
|
142
|
+
{},
|
|
143
|
+
{
|
|
144
|
+
...options,
|
|
145
|
+
denied: NEEDS_MANIFEST_WRITE_HINT
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
return data.blockManifest.get?.hash ?? null;
|
|
149
|
+
}
|
|
150
|
+
async function saveBlockManifest(manifest, options) {
|
|
151
|
+
const data = await adminGraphql(SAVE_BLOCK_MANIFEST_MUTATION, manifest, {
|
|
152
|
+
...options,
|
|
153
|
+
denied: NEEDS_MANIFEST_WRITE_HINT
|
|
154
|
+
});
|
|
155
|
+
return data.blockManifest.save;
|
|
156
|
+
}
|
|
117
157
|
|
|
118
158
|
// src/format.ts
|
|
119
159
|
var GREEN = "\x1B[32m";
|
|
@@ -601,6 +641,25 @@ function runAddBlock(options, deps) {
|
|
|
601
641
|
}
|
|
602
642
|
}
|
|
603
643
|
|
|
644
|
+
// src/args.ts
|
|
645
|
+
function flagValue(args, name) {
|
|
646
|
+
const index = args.findIndex((arg) => arg === name);
|
|
647
|
+
if (index !== -1) {
|
|
648
|
+
const value = args[index + 1];
|
|
649
|
+
if (value === void 0 || value.startsWith("--")) {
|
|
650
|
+
throw new CliError(
|
|
651
|
+
`${name} needs a value`,
|
|
652
|
+
`pass ${name} <value>, or ${name}=<value>`
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
return value;
|
|
656
|
+
}
|
|
657
|
+
return args.find((arg) => arg.startsWith(`${name}=`))?.slice(name.length + 1);
|
|
658
|
+
}
|
|
659
|
+
function hasFlag(args, name) {
|
|
660
|
+
return args.includes(name);
|
|
661
|
+
}
|
|
662
|
+
|
|
604
663
|
// src/init.ts
|
|
605
664
|
import {
|
|
606
665
|
copyFileSync as copyFileSync2,
|
|
@@ -1464,8 +1523,345 @@ async function runLink(options, deps) {
|
|
|
1464
1523
|
}
|
|
1465
1524
|
}
|
|
1466
1525
|
|
|
1526
|
+
// src/sync-manifest.ts
|
|
1527
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1528
|
+
import { isAbsolute, join as join8 } from "path";
|
|
1529
|
+
import {
|
|
1530
|
+
buildBlockManifest
|
|
1531
|
+
} from "@cmssy/core";
|
|
1532
|
+
|
|
1533
|
+
// src/site-modules.ts
|
|
1534
|
+
import { mkdirSync as mkdirSync3, rmSync, writeFileSync as writeFileSync5 } from "fs";
|
|
1535
|
+
import { basename, join as join7, resolve as resolve3 } from "path";
|
|
1536
|
+
import { pathToFileURL } from "url";
|
|
1537
|
+
import { build } from "esbuild";
|
|
1538
|
+
var RUNTIME_ONLY_MODULES = ["server-only", "client-only"];
|
|
1539
|
+
var ASSET_EXTENSIONS = [
|
|
1540
|
+
".module.css",
|
|
1541
|
+
".module.scss",
|
|
1542
|
+
".module.sass",
|
|
1543
|
+
".module.less",
|
|
1544
|
+
".css",
|
|
1545
|
+
".scss",
|
|
1546
|
+
".sass",
|
|
1547
|
+
".less",
|
|
1548
|
+
".png",
|
|
1549
|
+
".jpg",
|
|
1550
|
+
".jpeg",
|
|
1551
|
+
".gif",
|
|
1552
|
+
".svg",
|
|
1553
|
+
".webp",
|
|
1554
|
+
".avif",
|
|
1555
|
+
".ico",
|
|
1556
|
+
".woff",
|
|
1557
|
+
".woff2",
|
|
1558
|
+
".ttf",
|
|
1559
|
+
".otf",
|
|
1560
|
+
".mp4",
|
|
1561
|
+
".webm",
|
|
1562
|
+
".mp3",
|
|
1563
|
+
".md",
|
|
1564
|
+
".mdx"
|
|
1565
|
+
];
|
|
1566
|
+
var assetLoaders = Object.fromEntries(
|
|
1567
|
+
ASSET_EXTENSIONS.map((extension) => [extension, "empty"])
|
|
1568
|
+
);
|
|
1569
|
+
var runtimeOnlyModules = {
|
|
1570
|
+
name: "cmssy-runtime-only-modules",
|
|
1571
|
+
setup(api) {
|
|
1572
|
+
const filter = new RegExp(`^(${RUNTIME_ONLY_MODULES.join("|")})$`);
|
|
1573
|
+
api.onResolve({ filter }, (args) => ({
|
|
1574
|
+
path: args.path,
|
|
1575
|
+
namespace: "cmssy-empty"
|
|
1576
|
+
}));
|
|
1577
|
+
api.onLoad({ filter: /.*/, namespace: "cmssy-empty" }, () => ({
|
|
1578
|
+
contents: "export {};",
|
|
1579
|
+
loader: "js"
|
|
1580
|
+
}));
|
|
1581
|
+
}
|
|
1582
|
+
};
|
|
1583
|
+
async function loadSiteModule(cwd, entry) {
|
|
1584
|
+
const entryPath = resolve3(cwd, entry);
|
|
1585
|
+
let code;
|
|
1586
|
+
try {
|
|
1587
|
+
const result = await build({
|
|
1588
|
+
absWorkingDir: cwd,
|
|
1589
|
+
entryPoints: [entryPath],
|
|
1590
|
+
bundle: true,
|
|
1591
|
+
platform: "node",
|
|
1592
|
+
format: "esm",
|
|
1593
|
+
target: "node20",
|
|
1594
|
+
write: false,
|
|
1595
|
+
packages: "external",
|
|
1596
|
+
jsx: "automatic",
|
|
1597
|
+
loader: assetLoaders,
|
|
1598
|
+
define: { "import.meta.env": "process.env" },
|
|
1599
|
+
plugins: [runtimeOnlyModules],
|
|
1600
|
+
logLevel: "silent"
|
|
1601
|
+
});
|
|
1602
|
+
code = result.outputFiles[0]?.text ?? "";
|
|
1603
|
+
} catch (error) {
|
|
1604
|
+
const messages = error && typeof error === "object" && "errors" in error ? error.errors.map(
|
|
1605
|
+
(issue) => issue.location ? `${issue.location.file}:${issue.location.line} ${issue.text}` : issue.text
|
|
1606
|
+
) : [error instanceof Error ? error.message : String(error)];
|
|
1607
|
+
throw new CliError(`could not compile ${entry}`, messages.join("\n "));
|
|
1608
|
+
}
|
|
1609
|
+
const dir = join7(cwd, "node_modules", ".cache", "cmssy");
|
|
1610
|
+
const file = join7(
|
|
1611
|
+
dir,
|
|
1612
|
+
`${basename(entry).replace(/\.[^.]+$/, "")}.${process.pid}.${Date.now()}.mjs`
|
|
1613
|
+
);
|
|
1614
|
+
try {
|
|
1615
|
+
mkdirSync3(dir, { recursive: true });
|
|
1616
|
+
writeFileSync5(file, code);
|
|
1617
|
+
} catch (error) {
|
|
1618
|
+
throw new CliError(
|
|
1619
|
+
`could not write the compiled ${entry} to ${dir}`,
|
|
1620
|
+
error instanceof Error ? error.message : String(error)
|
|
1621
|
+
);
|
|
1622
|
+
}
|
|
1623
|
+
try {
|
|
1624
|
+
return await import(pathToFileURL(file).href);
|
|
1625
|
+
} catch (error) {
|
|
1626
|
+
throw new CliError(
|
|
1627
|
+
`could not load ${entry}`,
|
|
1628
|
+
error instanceof Error ? error.message : String(error)
|
|
1629
|
+
);
|
|
1630
|
+
} finally {
|
|
1631
|
+
rmSync(file, { force: true });
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
// src/sync-manifest.ts
|
|
1636
|
+
var BLOCKS_CANDIDATES = [
|
|
1637
|
+
"cmssy/blocks.ts",
|
|
1638
|
+
"cmssy/blocks.tsx",
|
|
1639
|
+
"src/cmssy/blocks.ts",
|
|
1640
|
+
"src/cmssy/blocks.tsx",
|
|
1641
|
+
"app/cmssy/blocks.ts",
|
|
1642
|
+
"app/cmssy/blocks.tsx"
|
|
1643
|
+
];
|
|
1644
|
+
var CONFIG_CANDIDATES = [
|
|
1645
|
+
"cmssy.config.ts",
|
|
1646
|
+
"cmssy.config.mts",
|
|
1647
|
+
"src/cmssy.config.ts",
|
|
1648
|
+
"src/cmssy.config.mts",
|
|
1649
|
+
"app/cmssy.config.ts"
|
|
1650
|
+
];
|
|
1651
|
+
var SYNC_MANIFEST_USAGE = [
|
|
1652
|
+
" cmssy sync-manifest [--blocks <path>] [--config <path>] [--token <cs_...>]",
|
|
1653
|
+
" [--org <slug>] [--workspace <slug>] [--dry-run] [--help]"
|
|
1654
|
+
];
|
|
1655
|
+
function findModule(cwd, requested, candidates, flag) {
|
|
1656
|
+
if (requested?.trim()) {
|
|
1657
|
+
const path = requested.trim();
|
|
1658
|
+
if (!existsSync7(isAbsolute(path) ? path : join8(cwd, path))) {
|
|
1659
|
+
throw new CliError(`${path} does not exist`, `check the ${flag} path`);
|
|
1660
|
+
}
|
|
1661
|
+
return path;
|
|
1662
|
+
}
|
|
1663
|
+
const found = candidates.find(
|
|
1664
|
+
(candidate) => existsSync7(join8(cwd, candidate))
|
|
1665
|
+
);
|
|
1666
|
+
if (!found) {
|
|
1667
|
+
throw new CliError(
|
|
1668
|
+
`no ${candidates[0]} found in ${cwd}`,
|
|
1669
|
+
`looked for ${candidates.join(", ")} - pass ${flag} <path> to name the file`
|
|
1670
|
+
);
|
|
1671
|
+
}
|
|
1672
|
+
return found;
|
|
1673
|
+
}
|
|
1674
|
+
function isBlockSource(value) {
|
|
1675
|
+
return !!value && typeof value === "object" && typeof value.type === "string" && !!value.props && typeof value.props === "object";
|
|
1676
|
+
}
|
|
1677
|
+
function readBlocks(module, path) {
|
|
1678
|
+
const exported = module.blocks ?? module.default;
|
|
1679
|
+
if (!Array.isArray(exported)) {
|
|
1680
|
+
throw new CliError(
|
|
1681
|
+
`${path} does not export a \`blocks\` array`,
|
|
1682
|
+
"export const blocks = [heroBlock, ...] - the same list the app renders from"
|
|
1683
|
+
);
|
|
1684
|
+
}
|
|
1685
|
+
const invalid = exported.findIndex((block) => !isBlockSource(block));
|
|
1686
|
+
if (invalid !== -1) {
|
|
1687
|
+
throw new CliError(
|
|
1688
|
+
`${path}: blocks[${invalid}] is not a block definition`,
|
|
1689
|
+
"every entry must come from defineBlock({ type, props, component })"
|
|
1690
|
+
);
|
|
1691
|
+
}
|
|
1692
|
+
const category = typeof module.category === "string" ? module.category : void 0;
|
|
1693
|
+
return { blocks: exported, category };
|
|
1694
|
+
}
|
|
1695
|
+
function isLayout(value) {
|
|
1696
|
+
return !!value && typeof value === "object" && Array.isArray(value.regions);
|
|
1697
|
+
}
|
|
1698
|
+
function readConfig(module, path) {
|
|
1699
|
+
const cmssy = module.cmssy;
|
|
1700
|
+
const site = cmssy && typeof cmssy === "object" ? cmssy : null;
|
|
1701
|
+
const layout = isLayout(module.layout) ? module.layout : isLayout(site?.layout) ? site.layout : null;
|
|
1702
|
+
if (!layout && module.layout !== void 0) {
|
|
1703
|
+
throw new CliError(
|
|
1704
|
+
`${path} exports \`layout\`, but it is not a defineCmssyLayout() result`,
|
|
1705
|
+
"export const layout = defineCmssyLayout({ regions: [...] })"
|
|
1706
|
+
);
|
|
1707
|
+
}
|
|
1708
|
+
return {
|
|
1709
|
+
layout,
|
|
1710
|
+
org: typeof site?.org === "string" ? site.org : void 0,
|
|
1711
|
+
workspaceSlug: typeof site?.workspaceSlug === "string" ? site.workspaceSlug : void 0
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1714
|
+
function resolveSlug(flag, fromConfig, fromEnv, variable, flagName) {
|
|
1715
|
+
const value = flag?.trim() || fromConfig?.trim() || fromEnv?.trim();
|
|
1716
|
+
if (!value) {
|
|
1717
|
+
throw new CliError(
|
|
1718
|
+
`no workspace to push to: ${variable} is not set`,
|
|
1719
|
+
`run cmssy link first, or pass ${flagName} <slug>`
|
|
1720
|
+
);
|
|
1721
|
+
}
|
|
1722
|
+
return value;
|
|
1723
|
+
}
|
|
1724
|
+
function resolveToken2(options, env) {
|
|
1725
|
+
const token = options.token?.trim() || env.CMSSY_API_TOKEN?.trim();
|
|
1726
|
+
if (!token) {
|
|
1727
|
+
throw new CliError(
|
|
1728
|
+
"no API token given",
|
|
1729
|
+
"create an API token in the cmssy dashboard (Settings \u2192 API Tokens) and set CMSSY_API_TOKEN in the deploy environment, or pass --token cs_..."
|
|
1730
|
+
);
|
|
1731
|
+
}
|
|
1732
|
+
return token;
|
|
1733
|
+
}
|
|
1734
|
+
function matchWorkspace(workspaces, org, slug) {
|
|
1735
|
+
const match = workspaces.find(
|
|
1736
|
+
(workspace) => workspace.slug === slug && (workspace.organizationSlug ?? null) === org
|
|
1737
|
+
);
|
|
1738
|
+
if (!match) {
|
|
1739
|
+
const known = workspaces.map(
|
|
1740
|
+
(workspace) => `${workspace.organizationSlug ?? "?"}/${workspace.slug}`
|
|
1741
|
+
).join(", ");
|
|
1742
|
+
throw new CliError(
|
|
1743
|
+
`the token's user is not a member of ${org}/${slug}`,
|
|
1744
|
+
known ? `the token can reach: ${known} - check CMSSY_ORG_SLUG and CMSSY_WORKSPACE_SLUG` : "the token's user belongs to no workspace - check the token"
|
|
1745
|
+
);
|
|
1746
|
+
}
|
|
1747
|
+
return match;
|
|
1748
|
+
}
|
|
1749
|
+
function describe(manifest) {
|
|
1750
|
+
const lines = [
|
|
1751
|
+
` blocks: ${manifest.blocks.map((block) => block.type).join(", ") || "-"}`
|
|
1752
|
+
];
|
|
1753
|
+
if (!manifest.regions) {
|
|
1754
|
+
lines.push(" regions: none declared - the stored regions are kept");
|
|
1755
|
+
return lines;
|
|
1756
|
+
}
|
|
1757
|
+
const regions = manifest.regions.map((region) => {
|
|
1758
|
+
const settings = region.settings ? Object.keys(region.settings) : [];
|
|
1759
|
+
return settings.length ? `${region.id} (${settings.join(", ")})` : region.id;
|
|
1760
|
+
});
|
|
1761
|
+
lines.push(` regions: ${regions.join(", ") || "-"}`);
|
|
1762
|
+
return lines;
|
|
1763
|
+
}
|
|
1764
|
+
async function collectManifest(options, deps) {
|
|
1765
|
+
const load = deps.load ?? loadSiteModule;
|
|
1766
|
+
const blocksPath = findModule(
|
|
1767
|
+
deps.cwd,
|
|
1768
|
+
options.blocks,
|
|
1769
|
+
BLOCKS_CANDIDATES,
|
|
1770
|
+
"--blocks"
|
|
1771
|
+
);
|
|
1772
|
+
const configPath = findModule(
|
|
1773
|
+
deps.cwd,
|
|
1774
|
+
options.config,
|
|
1775
|
+
CONFIG_CANDIDATES,
|
|
1776
|
+
"--config"
|
|
1777
|
+
);
|
|
1778
|
+
const { blocks, category } = readBlocks(
|
|
1779
|
+
await load(deps.cwd, blocksPath),
|
|
1780
|
+
blocksPath
|
|
1781
|
+
);
|
|
1782
|
+
const config = readConfig(await load(deps.cwd, configPath), configPath);
|
|
1783
|
+
if (blocks.length === 0) {
|
|
1784
|
+
throw new CliError(
|
|
1785
|
+
`${blocksPath} exports an empty \`blocks\` array`,
|
|
1786
|
+
"register at least one block - cmssy add block <name>"
|
|
1787
|
+
);
|
|
1788
|
+
}
|
|
1789
|
+
const manifest = buildBlockManifest(blocks, {
|
|
1790
|
+
category,
|
|
1791
|
+
regions: config.layout?.regions
|
|
1792
|
+
});
|
|
1793
|
+
return {
|
|
1794
|
+
manifest,
|
|
1795
|
+
blocksPath,
|
|
1796
|
+
configPath,
|
|
1797
|
+
org: config.org,
|
|
1798
|
+
workspaceSlug: config.workspaceSlug
|
|
1799
|
+
};
|
|
1800
|
+
}
|
|
1801
|
+
async function runSyncManifest(options, deps) {
|
|
1802
|
+
if (options.help) {
|
|
1803
|
+
deps.log("usage:");
|
|
1804
|
+
for (const line of SYNC_MANIFEST_USAGE) deps.log(line);
|
|
1805
|
+
return 0;
|
|
1806
|
+
}
|
|
1807
|
+
try {
|
|
1808
|
+
loadEnvFiles(deps.cwd, deps.env);
|
|
1809
|
+
const collected = await collectManifest(options, deps);
|
|
1810
|
+
const { manifest } = collected;
|
|
1811
|
+
if (options.dryRun) {
|
|
1812
|
+
deps.log(JSON.stringify(manifest, null, 2));
|
|
1813
|
+
return 0;
|
|
1814
|
+
}
|
|
1815
|
+
const token = resolveToken2(options, deps.env);
|
|
1816
|
+
const org = resolveSlug(
|
|
1817
|
+
options.org,
|
|
1818
|
+
collected.org,
|
|
1819
|
+
deps.env.CMSSY_ORG_SLUG,
|
|
1820
|
+
"CMSSY_ORG_SLUG",
|
|
1821
|
+
"--org"
|
|
1822
|
+
);
|
|
1823
|
+
const slug = resolveSlug(
|
|
1824
|
+
options.workspace,
|
|
1825
|
+
collected.workspaceSlug,
|
|
1826
|
+
deps.env.CMSSY_WORKSPACE_SLUG,
|
|
1827
|
+
"CMSSY_WORKSPACE_SLUG",
|
|
1828
|
+
"--workspace"
|
|
1829
|
+
);
|
|
1830
|
+
const request2 = {
|
|
1831
|
+
token,
|
|
1832
|
+
apiUrl: deps.env.CMSSY_API_URL,
|
|
1833
|
+
fetch: deps.fetch
|
|
1834
|
+
};
|
|
1835
|
+
const workspace = matchWorkspace(
|
|
1836
|
+
await fetchMyWorkspaces(request2),
|
|
1837
|
+
org,
|
|
1838
|
+
slug
|
|
1839
|
+
);
|
|
1840
|
+
const scoped = { ...request2, workspaceId: workspace.id };
|
|
1841
|
+
const before = await fetchBlockManifestHash(scoped);
|
|
1842
|
+
const saved = await saveBlockManifest(manifest, scoped);
|
|
1843
|
+
const unchanged = before === saved.hash;
|
|
1844
|
+
const count = `${manifest.blocks.length} block${manifest.blocks.length === 1 ? "" : "s"}${manifest.regions ? ` and ${manifest.regions.length} region${manifest.regions.length === 1 ? "" : "s"}` : ""}`;
|
|
1845
|
+
deps.log(
|
|
1846
|
+
unchanged ? `cmssy: ${org}/${slug} already has this manifest - ${count} unchanged (${collected.blocksPath}, ${collected.configPath})` : `cmssy: pushed ${count} to ${org}/${slug} (${collected.blocksPath}, ${collected.configPath})`
|
|
1847
|
+
);
|
|
1848
|
+
for (const line of describe(manifest)) deps.log(line);
|
|
1849
|
+
deps.log(
|
|
1850
|
+
unchanged ? ` manifest ${saved.hash.slice(0, 12)} - last updated ${saved.updatedAt}` : ` manifest ${saved.hash.slice(0, 12)} - updated ${saved.updatedAt}`
|
|
1851
|
+
);
|
|
1852
|
+
return 0;
|
|
1853
|
+
} catch (error) {
|
|
1854
|
+
if (error instanceof CliError) {
|
|
1855
|
+
deps.log(`cmssy: ${error.message}`);
|
|
1856
|
+
if (error.fix) deps.log(` ${error.fix}`);
|
|
1857
|
+
return 1;
|
|
1858
|
+
}
|
|
1859
|
+
throw error;
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1467
1863
|
// src/types-command.ts
|
|
1468
|
-
import { mkdirSync as
|
|
1864
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync7, readdirSync, writeFileSync as writeFileSync6 } from "fs";
|
|
1469
1865
|
import { dirname as dirname3, relative, resolve as resolvePath } from "path";
|
|
1470
1866
|
import { DEFAULT_CMSSY_API_URL } from "@cmssy/core";
|
|
1471
1867
|
|
|
@@ -1774,7 +2170,7 @@ function describeDrift(previous, next) {
|
|
|
1774
2170
|
report("fields", declared(body(previous), fields), declared(body(next), fields));
|
|
1775
2171
|
return lines.length ? lines : ["the generated output differs"];
|
|
1776
2172
|
}
|
|
1777
|
-
function
|
|
2173
|
+
function resolve4(value, fromEnv, variable) {
|
|
1778
2174
|
const resolved = value?.trim() || fromEnv?.trim();
|
|
1779
2175
|
if (!resolved) {
|
|
1780
2176
|
throw new CliError(
|
|
@@ -1856,8 +2252,8 @@ function syncOperations(options, deps) {
|
|
|
1856
2252
|
return 1;
|
|
1857
2253
|
}
|
|
1858
2254
|
try {
|
|
1859
|
-
|
|
1860
|
-
|
|
2255
|
+
mkdirSync4(dirname3(outPath), { recursive: true });
|
|
2256
|
+
writeFileSync6(outPath, source);
|
|
1861
2257
|
} catch (error) {
|
|
1862
2258
|
throw new CliError(
|
|
1863
2259
|
`could not write ${shown}: ${error.message}`,
|
|
@@ -1874,8 +2270,8 @@ async function runTypes(options, deps) {
|
|
|
1874
2270
|
try {
|
|
1875
2271
|
loadEnvFiles(deps.cwd, deps.env);
|
|
1876
2272
|
operationsStatus = syncOperations(options, deps);
|
|
1877
|
-
const org =
|
|
1878
|
-
const workspace =
|
|
2273
|
+
const org = resolve4(options.org, deps.env.CMSSY_ORG_SLUG, "CMSSY_ORG_SLUG");
|
|
2274
|
+
const workspace = resolve4(
|
|
1879
2275
|
options.workspace,
|
|
1880
2276
|
deps.env.CMSSY_WORKSPACE_SLUG,
|
|
1881
2277
|
"CMSSY_WORKSPACE_SLUG"
|
|
@@ -1922,8 +2318,8 @@ async function runTypes(options, deps) {
|
|
|
1922
2318
|
deps.log(" run `cmssy types` and commit the result");
|
|
1923
2319
|
return 1;
|
|
1924
2320
|
}
|
|
1925
|
-
|
|
1926
|
-
|
|
2321
|
+
mkdirSync4(dirname3(outPath), { recursive: true });
|
|
2322
|
+
writeFileSync6(outPath, source);
|
|
1927
2323
|
const fieldCount = models.reduce(
|
|
1928
2324
|
(total, model) => total + model.fields.length,
|
|
1929
2325
|
0
|
|
@@ -1952,16 +2348,9 @@ var USAGE = [
|
|
|
1952
2348
|
" cmssy add block <name> [--dir <path>]",
|
|
1953
2349
|
" cmssy link [--token <cs_...>] [--workspace <slug>] [--preview-url <url>]",
|
|
1954
2350
|
" cmssy types [--out <path>] [--operations-out <path>] [--no-operations]",
|
|
1955
|
-
" [--check] [--org <slug>] [--workspace <slug>]"
|
|
2351
|
+
" [--check] [--org <slug>] [--workspace <slug>]",
|
|
2352
|
+
...SYNC_MANIFEST_USAGE
|
|
1956
2353
|
].join("\n");
|
|
1957
|
-
function flagValue(args, name) {
|
|
1958
|
-
const index = args.findIndex((arg) => arg === name);
|
|
1959
|
-
if (index !== -1) return args[index + 1];
|
|
1960
|
-
return args.find((arg) => arg.startsWith(`${name}=`))?.slice(name.length + 1);
|
|
1961
|
-
}
|
|
1962
|
-
function hasFlag(args, name) {
|
|
1963
|
-
return args.includes(name);
|
|
1964
|
-
}
|
|
1965
2354
|
async function runLinkCommand(args) {
|
|
1966
2355
|
const rl = process.stdin.isTTY ? createInterface({ input: process.stdin, output: process.stdout }) : null;
|
|
1967
2356
|
try {
|
|
@@ -2029,6 +2418,26 @@ async function main() {
|
|
|
2029
2418
|
);
|
|
2030
2419
|
return;
|
|
2031
2420
|
}
|
|
2421
|
+
if (command === "sync-manifest") {
|
|
2422
|
+
process.exitCode = await runSyncManifest(
|
|
2423
|
+
{
|
|
2424
|
+
help: hasFlag(args, "--help"),
|
|
2425
|
+
blocks: flagValue(args, "--blocks"),
|
|
2426
|
+
config: flagValue(args, "--config"),
|
|
2427
|
+
token: flagValue(args, "--token"),
|
|
2428
|
+
org: flagValue(args, "--org"),
|
|
2429
|
+
workspace: flagValue(args, "--workspace"),
|
|
2430
|
+
dryRun: hasFlag(args, "--dry-run")
|
|
2431
|
+
},
|
|
2432
|
+
{
|
|
2433
|
+
cwd: process.cwd(),
|
|
2434
|
+
env: process.env,
|
|
2435
|
+
log: (line) => console.log(line),
|
|
2436
|
+
fetch: globalThis.fetch
|
|
2437
|
+
}
|
|
2438
|
+
);
|
|
2439
|
+
return;
|
|
2440
|
+
}
|
|
2032
2441
|
console.error(USAGE);
|
|
2033
2442
|
process.exitCode = command === void 0 || command === "--help" ? 0 : 1;
|
|
2034
2443
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cmssy/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "15.0.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,8 @@
|
|
|
27
27
|
"assets"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"
|
|
30
|
+
"esbuild": "^0.27.7",
|
|
31
|
+
"@cmssy/core": "15.0.0"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
33
34
|
"@types/node": "^20.0.0",
|