contentbit 0.1.0 → 0.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/bin.js +293 -44
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +302 -37
- package/dist/load-registry.d.ts.map +1 -1
- package/dist/load-registry.js +10 -1
- package/dist/run.d.ts +1 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +293 -44
- package/package.json +6 -3
package/dist/commands/init.js
CHANGED
|
@@ -1,32 +1,80 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
2
3
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
3
4
|
import { join } from 'node:path';
|
|
4
5
|
import { parseArgs } from 'node:util';
|
|
5
6
|
import { loadRegistry } from '../load-registry.js';
|
|
6
7
|
const TARGETS = ['react', 'html', 'markdown'];
|
|
7
|
-
|
|
8
|
+
/** Markdown library choices per target; the first entry is the default. */
|
|
9
|
+
const MD_CHOICES = {
|
|
10
|
+
react: ['react-markdown', 'none'],
|
|
11
|
+
html: ['marked', 'markdown-it', 'none'],
|
|
12
|
+
markdown: ['none'],
|
|
13
|
+
};
|
|
14
|
+
const REGISTRY_TEMPLATE = `// Custom block definitions for this project. The CLI and your app share
|
|
15
|
+
// this module — Node 22.18+ imports TypeScript directly:
|
|
8
16
|
//
|
|
9
|
-
// contentbit validate "content/**/*.md" --registry ./blocks/registry.
|
|
17
|
+
// contentbit validate "content/**/*.md" --registry ./blocks/registry.ts
|
|
10
18
|
//
|
|
11
|
-
//
|
|
19
|
+
// Definitions stay framework-free (the CLI and every render target use
|
|
20
|
+
// them); React components live next door in blocks/components.tsx.
|
|
12
21
|
// Docs: https://contentbit.dev/docs/guides/custom-blocks
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
import { defineBlock, markdownBody, type BlockDefinition } from '@contentbit/core'
|
|
23
|
+
import { z } from 'zod'
|
|
24
|
+
|
|
25
|
+
export const quote = defineBlock({
|
|
26
|
+
name: 'quote',
|
|
27
|
+
description: 'A pull quote with an author.',
|
|
28
|
+
props: z.object({
|
|
29
|
+
author: z.string().min(1),
|
|
30
|
+
role: z.string().optional(),
|
|
31
|
+
}),
|
|
32
|
+
content: markdownBody({ minLength: 3 }),
|
|
33
|
+
authoring: {
|
|
34
|
+
useWhen: ['Quoting a person to support a point'],
|
|
35
|
+
avoidWhen: ['Highlighting your own remark, use callout instead'],
|
|
36
|
+
example: ':::quote{author="Ada Lovelace"}\\nThe Analytical Engine weaves algebraic patterns.\\n:::',
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
export default [quote] satisfies BlockDefinition<unknown>[]
|
|
41
|
+
`;
|
|
42
|
+
/** blocks/components.tsx — React components for custom blocks, next to their definitions. */
|
|
43
|
+
function blockComponentsTemplate(styled) {
|
|
44
|
+
const body = styled
|
|
45
|
+
? ` return (
|
|
46
|
+
<figure className="my-6 border-s-2 ps-4">
|
|
47
|
+
<blockquote className="text-lg italic">{ctx.renderMarkdown(data.markdown)}</blockquote>
|
|
48
|
+
<figcaption className="text-muted-foreground mt-2 text-sm">
|
|
49
|
+
— {String(node.props.author)}
|
|
50
|
+
{node.props.role ? \`, \${String(node.props.role)}\` : null}
|
|
51
|
+
</figcaption>
|
|
52
|
+
</figure>
|
|
53
|
+
)`
|
|
54
|
+
: ` return (
|
|
55
|
+
<figure style={{ margin: '1.5rem 0', borderLeft: '2px solid #d4d4d4', paddingLeft: '1rem' }}>
|
|
56
|
+
<blockquote style={{ fontStyle: 'italic' }}>{ctx.renderMarkdown(data.markdown)}</blockquote>
|
|
57
|
+
<figcaption style={{ marginTop: '0.5rem', fontSize: '0.875rem', opacity: 0.7 }}>
|
|
58
|
+
— {String(node.props.author)}
|
|
59
|
+
{node.props.role ? \`, \${String(node.props.role)}\` : null}
|
|
60
|
+
</figcaption>
|
|
61
|
+
</figure>
|
|
62
|
+
)`;
|
|
63
|
+
return `import type { BlockComponent, BlockComponentProps } from '@contentbit/react'
|
|
64
|
+
|
|
65
|
+
// One React component per custom block, keyed by block name. Definitions
|
|
66
|
+
// live in ./registry.ts — add a block there, add its component here, and
|
|
67
|
+
// the rest of the app never changes.
|
|
68
|
+
function QuoteBlock({ node, ctx }: BlockComponentProps) {
|
|
69
|
+
const data = node.data as { markdown: string }
|
|
70
|
+
${body}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const blockComponents: Record<string, BlockComponent> = {
|
|
74
|
+
quote: QuoteBlock,
|
|
75
|
+
}
|
|
29
76
|
`;
|
|
77
|
+
}
|
|
30
78
|
const EXAMPLE_CONTENT = `# Hello, Content Blocks
|
|
31
79
|
|
|
32
80
|
Regular Markdown works everywhere. Blocks add validated structure:
|
|
@@ -40,26 +88,144 @@ Run the validate script and you will get file:line:col diagnostics.
|
|
|
40
88
|
2. Run \`contentbit validate "content/**/*.md"\`.
|
|
41
89
|
3. Render it with the target you picked at init.
|
|
42
90
|
:::
|
|
91
|
+
|
|
92
|
+
This one is a **custom block**, defined in \`blocks/registry.ts\` and rendered
|
|
93
|
+
by the \`QuoteBlock\` component, in about twenty lines:
|
|
94
|
+
|
|
95
|
+
:::quote{author="Ada Lovelace" role="Notes on the Analytical Engine, 1843"}
|
|
96
|
+
The Analytical Engine weaves algebraic patterns just as the Jacquard loom
|
|
97
|
+
weaves flowers and leaves.
|
|
98
|
+
:::
|
|
43
99
|
`;
|
|
44
|
-
|
|
100
|
+
/** The wrapper component: styled pack or headless, with or without a Markdown lib. */
|
|
101
|
+
function reactComponent(styled, mdWired, blocksImport) {
|
|
102
|
+
const mdImport = mdWired ? "import ReactMarkdown from 'react-markdown'\n" : '';
|
|
103
|
+
const mdProp = mdWired
|
|
104
|
+
? '\n renderMarkdown={(md) => <ReactMarkdown>{md}</ReactMarkdown>}'
|
|
105
|
+
: `\n // TODO: plug your Markdown library in here, e.g. react-markdown.
|
|
106
|
+
// One function renders all prose: https://contentbit.dev/docs/guides/markdown
|
|
107
|
+
// renderMarkdown={(md) => <Markdown source={md} />}`;
|
|
108
|
+
const rendererImport = styled
|
|
109
|
+
? `\n// The styled pack installed by shadcn. Yours to edit.
|
|
110
|
+
import { ContentRenderer } from '@/components/content-blocks/content-renderer'`
|
|
111
|
+
: '';
|
|
112
|
+
const renderer = styled ? 'ContentRenderer' : 'ContentBlocks';
|
|
113
|
+
const reactImport = styled ? '' : "import { ContentBlocks } from '@contentbit/react'\n";
|
|
114
|
+
return `'use client'
|
|
115
|
+
|
|
116
|
+
import { genericBlocks } from '@contentbit/blocks'
|
|
45
117
|
import { createBlockRegistry, parseDocument, validateDocument } from '@contentbit/core'
|
|
46
|
-
|
|
118
|
+
${reactImport}${mdImport}${rendererImport}
|
|
119
|
+
// Everything block-related lives in the blocks/ folder: definitions in
|
|
120
|
+
// registry.ts (shared with the validate CLI), components in components.tsx.
|
|
121
|
+
import customBlocks from '${blocksImport}/registry'
|
|
122
|
+
import { blockComponents } from '${blocksImport}/components'
|
|
47
123
|
|
|
48
|
-
const registry = createBlockRegistry().use(genericBlocks())
|
|
124
|
+
const registry = createBlockRegistry().use(genericBlocks()).use(customBlocks)
|
|
49
125
|
|
|
50
126
|
export function Content({ source }: { source: string }) {
|
|
51
127
|
const result = validateDocument(parseDocument(source), registry)
|
|
52
128
|
return (
|
|
53
|
-
|
|
129
|
+
<${renderer}
|
|
54
130
|
document={result.document}
|
|
55
|
-
|
|
56
|
-
// One function renders all prose: https://contentbit.dev/docs/guides/markdown
|
|
57
|
-
// renderMarkdown={(md) => <Markdown source={md} />}
|
|
131
|
+
components={blockComponents}${mdProp}
|
|
58
132
|
/>
|
|
59
133
|
)
|
|
60
134
|
}
|
|
61
135
|
`;
|
|
62
|
-
|
|
136
|
+
}
|
|
137
|
+
function htmlRenderScript(md) {
|
|
138
|
+
const wiring = md === 'marked'
|
|
139
|
+
? `import { marked } from 'marked'
|
|
140
|
+
|
|
141
|
+
const renderMarkdown = (md) => marked.parse(md, { async: false })`
|
|
142
|
+
: md === 'markdown-it'
|
|
143
|
+
? `import MarkdownIt from 'markdown-it'
|
|
144
|
+
|
|
145
|
+
const mdIt = new MarkdownIt() // html: false by default — raw HTML stays escaped
|
|
146
|
+
const renderMarkdown = (md) => mdIt.render(md)`
|
|
147
|
+
: `// TODO: plug a Markdown library in here (marked, markdown-it, remark).
|
|
148
|
+
const renderMarkdown = undefined`;
|
|
149
|
+
return `// Render content/example.md to example.html. Run: node scripts/render-example.mjs
|
|
150
|
+
import { genericBlocks } from '@contentbit/blocks'
|
|
151
|
+
import { createBlockRegistry, parseDocument, validateDocument } from '@contentbit/core'
|
|
152
|
+
import { renderToHtml } from '@contentbit/html'
|
|
153
|
+
import { readFile, writeFile } from 'node:fs/promises'
|
|
154
|
+
${wiring}
|
|
155
|
+
|
|
156
|
+
const source = await readFile('content/example.md', 'utf8')
|
|
157
|
+
const registry = createBlockRegistry().use(genericBlocks())
|
|
158
|
+
const result = validateDocument(parseDocument(source), registry)
|
|
159
|
+
const html = renderToHtml(result.document, { renderMarkdown })
|
|
160
|
+
await writeFile('example.html', html, 'utf8')
|
|
161
|
+
console.log('wrote example.html')
|
|
162
|
+
`;
|
|
163
|
+
}
|
|
164
|
+
/** Where the component and example page belong for the detected framework. */
|
|
165
|
+
function detectFramework(cwd, deps) {
|
|
166
|
+
if ((deps['@tanstack/react-start'] || deps['@tanstack/react-router']) &&
|
|
167
|
+
existsSync(join(cwd, 'src/routes'))) {
|
|
168
|
+
return {
|
|
169
|
+
framework: 'tanstack',
|
|
170
|
+
componentPath: 'src/components/content-blocks.tsx',
|
|
171
|
+
pagePath: 'src/routes/example.tsx',
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
if (deps.next) {
|
|
175
|
+
const appDir = existsSync(join(cwd, 'src/app')) ? 'src/app' : 'app';
|
|
176
|
+
if (existsSync(join(cwd, appDir))) {
|
|
177
|
+
return {
|
|
178
|
+
framework: 'next',
|
|
179
|
+
componentPath: 'components/content-blocks.tsx',
|
|
180
|
+
pagePath: `${appDir}/example/page.tsx`,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return { framework: null, componentPath: 'components/content-blocks.tsx', pagePath: null };
|
|
185
|
+
}
|
|
186
|
+
const TANSTACK_PAGE = `import { createFileRoute } from '@tanstack/react-router'
|
|
187
|
+
|
|
188
|
+
import { Content } from '../components/content-blocks'
|
|
189
|
+
// Vite's ?raw import inlines the Markdown as a string at build time.
|
|
190
|
+
import source from '../../content/example.md?raw'
|
|
191
|
+
|
|
192
|
+
export const Route = createFileRoute('/example')({ component: ExamplePage })
|
|
193
|
+
|
|
194
|
+
function ExamplePage() {
|
|
195
|
+
return (
|
|
196
|
+
<main style={{ maxWidth: '42rem', margin: '0 auto', padding: '3rem 1.5rem' }}>
|
|
197
|
+
<Content source={source} />
|
|
198
|
+
</main>
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
`;
|
|
202
|
+
const NEXT_PAGE = `import { readFile } from 'node:fs/promises'
|
|
203
|
+
|
|
204
|
+
// If your project has no "@/" path alias, switch to a relative import.
|
|
205
|
+
import { Content } from '@/components/content-blocks'
|
|
206
|
+
|
|
207
|
+
export default async function ExamplePage() {
|
|
208
|
+
const source = await readFile('content/example.md', 'utf8')
|
|
209
|
+
return (
|
|
210
|
+
<main style={{ maxWidth: '42rem', margin: '0 auto', padding: '3rem 1.5rem' }}>
|
|
211
|
+
<Content source={source} />
|
|
212
|
+
</main>
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
`;
|
|
216
|
+
function detectPackageManager(cwd) {
|
|
217
|
+
// The project's lockfile outranks however the CLI itself was launched.
|
|
218
|
+
const locks = [
|
|
219
|
+
['pnpm-lock.yaml', 'pnpm'],
|
|
220
|
+
['yarn.lock', 'yarn'],
|
|
221
|
+
['bun.lock', 'bun'],
|
|
222
|
+
['bun.lockb', 'bun'],
|
|
223
|
+
['package-lock.json', 'npm'],
|
|
224
|
+
];
|
|
225
|
+
for (const [file, pm] of locks) {
|
|
226
|
+
if (existsSync(join(cwd, file)))
|
|
227
|
+
return pm;
|
|
228
|
+
}
|
|
63
229
|
const agent = process.env.npm_config_user_agent ?? '';
|
|
64
230
|
for (const pm of ['pnpm', 'yarn', 'bun']) {
|
|
65
231
|
if (agent.startsWith(pm))
|
|
@@ -71,6 +237,15 @@ function installArgs(pm, dev, pkgs) {
|
|
|
71
237
|
const add = pm === 'npm' ? 'install' : 'add';
|
|
72
238
|
return dev ? [add, '-D', ...pkgs] : [add, ...pkgs];
|
|
73
239
|
}
|
|
240
|
+
function dlxCommand(pm) {
|
|
241
|
+
if (pm === 'pnpm')
|
|
242
|
+
return ['pnpm', ['dlx']];
|
|
243
|
+
if (pm === 'yarn')
|
|
244
|
+
return ['yarn', ['dlx']];
|
|
245
|
+
if (pm === 'bun')
|
|
246
|
+
return ['bunx', []];
|
|
247
|
+
return ['npx', ['--yes']];
|
|
248
|
+
}
|
|
74
249
|
function runInstall(pm, args, cwd) {
|
|
75
250
|
return new Promise((resolve) => {
|
|
76
251
|
const child = spawn(pm, args, { cwd, stdio: 'inherit', shell: process.platform === 'win32' });
|
|
@@ -95,9 +270,12 @@ export async function initCommand(args, io) {
|
|
|
95
270
|
args,
|
|
96
271
|
options: {
|
|
97
272
|
target: { type: 'string', short: 't' },
|
|
273
|
+
md: { type: 'string' },
|
|
98
274
|
yes: { type: 'boolean', short: 'y', default: false },
|
|
99
275
|
cwd: { type: 'string', default: process.cwd() },
|
|
100
276
|
'no-install': { type: 'boolean', default: false },
|
|
277
|
+
'no-page': { type: 'boolean', default: false },
|
|
278
|
+
'no-styled': { type: 'boolean', default: false },
|
|
101
279
|
},
|
|
102
280
|
});
|
|
103
281
|
const cwd = values.cwd;
|
|
@@ -140,17 +318,48 @@ export async function initCommand(args, io) {
|
|
|
140
318
|
else {
|
|
141
319
|
target = detected;
|
|
142
320
|
}
|
|
321
|
+
// Resolve the Markdown library: flag > prompt (interactive) > target default.
|
|
322
|
+
// The default gives working prose rendering out of the box; 'none' opts out.
|
|
323
|
+
const choices = MD_CHOICES[target];
|
|
324
|
+
let md;
|
|
325
|
+
if (values.md) {
|
|
326
|
+
if (!choices.includes(values.md)) {
|
|
327
|
+
io.stderr(`Unknown markdown library "${values.md}". Use one of: ${choices.join(', ')}`);
|
|
328
|
+
return 2;
|
|
329
|
+
}
|
|
330
|
+
md = values.md;
|
|
331
|
+
}
|
|
332
|
+
else if (choices.length > 1 && !values.yes && process.stdin.isTTY && process.stdout.isTTY) {
|
|
333
|
+
const { isCancel, select } = await import('@clack/prompts');
|
|
334
|
+
const answer = await select({
|
|
335
|
+
message: 'Markdown library for prose rendering?',
|
|
336
|
+
initialValue: choices[0],
|
|
337
|
+
options: choices.map((c) => ({
|
|
338
|
+
value: c,
|
|
339
|
+
label: c,
|
|
340
|
+
hint: c === 'none' ? 'wire one yourself later' : 'installed and wired for you',
|
|
341
|
+
})),
|
|
342
|
+
});
|
|
343
|
+
if (isCancel(answer))
|
|
344
|
+
return 1;
|
|
345
|
+
md = answer;
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
md = choices[0];
|
|
349
|
+
}
|
|
143
350
|
// Install runtime packages plus the CLI as a dev dependency.
|
|
144
|
-
const runtime = ['@contentbit/core', '@contentbit/blocks'];
|
|
351
|
+
const runtime = ['@contentbit/core', '@contentbit/blocks', 'zod'];
|
|
145
352
|
if (target === 'react')
|
|
146
353
|
runtime.push('@contentbit/react');
|
|
147
354
|
if (target === 'html')
|
|
148
355
|
runtime.push('@contentbit/html');
|
|
356
|
+
if (md !== 'none')
|
|
357
|
+
runtime.push(md);
|
|
149
358
|
if (values['no-install']) {
|
|
150
359
|
io.stdout(`skipped install: ${runtime.join(' ')} + contentbit (dev)`);
|
|
151
360
|
}
|
|
152
361
|
else {
|
|
153
|
-
const pm = detectPackageManager();
|
|
362
|
+
const pm = detectPackageManager(cwd);
|
|
154
363
|
io.stdout(`installing with ${pm}: ${runtime.join(' ')}`);
|
|
155
364
|
if ((await runInstall(pm, installArgs(pm, false, runtime), cwd)) !== 0) {
|
|
156
365
|
io.stderr('install failed');
|
|
@@ -163,11 +372,54 @@ export async function initCommand(args, io) {
|
|
|
163
372
|
}
|
|
164
373
|
// Scaffold project files; never overwrite.
|
|
165
374
|
const files = [
|
|
166
|
-
['blocks/registry.
|
|
375
|
+
['blocks/registry.ts', REGISTRY_TEMPLATE],
|
|
167
376
|
['content/example.md', EXAMPLE_CONTENT],
|
|
168
377
|
];
|
|
169
|
-
|
|
170
|
-
|
|
378
|
+
const layout = detectFramework(cwd, { ...pkg.dependencies, ...pkg.devDependencies });
|
|
379
|
+
// shadcn project? Pull the styled component pack from the contentbit registry.
|
|
380
|
+
let styled = false;
|
|
381
|
+
const componentsJsonPath = join(cwd, 'components.json');
|
|
382
|
+
if (target === 'react' && !values['no-styled'] && existsSync(componentsJsonPath)) {
|
|
383
|
+
const componentsJson = JSON.parse(await readFile(componentsJsonPath, 'utf8'));
|
|
384
|
+
componentsJson.registries ??= {};
|
|
385
|
+
if (!componentsJson.registries['@contentbit']) {
|
|
386
|
+
componentsJson.registries['@contentbit'] = 'https://contentbit.dev/r/{name}.json';
|
|
387
|
+
await writeFile(componentsJsonPath, `${JSON.stringify(componentsJson, null, 2)}\n`, 'utf8');
|
|
388
|
+
io.stdout('added @contentbit registry to components.json');
|
|
389
|
+
}
|
|
390
|
+
if (values['no-install']) {
|
|
391
|
+
io.stdout('skipped: shadcn add @contentbit/generic-pack');
|
|
392
|
+
styled = true;
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
const [bin, prefix] = dlxCommand(detectPackageManager(cwd));
|
|
396
|
+
io.stdout('installing the styled pack: shadcn add @contentbit/generic-pack');
|
|
397
|
+
const code = await runInstall(bin, [...prefix, 'shadcn@latest', 'add', '@contentbit/generic-pack', '--yes'], cwd);
|
|
398
|
+
if (code === 0)
|
|
399
|
+
styled = true;
|
|
400
|
+
else
|
|
401
|
+
io.stderr('styled pack install failed; falling back to headless defaults');
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
if (target === 'react') {
|
|
405
|
+
const depth = layout.componentPath.split('/').length - 1;
|
|
406
|
+
const blocksImport = `${'../'.repeat(depth)}blocks`;
|
|
407
|
+
files.push(['blocks/components.tsx', blockComponentsTemplate(styled)]);
|
|
408
|
+
files.push([
|
|
409
|
+
layout.componentPath,
|
|
410
|
+
reactComponent(styled, md === 'react-markdown', blocksImport),
|
|
411
|
+
]);
|
|
412
|
+
// A visible page in the framework's own routing convention.
|
|
413
|
+
if (!values['no-page'] && layout.pagePath) {
|
|
414
|
+
files.push([layout.pagePath, layout.framework === 'tanstack' ? TANSTACK_PAGE : NEXT_PAGE]);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (target === 'html') {
|
|
418
|
+
files.push([
|
|
419
|
+
'scripts/render-example.mjs',
|
|
420
|
+
htmlRenderScript(md),
|
|
421
|
+
]);
|
|
422
|
+
}
|
|
171
423
|
for (const [rel, content] of files) {
|
|
172
424
|
const result = await scaffold(join(cwd, rel), content);
|
|
173
425
|
io.stdout(`${result}: ${rel}`);
|
|
@@ -177,24 +429,37 @@ export async function initCommand(args, io) {
|
|
|
177
429
|
fresh.scripts ??= {};
|
|
178
430
|
if (!fresh.scripts['content:check']) {
|
|
179
431
|
fresh.scripts['content:check'] =
|
|
180
|
-
'contentbit validate "content/**/*.md" --registry ./blocks/registry.
|
|
432
|
+
'contentbit validate "content/**/*.md" --registry ./blocks/registry.ts';
|
|
181
433
|
await writeFile(pkgPath, `${JSON.stringify(fresh, null, 2)}\n`, 'utf8');
|
|
182
434
|
io.stdout('added script: content:check');
|
|
183
435
|
}
|
|
184
436
|
// Generate the LLM authoring guide from the registry, ready to paste into a prompt.
|
|
185
|
-
|
|
437
|
+
let registry;
|
|
438
|
+
try {
|
|
439
|
+
// Include the scaffolded custom blocks so the guide covers them too.
|
|
440
|
+
registry = await loadRegistry(join(cwd, 'blocks/registry.ts'));
|
|
441
|
+
}
|
|
442
|
+
catch {
|
|
443
|
+
registry = await loadRegistry(); // packages not installed yet (--no-install)
|
|
444
|
+
}
|
|
186
445
|
const guide = registry.toAuthoringGuide({ audience: 'llm', includeExamples: true });
|
|
187
446
|
await writeFile(join(cwd, 'contentbit-guide.md'), guide, 'utf8');
|
|
188
447
|
io.stdout('created: contentbit-guide.md (LLM authoring instructions)');
|
|
189
448
|
io.stdout('');
|
|
190
449
|
io.stdout('Done. Next steps:');
|
|
191
|
-
io.stdout(` 1. Validate the starter content: ${detectPackageManager()} run content:check`);
|
|
450
|
+
io.stdout(` 1. Validate the starter content: ${detectPackageManager(cwd)} run content:check`);
|
|
192
451
|
if (target === 'react') {
|
|
193
|
-
|
|
452
|
+
if (!values['no-page'] && layout.pagePath) {
|
|
453
|
+
io.stdout(' 2. Start the dev server and open /example to see the article rendered.');
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
io.stdout(' 2. Render it: import { Content } from "./components/content-blocks"');
|
|
457
|
+
io.stdout(' <Content source={...content/example.md as a string} />');
|
|
458
|
+
}
|
|
194
459
|
io.stdout(' 3. Styled components: pnpm dlx shadcn@latest add @contentbit/generic-pack');
|
|
195
460
|
}
|
|
196
461
|
else if (target === 'html') {
|
|
197
|
-
io.stdout(' 2. Render it:
|
|
462
|
+
io.stdout(' 2. Render it: node scripts/render-example.mjs && open example.html');
|
|
198
463
|
}
|
|
199
464
|
else {
|
|
200
465
|
io.stdout(' 2. Render it: contentbit render content/example.md --target markdown');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load-registry.d.ts","sourceRoot":"","sources":["../src/load-registry.ts"],"names":[],"mappings":"AACA,OAAO,EAA6C,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAGhG,+EAA+E;AAC/E,wBAAsB,YAAY,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,
|
|
1
|
+
{"version":3,"file":"load-registry.d.ts","sourceRoot":"","sources":["../src/load-registry.ts"],"names":[],"mappings":"AACA,OAAO,EAA6C,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAGhG,+EAA+E;AAC/E,wBAAsB,YAAY,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAsBhF"}
|
package/dist/load-registry.js
CHANGED
|
@@ -5,7 +5,16 @@ import { pathToFileURL } from 'node:url';
|
|
|
5
5
|
export async function loadRegistry(registryPath) {
|
|
6
6
|
const registry = createBlockRegistry().use(genericBlocks());
|
|
7
7
|
if (registryPath) {
|
|
8
|
-
|
|
8
|
+
let mod;
|
|
9
|
+
try {
|
|
10
|
+
mod = (await import(pathToFileURL(registryPath).href));
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
if (err instanceof Error && 'code' in err && err.code === 'ERR_UNKNOWN_FILE_EXTENSION') {
|
|
14
|
+
throw new Error(`Importing a TypeScript registry needs Node 22.18+ (native type stripping): ${registryPath}`);
|
|
15
|
+
}
|
|
16
|
+
throw err;
|
|
17
|
+
}
|
|
9
18
|
if (!Array.isArray(mod.default)) {
|
|
10
19
|
throw new Error(`--registry module must default-export an array of block definitions: ${registryPath}`);
|
|
11
20
|
}
|
package/dist/run.d.ts
CHANGED
|
@@ -3,6 +3,6 @@ export interface Io {
|
|
|
3
3
|
stderr(line: string): void;
|
|
4
4
|
writeFile(path: string, content: string): Promise<void>;
|
|
5
5
|
}
|
|
6
|
-
export declare const USAGE = "Usage: contentbit <init|validate|render|instructions|docs> [options]\n\n init [-t react|html|markdown] [-y] [--no-install]\n\n validate <globs...> [--registry <module.mjs>] [--strict-warnings]\n render <file> --target html|markdown [--registry <module.mjs>] [--out <file>]\n instructions [--audience llm|human] [--no-examples] [--registry <module.mjs>] [--out <file>]\n docs [--registry <module.mjs>] [--out <file>]";
|
|
6
|
+
export declare const USAGE = "Usage: contentbit <init|validate|render|instructions|docs> [options]\n\n init [-t react|html|markdown] [--md ...] [-y] [--no-install] [--no-page]\n\n validate <globs...> [--registry <module.mjs>] [--strict-warnings]\n render <file> --target html|markdown [--registry <module.mjs>] [--out <file>]\n instructions [--audience llm|human] [--no-examples] [--registry <module.mjs>] [--out <file>]\n docs [--registry <module.mjs>] [--out <file>]";
|
|
7
7
|
export declare function run(argv: string[], io: Io): Promise<number>;
|
|
8
8
|
//# sourceMappingURL=run.d.ts.map
|
package/dist/run.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,EAAE;IACjB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACxD;AAED,eAAO,MAAM,KAAK,
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,EAAE;IACjB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACxD;AAED,eAAO,MAAM,KAAK,gcAO8B,CAAA;AAYhD,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAcjE"}
|