@backmatter/core 0.0.15 → 0.0.16
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/package.json +1 -1
- package/src/config.ts +4 -0
- package/src/model.ts +35 -1
- package/src/node/cli.ts +9 -4
- package/src/node/create-backmatter.ts +4 -1
- package/src/node/generate.ts +35 -3
- package/src/node/index.ts +1 -0
- package/src/sync-schema.ts +3 -0
package/package.json
CHANGED
package/src/config.ts
CHANGED
|
@@ -8,6 +8,8 @@ export type BackmatterDirs = {
|
|
|
8
8
|
models?: string;
|
|
9
9
|
/** Folder with one `defineBlock` default export per file. Default: "blocks". */
|
|
10
10
|
blocks?: string;
|
|
11
|
+
/** Folder with one in-content `defineBlock` per file. Default: "richtext". */
|
|
12
|
+
richtext?: string;
|
|
11
13
|
/** Folder with one MDX tag component default export per file. Default: "mdx". */
|
|
12
14
|
mdx?: string;
|
|
13
15
|
/** Folder with one layout default export per model type. Default: "layouts". */
|
|
@@ -51,6 +53,7 @@ export type ResolvedConfig = Omit<BackmatterConfig, "dirs" | "defaultLocale"> &
|
|
|
51
53
|
export const DEFAULT_DIRS: Required<BackmatterDirs> = {
|
|
52
54
|
models: "models",
|
|
53
55
|
blocks: "blocks",
|
|
56
|
+
richtext: "richtext",
|
|
54
57
|
mdx: "mdx",
|
|
55
58
|
layouts: "layouts",
|
|
56
59
|
objects: "objects",
|
|
@@ -159,6 +162,7 @@ export function resolveConfig(
|
|
|
159
162
|
dirs: {
|
|
160
163
|
models: config.dirs?.models ?? DEFAULT_DIRS.models,
|
|
161
164
|
blocks: config.dirs?.blocks ?? DEFAULT_DIRS.blocks,
|
|
165
|
+
richtext: config.dirs?.richtext ?? DEFAULT_DIRS.richtext,
|
|
162
166
|
mdx: config.dirs?.mdx ?? DEFAULT_DIRS.mdx,
|
|
163
167
|
layouts: config.dirs?.layouts ?? DEFAULT_DIRS.layouts,
|
|
164
168
|
objects: config.dirs?.objects ?? DEFAULT_DIRS.objects,
|
package/src/model.ts
CHANGED
|
@@ -85,6 +85,8 @@ export type SchemaSnapshot = {
|
|
|
85
85
|
version: number;
|
|
86
86
|
models: ModelSnapshot[];
|
|
87
87
|
blocks?: BlockSnapshot[];
|
|
88
|
+
/** In-content blocks from `richtext/`. Separate from page-builder `blocks`. */
|
|
89
|
+
richtext?: BlockSnapshot[];
|
|
88
90
|
objects?: ObjectSnapshot[];
|
|
89
91
|
};
|
|
90
92
|
|
|
@@ -178,6 +180,7 @@ export function serializeSchema(
|
|
|
178
180
|
models: ModelDef[],
|
|
179
181
|
blocks?: BlockDef[],
|
|
180
182
|
objects?: ObjectDef[],
|
|
183
|
+
richtext?: BlockDef[],
|
|
181
184
|
): SchemaSnapshot {
|
|
182
185
|
const snapshot: SchemaSnapshot = {
|
|
183
186
|
version: 1,
|
|
@@ -186,6 +189,9 @@ export function serializeSchema(
|
|
|
186
189
|
if (blocks && blocks.length > 0) {
|
|
187
190
|
snapshot.blocks = blocks.map(serializeBlock);
|
|
188
191
|
}
|
|
192
|
+
if (richtext && richtext.length > 0) {
|
|
193
|
+
snapshot.richtext = richtext.map(serializeBlock);
|
|
194
|
+
}
|
|
189
195
|
if (objects && objects.length > 0) {
|
|
190
196
|
snapshot.objects = objects.map(serializeObject);
|
|
191
197
|
}
|
|
@@ -301,6 +307,28 @@ export function parseSchemaSnapshot(input: unknown): SchemaSnapshot {
|
|
|
301
307
|
}
|
|
302
308
|
}
|
|
303
309
|
|
|
310
|
+
let richtext: BlockSnapshot[] = [];
|
|
311
|
+
if (value.richtext !== undefined) {
|
|
312
|
+
if (!Array.isArray(value.richtext)) {
|
|
313
|
+
throw new Error("schema.richtext must be an array");
|
|
314
|
+
}
|
|
315
|
+
richtext = value.richtext.map((raw, i) => {
|
|
316
|
+
try {
|
|
317
|
+
return parseBlockSnapshot(raw);
|
|
318
|
+
} catch (err) {
|
|
319
|
+
const msg = err instanceof Error ? err.message : "invalid block";
|
|
320
|
+
throw new Error(`schema.richtext[${i}]: ${msg}`);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
const richtextNames = new Set<string>();
|
|
324
|
+
for (const block of richtext) {
|
|
325
|
+
if (richtextNames.has(block.name)) {
|
|
326
|
+
throw new Error(`duplicate richtext block name: ${block.name}`);
|
|
327
|
+
}
|
|
328
|
+
richtextNames.add(block.name);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
304
332
|
let objects: ObjectSnapshot[] = [];
|
|
305
333
|
if (value.objects !== undefined) {
|
|
306
334
|
if (!Array.isArray(value.objects)) {
|
|
@@ -323,5 +351,11 @@ export function parseSchemaSnapshot(input: unknown): SchemaSnapshot {
|
|
|
323
351
|
}
|
|
324
352
|
}
|
|
325
353
|
|
|
326
|
-
return {
|
|
354
|
+
return {
|
|
355
|
+
version,
|
|
356
|
+
models,
|
|
357
|
+
blocks,
|
|
358
|
+
...(richtext.length > 0 ? { richtext } : {}),
|
|
359
|
+
objects,
|
|
360
|
+
};
|
|
327
361
|
}
|
package/src/node/cli.ts
CHANGED
|
@@ -234,8 +234,10 @@ export async function runGenerate(args: CliArgs): Promise<GenerateResult> {
|
|
|
234
234
|
root,
|
|
235
235
|
modelsDir: config.dirs.models,
|
|
236
236
|
blocksDir: config.dirs.blocks,
|
|
237
|
+
richtextDir: config.dirs.richtext,
|
|
237
238
|
mdxDir: config.dirs.mdx,
|
|
238
239
|
layoutsDir: config.dirs.layouts,
|
|
240
|
+
objectsDir: config.dirs.objects,
|
|
239
241
|
});
|
|
240
242
|
}
|
|
241
243
|
|
|
@@ -247,8 +249,9 @@ function renderConfigFile(sdk: string): string {
|
|
|
247
249
|
import { defineConfig } from "${from}";
|
|
248
250
|
|
|
249
251
|
// General settings only. Models live in models/*.ts, composer blocks in
|
|
250
|
-
// blocks/*.tsx,
|
|
251
|
-
// content/{locale}/{type}/{slug}.{mdx,json} — all discovered
|
|
252
|
+
// blocks/*.tsx, in-content blocks in richtext/*.tsx, MDX tags in mdx/*.tsx,
|
|
253
|
+
// and content in content/{locale}/{type}/{slug}.{mdx,json} — all discovered
|
|
254
|
+
// automatically.
|
|
252
255
|
// Set workspaceId from General settings (not an env var).
|
|
253
256
|
// API URL defaults to https://api.backmatter.app in production; tokens stay env.
|
|
254
257
|
export default defineConfig({
|
|
@@ -263,11 +266,13 @@ function renderCatchAllPage(registryImport: string): string {
|
|
|
263
266
|
import { cacheLife } from "next/cache";
|
|
264
267
|
import { createCatchAll } from "@backmatter/next/routes";
|
|
265
268
|
import { cms } from "${registryImport}";
|
|
266
|
-
import { components } from "${registryImport}/mdx";
|
|
269
|
+
import { components as mdxComponents } from "${registryImport}/mdx";
|
|
270
|
+
import { richtextComponents } from "${registryImport}/richtext";
|
|
267
271
|
import { blockLoaders } from "${registryImport}/blocks";
|
|
268
272
|
import { blockFallbacks } from "${registryImport}/block-fallbacks";
|
|
269
273
|
import { layouts } from "${registryImport}/layouts";
|
|
270
274
|
|
|
275
|
+
const components = { ...richtextComponents, ...mdxComponents };
|
|
271
276
|
const route = createCatchAll(cms, { components, blocks: blockLoaders, fallbacks: blockFallbacks, layouts });
|
|
272
277
|
|
|
273
278
|
export const generateStaticParams = route.generateStaticParams;
|
|
@@ -451,7 +456,7 @@ export async function runInit(args: CliArgs): Promise<string[]> {
|
|
|
451
456
|
notes.push("no @backmatter/next dependency: skipped the catch-all route");
|
|
452
457
|
}
|
|
453
458
|
|
|
454
|
-
for (const dir of ["models", "content", "blocks", "mdx", "layouts"]) {
|
|
459
|
+
for (const dir of ["models", "content", "blocks", "richtext", "mdx", "layouts"]) {
|
|
455
460
|
await mkdir(path.join(root, dir), { recursive: true });
|
|
456
461
|
}
|
|
457
462
|
|
|
@@ -18,6 +18,8 @@ export type CreateBackmatterOptions<Models extends readonly ModelDef[] = readonl
|
|
|
18
18
|
BackmatterConfig & {
|
|
19
19
|
models: Models;
|
|
20
20
|
blocks?: readonly BlockDef[];
|
|
21
|
+
/** In-content blocks (`richtext/*.tsx`). Synced separately from page blocks. */
|
|
22
|
+
richtext?: readonly BlockDef[];
|
|
21
23
|
objects?: readonly ObjectDef[];
|
|
22
24
|
/**
|
|
23
25
|
* When true, API fetches use Next cache tags instead of `cache: "no-store"`.
|
|
@@ -67,7 +69,7 @@ function toSnapshots(models: readonly ModelDef[]): ModelSnapshot[] {
|
|
|
67
69
|
export function createBackmatter<const Models extends readonly ModelDef[]>(
|
|
68
70
|
options: CreateBackmatterOptions<Models>,
|
|
69
71
|
): BackmatterClient<Models> {
|
|
70
|
-
const { models: modelDefs, blocks: blockDefs, objects: objectDefs, taggedApiFetch, sync = true, ...rest } = options;
|
|
72
|
+
const { models: modelDefs, blocks: blockDefs, richtext: richtextDefs, objects: objectDefs, taggedApiFetch, sync = true, ...rest } = options;
|
|
71
73
|
const config = resolveConfig(rest);
|
|
72
74
|
const models = toSnapshots(modelDefs);
|
|
73
75
|
const siteRoot = config.siteRoot ?? process.cwd();
|
|
@@ -91,6 +93,7 @@ export function createBackmatter<const Models extends readonly ModelDef[]>(
|
|
|
91
93
|
hostToken,
|
|
92
94
|
models: [...modelDefs],
|
|
93
95
|
blocks: blockDefs ? [...blockDefs] : undefined,
|
|
96
|
+
richtext: richtextDefs ? [...richtextDefs] : undefined,
|
|
94
97
|
objects: objectDefs ? [...objectDefs] : undefined,
|
|
95
98
|
branch: config.branch,
|
|
96
99
|
siteUrl,
|
package/src/node/generate.ts
CHANGED
|
@@ -31,6 +31,7 @@ export type GenerateOptions = {
|
|
|
31
31
|
outDir?: string;
|
|
32
32
|
modelsDir?: string;
|
|
33
33
|
blocksDir?: string;
|
|
34
|
+
richtextDir?: string;
|
|
34
35
|
mdxDir?: string;
|
|
35
36
|
layoutsDir?: string;
|
|
36
37
|
objectsDir?: string;
|
|
@@ -59,6 +60,7 @@ export type GenerateResult = {
|
|
|
59
60
|
changed: string[];
|
|
60
61
|
models: ModelFile[];
|
|
61
62
|
blocks: BlockFile[];
|
|
63
|
+
richtext: BlockFile[];
|
|
62
64
|
mdx: BlockFile[];
|
|
63
65
|
layouts: ModelFile[];
|
|
64
66
|
objects: ModelFile[];
|
|
@@ -161,11 +163,13 @@ export function renderSyncBarrel(input: {
|
|
|
161
163
|
outDir: string;
|
|
162
164
|
models: ModelFile[];
|
|
163
165
|
blocks: BlockFile[];
|
|
166
|
+
richtext?: BlockFile[];
|
|
164
167
|
objects?: ModelFile[];
|
|
165
168
|
configFile: string | null;
|
|
166
169
|
sdk: string;
|
|
167
170
|
}): string {
|
|
168
171
|
const objects = input.objects ?? [];
|
|
172
|
+
const richtext = input.richtext ?? [];
|
|
169
173
|
const lines = [GENERATED_HEADER, `import { createBackmatter } from "${input.sdk}";`];
|
|
170
174
|
if (input.configFile) {
|
|
171
175
|
lines.push(`import config from "${importSpecifier(input.outDir, input.configFile)}";`);
|
|
@@ -178,6 +182,9 @@ export function renderSyncBarrel(input: {
|
|
|
178
182
|
for (const b of input.blocks) {
|
|
179
183
|
lines.push(`import ${b.name} from "${importSpecifier(input.outDir, b.file)}";`);
|
|
180
184
|
}
|
|
185
|
+
for (const b of richtext) {
|
|
186
|
+
lines.push(`import richtext${b.name} from "${importSpecifier(input.outDir, b.file)}";`);
|
|
187
|
+
}
|
|
181
188
|
for (const object of objects) {
|
|
182
189
|
lines.push(`import ${object.importName} from "${importSpecifier(input.outDir, object.file)}";`);
|
|
183
190
|
}
|
|
@@ -187,10 +194,16 @@ export function renderSyncBarrel(input: {
|
|
|
187
194
|
lines.push("");
|
|
188
195
|
lines.push(`export const blocks = [${input.blocks.map((b) => b.name).join(", ")}] as const;`);
|
|
189
196
|
lines.push("");
|
|
197
|
+
lines.push(
|
|
198
|
+
`export const richtext = [${richtext.map((b) => `richtext${b.name}`).join(", ")}] as const;`,
|
|
199
|
+
);
|
|
200
|
+
lines.push("");
|
|
190
201
|
lines.push(`export const objects = [${objects.map((o) => o.importName).join(", ")}] as const;`);
|
|
191
202
|
lines.push("");
|
|
192
203
|
lines.push("/** Typed content client: cms.query.{type}, cms.getDoc, cms.listDocs. */");
|
|
193
|
-
lines.push(
|
|
204
|
+
lines.push(
|
|
205
|
+
"export const cms = createBackmatter({ ...config, models, blocks, richtext, objects, sync: true });",
|
|
206
|
+
);
|
|
194
207
|
return `${lines.join("\n")}\n`;
|
|
195
208
|
}
|
|
196
209
|
|
|
@@ -206,6 +219,18 @@ export function renderMdxBarrel(input: { outDir: string; mdx: BlockFile[] }): st
|
|
|
206
219
|
return `${lines.join("\n")}\n`;
|
|
207
220
|
}
|
|
208
221
|
|
|
222
|
+
export function renderRichtextBarrel(input: { outDir: string; richtext: BlockFile[] }): string {
|
|
223
|
+
const lines = [GENERATED_HEADER];
|
|
224
|
+
for (const b of input.richtext) {
|
|
225
|
+
lines.push(`import ${b.name} from "${importSpecifier(input.outDir, b.file)}";`);
|
|
226
|
+
}
|
|
227
|
+
lines.push("");
|
|
228
|
+
lines.push("/** In-content blocks: <Image /> in MDX resolves to richtext/Image.tsx. */");
|
|
229
|
+
const entries = input.richtext.map((b) => `${b.name}: ${b.name}.component`);
|
|
230
|
+
lines.push(`export const richtextComponents = ${entries.length ? `{ ${entries.join(", ")} }` : "{}"};`);
|
|
231
|
+
return `${lines.join("\n")}\n`;
|
|
232
|
+
}
|
|
233
|
+
|
|
209
234
|
export function renderBlocksBarrel(input: { outDir: string; blocks: BlockFile[] }): string {
|
|
210
235
|
const lines = [GENERATED_HEADER, ""];
|
|
211
236
|
lines.push('/** Composer loaders: JSON `{ type: "Hero" }` → dynamic import of blocks/Hero.tsx. */');
|
|
@@ -257,6 +282,7 @@ export const REGISTRY_FILES = [
|
|
|
257
282
|
"blocks.ts",
|
|
258
283
|
"block-fallbacks.ts",
|
|
259
284
|
"mdx.ts",
|
|
285
|
+
"richtext.ts",
|
|
260
286
|
"layouts.ts",
|
|
261
287
|
"sync.ts",
|
|
262
288
|
] as const;
|
|
@@ -269,6 +295,10 @@ export function generate(options: GenerateOptions): GenerateResult {
|
|
|
269
295
|
root,
|
|
270
296
|
options.blocksDir ?? DEFAULT_DIRS.blocks,
|
|
271
297
|
);
|
|
298
|
+
const { blocks: richtext, skipped: skippedRichtext } = listBlockFiles(
|
|
299
|
+
root,
|
|
300
|
+
options.richtextDir ?? DEFAULT_DIRS.richtext,
|
|
301
|
+
);
|
|
272
302
|
const fallbacks = listBlockFallbackFiles(root, options.blocksDir ?? DEFAULT_DIRS.blocks);
|
|
273
303
|
const { blocks: mdx, skipped: skippedMdx } = listMdxFiles(
|
|
274
304
|
root,
|
|
@@ -285,8 +315,9 @@ export function generate(options: GenerateOptions): GenerateResult {
|
|
|
285
315
|
"blocks.ts": renderBlocksBarrel({ outDir, blocks }),
|
|
286
316
|
"block-fallbacks.ts": renderBlockFallbacksBarrel({ outDir, fallbacks }),
|
|
287
317
|
"mdx.ts": renderMdxBarrel({ outDir, mdx }),
|
|
318
|
+
"richtext.ts": renderRichtextBarrel({ outDir, richtext }),
|
|
288
319
|
"layouts.ts": renderLayoutsBarrel({ outDir, layouts }),
|
|
289
|
-
"sync.ts": renderSyncBarrel({ outDir, models, blocks, objects, configFile, sdk }),
|
|
320
|
+
"sync.ts": renderSyncBarrel({ outDir, models, blocks, richtext, objects, configFile, sdk }),
|
|
290
321
|
};
|
|
291
322
|
const outputs: GeneratedFile[] = REGISTRY_FILES.map((name) => {
|
|
292
323
|
const file = path.join(outDir, name);
|
|
@@ -304,10 +335,11 @@ export function generate(options: GenerateOptions): GenerateResult {
|
|
|
304
335
|
changed: outputs.filter((o) => o.changed).map((o) => o.rel),
|
|
305
336
|
models,
|
|
306
337
|
blocks,
|
|
338
|
+
richtext,
|
|
307
339
|
mdx,
|
|
308
340
|
layouts,
|
|
309
341
|
objects,
|
|
310
|
-
skippedBlocks,
|
|
342
|
+
skippedBlocks: [...skippedBlocks, ...skippedRichtext],
|
|
311
343
|
skippedMdx,
|
|
312
344
|
configFile,
|
|
313
345
|
};
|
package/src/node/index.ts
CHANGED
package/src/sync-schema.ts
CHANGED
|
@@ -23,6 +23,8 @@ export type SyncSchemaOptions = {
|
|
|
23
23
|
hostToken: string;
|
|
24
24
|
models: Array<ModelDef | ModelSnapshot>;
|
|
25
25
|
blocks?: BlockDef[];
|
|
26
|
+
/** In-content blocks from the client `richtext/` directory. */
|
|
27
|
+
richtext?: BlockDef[];
|
|
26
28
|
objects?: ObjectDef[];
|
|
27
29
|
branch?: string;
|
|
28
30
|
siteUrl?: string;
|
|
@@ -34,6 +36,7 @@ export async function syncSchema(options: SyncSchemaOptions): Promise<void> {
|
|
|
34
36
|
options.models as ModelDef[],
|
|
35
37
|
options.blocks,
|
|
36
38
|
options.objects,
|
|
39
|
+
options.richtext,
|
|
37
40
|
);
|
|
38
41
|
const body: { payload: ReturnType<typeof serializeSchema>; branch: string; siteUrl?: string } = {
|
|
39
42
|
payload,
|