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/run.js
CHANGED
|
@@ -16153,7 +16153,17 @@ import { pathToFileURL } from "node:url";
|
|
|
16153
16153
|
async function loadRegistry(registryPath) {
|
|
16154
16154
|
const registry2 = createBlockRegistry().use(genericBlocks());
|
|
16155
16155
|
if (registryPath) {
|
|
16156
|
-
|
|
16156
|
+
let mod;
|
|
16157
|
+
try {
|
|
16158
|
+
mod = await import(pathToFileURL(registryPath).href);
|
|
16159
|
+
} catch (err) {
|
|
16160
|
+
if (err instanceof Error && "code" in err && err.code === "ERR_UNKNOWN_FILE_EXTENSION") {
|
|
16161
|
+
throw new Error(
|
|
16162
|
+
`Importing a TypeScript registry needs Node 22.18+ (native type stripping): ${registryPath}`
|
|
16163
|
+
);
|
|
16164
|
+
}
|
|
16165
|
+
throw err;
|
|
16166
|
+
}
|
|
16157
16167
|
if (!Array.isArray(mod.default)) {
|
|
16158
16168
|
throw new Error(
|
|
16159
16169
|
`--registry module must default-export an array of block definitions: ${registryPath}`
|
|
@@ -16177,10 +16187,131 @@ __export(init_exports, {
|
|
|
16177
16187
|
initCommand: () => initCommand
|
|
16178
16188
|
});
|
|
16179
16189
|
import { spawn } from "node:child_process";
|
|
16190
|
+
import { existsSync } from "node:fs";
|
|
16180
16191
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
16181
16192
|
import { join } from "node:path";
|
|
16182
16193
|
import { parseArgs } from "node:util";
|
|
16183
|
-
function
|
|
16194
|
+
function blockComponentsTemplate(styled) {
|
|
16195
|
+
const body = styled ? ` return (
|
|
16196
|
+
<figure className="my-6 border-s-2 ps-4">
|
|
16197
|
+
<blockquote className="text-lg italic">{ctx.renderMarkdown(data.markdown)}</blockquote>
|
|
16198
|
+
<figcaption className="text-muted-foreground mt-2 text-sm">
|
|
16199
|
+
\u2014 {String(node.props.author)}
|
|
16200
|
+
{node.props.role ? \`, \${String(node.props.role)}\` : null}
|
|
16201
|
+
</figcaption>
|
|
16202
|
+
</figure>
|
|
16203
|
+
)` : ` return (
|
|
16204
|
+
<figure style={{ margin: '1.5rem 0', borderLeft: '2px solid #d4d4d4', paddingLeft: '1rem' }}>
|
|
16205
|
+
<blockquote style={{ fontStyle: 'italic' }}>{ctx.renderMarkdown(data.markdown)}</blockquote>
|
|
16206
|
+
<figcaption style={{ marginTop: '0.5rem', fontSize: '0.875rem', opacity: 0.7 }}>
|
|
16207
|
+
\u2014 {String(node.props.author)}
|
|
16208
|
+
{node.props.role ? \`, \${String(node.props.role)}\` : null}
|
|
16209
|
+
</figcaption>
|
|
16210
|
+
</figure>
|
|
16211
|
+
)`;
|
|
16212
|
+
return `import type { BlockComponent, BlockComponentProps } from '@contentbit/react'
|
|
16213
|
+
|
|
16214
|
+
// One React component per custom block, keyed by block name. Definitions
|
|
16215
|
+
// live in ./registry.ts \u2014 add a block there, add its component here, and
|
|
16216
|
+
// the rest of the app never changes.
|
|
16217
|
+
function QuoteBlock({ node, ctx }: BlockComponentProps) {
|
|
16218
|
+
const data = node.data as { markdown: string }
|
|
16219
|
+
${body}
|
|
16220
|
+
}
|
|
16221
|
+
|
|
16222
|
+
export const blockComponents: Record<string, BlockComponent> = {
|
|
16223
|
+
quote: QuoteBlock,
|
|
16224
|
+
}
|
|
16225
|
+
`;
|
|
16226
|
+
}
|
|
16227
|
+
function reactComponent(styled, mdWired, blocksImport) {
|
|
16228
|
+
const mdImport = mdWired ? "import ReactMarkdown from 'react-markdown'\n" : "";
|
|
16229
|
+
const mdProp = mdWired ? "\n renderMarkdown={(md) => <ReactMarkdown>{md}</ReactMarkdown>}" : `
|
|
16230
|
+
// TODO: plug your Markdown library in here, e.g. react-markdown.
|
|
16231
|
+
// One function renders all prose: https://contentbit.dev/docs/guides/markdown
|
|
16232
|
+
// renderMarkdown={(md) => <Markdown source={md} />}`;
|
|
16233
|
+
const rendererImport = styled ? `
|
|
16234
|
+
// The styled pack installed by shadcn. Yours to edit.
|
|
16235
|
+
import { ContentRenderer } from '@/components/content-blocks/content-renderer'` : "";
|
|
16236
|
+
const renderer = styled ? "ContentRenderer" : "ContentBlocks";
|
|
16237
|
+
const reactImport = styled ? "" : "import { ContentBlocks } from '@contentbit/react'\n";
|
|
16238
|
+
return `'use client'
|
|
16239
|
+
|
|
16240
|
+
import { genericBlocks } from '@contentbit/blocks'
|
|
16241
|
+
import { createBlockRegistry, parseDocument, validateDocument } from '@contentbit/core'
|
|
16242
|
+
${reactImport}${mdImport}${rendererImport}
|
|
16243
|
+
// Everything block-related lives in the blocks/ folder: definitions in
|
|
16244
|
+
// registry.ts (shared with the validate CLI), components in components.tsx.
|
|
16245
|
+
import customBlocks from '${blocksImport}/registry'
|
|
16246
|
+
import { blockComponents } from '${blocksImport}/components'
|
|
16247
|
+
|
|
16248
|
+
const registry = createBlockRegistry().use(genericBlocks()).use(customBlocks)
|
|
16249
|
+
|
|
16250
|
+
export function Content({ source }: { source: string }) {
|
|
16251
|
+
const result = validateDocument(parseDocument(source), registry)
|
|
16252
|
+
return (
|
|
16253
|
+
<${renderer}
|
|
16254
|
+
document={result.document}
|
|
16255
|
+
components={blockComponents}${mdProp}
|
|
16256
|
+
/>
|
|
16257
|
+
)
|
|
16258
|
+
}
|
|
16259
|
+
`;
|
|
16260
|
+
}
|
|
16261
|
+
function htmlRenderScript(md) {
|
|
16262
|
+
const wiring = md === "marked" ? `import { marked } from 'marked'
|
|
16263
|
+
|
|
16264
|
+
const renderMarkdown = (md) => marked.parse(md, { async: false })` : md === "markdown-it" ? `import MarkdownIt from 'markdown-it'
|
|
16265
|
+
|
|
16266
|
+
const mdIt = new MarkdownIt() // html: false by default \u2014 raw HTML stays escaped
|
|
16267
|
+
const renderMarkdown = (md) => mdIt.render(md)` : `// TODO: plug a Markdown library in here (marked, markdown-it, remark).
|
|
16268
|
+
const renderMarkdown = undefined`;
|
|
16269
|
+
return `// Render content/example.md to example.html. Run: node scripts/render-example.mjs
|
|
16270
|
+
import { genericBlocks } from '@contentbit/blocks'
|
|
16271
|
+
import { createBlockRegistry, parseDocument, validateDocument } from '@contentbit/core'
|
|
16272
|
+
import { renderToHtml } from '@contentbit/html'
|
|
16273
|
+
import { readFile, writeFile } from 'node:fs/promises'
|
|
16274
|
+
${wiring}
|
|
16275
|
+
|
|
16276
|
+
const source = await readFile('content/example.md', 'utf8')
|
|
16277
|
+
const registry = createBlockRegistry().use(genericBlocks())
|
|
16278
|
+
const result = validateDocument(parseDocument(source), registry)
|
|
16279
|
+
const html = renderToHtml(result.document, { renderMarkdown })
|
|
16280
|
+
await writeFile('example.html', html, 'utf8')
|
|
16281
|
+
console.log('wrote example.html')
|
|
16282
|
+
`;
|
|
16283
|
+
}
|
|
16284
|
+
function detectFramework(cwd, deps) {
|
|
16285
|
+
if ((deps["@tanstack/react-start"] || deps["@tanstack/react-router"]) && existsSync(join(cwd, "src/routes"))) {
|
|
16286
|
+
return {
|
|
16287
|
+
framework: "tanstack",
|
|
16288
|
+
componentPath: "src/components/content-blocks.tsx",
|
|
16289
|
+
pagePath: "src/routes/example.tsx"
|
|
16290
|
+
};
|
|
16291
|
+
}
|
|
16292
|
+
if (deps.next) {
|
|
16293
|
+
const appDir = existsSync(join(cwd, "src/app")) ? "src/app" : "app";
|
|
16294
|
+
if (existsSync(join(cwd, appDir))) {
|
|
16295
|
+
return {
|
|
16296
|
+
framework: "next",
|
|
16297
|
+
componentPath: "components/content-blocks.tsx",
|
|
16298
|
+
pagePath: `${appDir}/example/page.tsx`
|
|
16299
|
+
};
|
|
16300
|
+
}
|
|
16301
|
+
}
|
|
16302
|
+
return { framework: null, componentPath: "components/content-blocks.tsx", pagePath: null };
|
|
16303
|
+
}
|
|
16304
|
+
function detectPackageManager(cwd) {
|
|
16305
|
+
const locks = [
|
|
16306
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
16307
|
+
["yarn.lock", "yarn"],
|
|
16308
|
+
["bun.lock", "bun"],
|
|
16309
|
+
["bun.lockb", "bun"],
|
|
16310
|
+
["package-lock.json", "npm"]
|
|
16311
|
+
];
|
|
16312
|
+
for (const [file2, pm] of locks) {
|
|
16313
|
+
if (existsSync(join(cwd, file2))) return pm;
|
|
16314
|
+
}
|
|
16184
16315
|
const agent = process.env.npm_config_user_agent ?? "";
|
|
16185
16316
|
for (const pm of ["pnpm", "yarn", "bun"]) {
|
|
16186
16317
|
if (agent.startsWith(pm)) return pm;
|
|
@@ -16191,6 +16322,12 @@ function installArgs(pm, dev, pkgs) {
|
|
|
16191
16322
|
const add = pm === "npm" ? "install" : "add";
|
|
16192
16323
|
return dev ? [add, "-D", ...pkgs] : [add, ...pkgs];
|
|
16193
16324
|
}
|
|
16325
|
+
function dlxCommand(pm) {
|
|
16326
|
+
if (pm === "pnpm") return ["pnpm", ["dlx"]];
|
|
16327
|
+
if (pm === "yarn") return ["yarn", ["dlx"]];
|
|
16328
|
+
if (pm === "bun") return ["bunx", []];
|
|
16329
|
+
return ["npx", ["--yes"]];
|
|
16330
|
+
}
|
|
16194
16331
|
function runInstall(pm, args, cwd) {
|
|
16195
16332
|
return new Promise((resolve) => {
|
|
16196
16333
|
const child = spawn(pm, args, { cwd, stdio: "inherit", shell: process.platform === "win32" });
|
|
@@ -16213,9 +16350,12 @@ async function initCommand(args, io) {
|
|
|
16213
16350
|
args,
|
|
16214
16351
|
options: {
|
|
16215
16352
|
target: { type: "string", short: "t" },
|
|
16353
|
+
md: { type: "string" },
|
|
16216
16354
|
yes: { type: "boolean", short: "y", default: false },
|
|
16217
16355
|
cwd: { type: "string", default: process.cwd() },
|
|
16218
|
-
"no-install": { type: "boolean", default: false }
|
|
16356
|
+
"no-install": { type: "boolean", default: false },
|
|
16357
|
+
"no-page": { type: "boolean", default: false },
|
|
16358
|
+
"no-styled": { type: "boolean", default: false }
|
|
16219
16359
|
}
|
|
16220
16360
|
});
|
|
16221
16361
|
const cwd = values.cwd;
|
|
@@ -16252,13 +16392,38 @@ async function initCommand(args, io) {
|
|
|
16252
16392
|
} else {
|
|
16253
16393
|
target = detected;
|
|
16254
16394
|
}
|
|
16255
|
-
const
|
|
16395
|
+
const choices = MD_CHOICES[target];
|
|
16396
|
+
let md;
|
|
16397
|
+
if (values.md) {
|
|
16398
|
+
if (!choices.includes(values.md)) {
|
|
16399
|
+
io.stderr(`Unknown markdown library "${values.md}". Use one of: ${choices.join(", ")}`);
|
|
16400
|
+
return 2;
|
|
16401
|
+
}
|
|
16402
|
+
md = values.md;
|
|
16403
|
+
} else if (choices.length > 1 && !values.yes && process.stdin.isTTY && process.stdout.isTTY) {
|
|
16404
|
+
const { isCancel, select } = await import("@clack/prompts");
|
|
16405
|
+
const answer = await select({
|
|
16406
|
+
message: "Markdown library for prose rendering?",
|
|
16407
|
+
initialValue: choices[0],
|
|
16408
|
+
options: choices.map((c) => ({
|
|
16409
|
+
value: c,
|
|
16410
|
+
label: c,
|
|
16411
|
+
hint: c === "none" ? "wire one yourself later" : "installed and wired for you"
|
|
16412
|
+
}))
|
|
16413
|
+
});
|
|
16414
|
+
if (isCancel(answer)) return 1;
|
|
16415
|
+
md = answer;
|
|
16416
|
+
} else {
|
|
16417
|
+
md = choices[0];
|
|
16418
|
+
}
|
|
16419
|
+
const runtime = ["@contentbit/core", "@contentbit/blocks", "zod"];
|
|
16256
16420
|
if (target === "react") runtime.push("@contentbit/react");
|
|
16257
16421
|
if (target === "html") runtime.push("@contentbit/html");
|
|
16422
|
+
if (md !== "none") runtime.push(md);
|
|
16258
16423
|
if (values["no-install"]) {
|
|
16259
16424
|
io.stdout(`skipped install: ${runtime.join(" ")} + contentbit (dev)`);
|
|
16260
16425
|
} else {
|
|
16261
|
-
const pm = detectPackageManager();
|
|
16426
|
+
const pm = detectPackageManager(cwd);
|
|
16262
16427
|
io.stdout(`installing with ${pm}: ${runtime.join(" ")}`);
|
|
16263
16428
|
if (await runInstall(pm, installArgs(pm, false, runtime), cwd) !== 0) {
|
|
16264
16429
|
io.stderr("install failed");
|
|
@@ -16270,10 +16435,54 @@ async function initCommand(args, io) {
|
|
|
16270
16435
|
}
|
|
16271
16436
|
}
|
|
16272
16437
|
const files = [
|
|
16273
|
-
["blocks/registry.
|
|
16438
|
+
["blocks/registry.ts", REGISTRY_TEMPLATE],
|
|
16274
16439
|
["content/example.md", EXAMPLE_CONTENT]
|
|
16275
16440
|
];
|
|
16276
|
-
|
|
16441
|
+
const layout = detectFramework(cwd, { ...pkg.dependencies, ...pkg.devDependencies });
|
|
16442
|
+
let styled = false;
|
|
16443
|
+
const componentsJsonPath = join(cwd, "components.json");
|
|
16444
|
+
if (target === "react" && !values["no-styled"] && existsSync(componentsJsonPath)) {
|
|
16445
|
+
const componentsJson = JSON.parse(await readFile(componentsJsonPath, "utf8"));
|
|
16446
|
+
componentsJson.registries ??= {};
|
|
16447
|
+
if (!componentsJson.registries["@contentbit"]) {
|
|
16448
|
+
componentsJson.registries["@contentbit"] = "https://contentbit.dev/r/{name}.json";
|
|
16449
|
+
await writeFile(componentsJsonPath, `${JSON.stringify(componentsJson, null, 2)}
|
|
16450
|
+
`, "utf8");
|
|
16451
|
+
io.stdout("added @contentbit registry to components.json");
|
|
16452
|
+
}
|
|
16453
|
+
if (values["no-install"]) {
|
|
16454
|
+
io.stdout("skipped: shadcn add @contentbit/generic-pack");
|
|
16455
|
+
styled = true;
|
|
16456
|
+
} else {
|
|
16457
|
+
const [bin, prefix] = dlxCommand(detectPackageManager(cwd));
|
|
16458
|
+
io.stdout("installing the styled pack: shadcn add @contentbit/generic-pack");
|
|
16459
|
+
const code = await runInstall(
|
|
16460
|
+
bin,
|
|
16461
|
+
[...prefix, "shadcn@latest", "add", "@contentbit/generic-pack", "--yes"],
|
|
16462
|
+
cwd
|
|
16463
|
+
);
|
|
16464
|
+
if (code === 0) styled = true;
|
|
16465
|
+
else io.stderr("styled pack install failed; falling back to headless defaults");
|
|
16466
|
+
}
|
|
16467
|
+
}
|
|
16468
|
+
if (target === "react") {
|
|
16469
|
+
const depth = layout.componentPath.split("/").length - 1;
|
|
16470
|
+
const blocksImport = `${"../".repeat(depth)}blocks`;
|
|
16471
|
+
files.push(["blocks/components.tsx", blockComponentsTemplate(styled)]);
|
|
16472
|
+
files.push([
|
|
16473
|
+
layout.componentPath,
|
|
16474
|
+
reactComponent(styled, md === "react-markdown", blocksImport)
|
|
16475
|
+
]);
|
|
16476
|
+
if (!values["no-page"] && layout.pagePath) {
|
|
16477
|
+
files.push([layout.pagePath, layout.framework === "tanstack" ? TANSTACK_PAGE : NEXT_PAGE]);
|
|
16478
|
+
}
|
|
16479
|
+
}
|
|
16480
|
+
if (target === "html") {
|
|
16481
|
+
files.push([
|
|
16482
|
+
"scripts/render-example.mjs",
|
|
16483
|
+
htmlRenderScript(md)
|
|
16484
|
+
]);
|
|
16485
|
+
}
|
|
16277
16486
|
for (const [rel, content] of files) {
|
|
16278
16487
|
const result = await scaffold(join(cwd, rel), content);
|
|
16279
16488
|
io.stdout(`${result}: ${rel}`);
|
|
@@ -16281,57 +16490,77 @@ async function initCommand(args, io) {
|
|
|
16281
16490
|
const fresh = JSON.parse(await readFile(pkgPath, "utf8"));
|
|
16282
16491
|
fresh.scripts ??= {};
|
|
16283
16492
|
if (!fresh.scripts["content:check"]) {
|
|
16284
|
-
fresh.scripts["content:check"] = 'contentbit validate "content/**/*.md" --registry ./blocks/registry.
|
|
16493
|
+
fresh.scripts["content:check"] = 'contentbit validate "content/**/*.md" --registry ./blocks/registry.ts';
|
|
16285
16494
|
await writeFile(pkgPath, `${JSON.stringify(fresh, null, 2)}
|
|
16286
16495
|
`, "utf8");
|
|
16287
16496
|
io.stdout("added script: content:check");
|
|
16288
16497
|
}
|
|
16289
|
-
|
|
16498
|
+
let registry2;
|
|
16499
|
+
try {
|
|
16500
|
+
registry2 = await loadRegistry(join(cwd, "blocks/registry.ts"));
|
|
16501
|
+
} catch {
|
|
16502
|
+
registry2 = await loadRegistry();
|
|
16503
|
+
}
|
|
16290
16504
|
const guide = registry2.toAuthoringGuide({ audience: "llm", includeExamples: true });
|
|
16291
16505
|
await writeFile(join(cwd, "contentbit-guide.md"), guide, "utf8");
|
|
16292
16506
|
io.stdout("created: contentbit-guide.md (LLM authoring instructions)");
|
|
16293
16507
|
io.stdout("");
|
|
16294
16508
|
io.stdout("Done. Next steps:");
|
|
16295
|
-
io.stdout(` 1. Validate the starter content: ${detectPackageManager()} run content:check`);
|
|
16509
|
+
io.stdout(` 1. Validate the starter content: ${detectPackageManager(cwd)} run content:check`);
|
|
16296
16510
|
if (target === "react") {
|
|
16297
|
-
|
|
16511
|
+
if (!values["no-page"] && layout.pagePath) {
|
|
16512
|
+
io.stdout(" 2. Start the dev server and open /example to see the article rendered.");
|
|
16513
|
+
} else {
|
|
16514
|
+
io.stdout(' 2. Render it: import { Content } from "./components/content-blocks"');
|
|
16515
|
+
io.stdout(" <Content source={...content/example.md as a string} />");
|
|
16516
|
+
}
|
|
16298
16517
|
io.stdout(" 3. Styled components: pnpm dlx shadcn@latest add @contentbit/generic-pack");
|
|
16299
16518
|
} else if (target === "html") {
|
|
16300
|
-
io.stdout(" 2. Render it:
|
|
16519
|
+
io.stdout(" 2. Render it: node scripts/render-example.mjs && open example.html");
|
|
16301
16520
|
} else {
|
|
16302
16521
|
io.stdout(" 2. Render it: contentbit render content/example.md --target markdown");
|
|
16303
16522
|
}
|
|
16304
16523
|
io.stdout(" Docs: https://contentbit.dev/docs");
|
|
16305
16524
|
return 0;
|
|
16306
16525
|
}
|
|
16307
|
-
var TARGETS, REGISTRY_TEMPLATE, EXAMPLE_CONTENT,
|
|
16526
|
+
var TARGETS, MD_CHOICES, REGISTRY_TEMPLATE, EXAMPLE_CONTENT, TANSTACK_PAGE, NEXT_PAGE;
|
|
16308
16527
|
var init_init = __esm({
|
|
16309
16528
|
"src/commands/init.ts"() {
|
|
16310
16529
|
"use strict";
|
|
16311
16530
|
init_load_registry();
|
|
16312
16531
|
TARGETS = ["react", "html", "markdown"];
|
|
16313
|
-
|
|
16532
|
+
MD_CHOICES = {
|
|
16533
|
+
react: ["react-markdown", "none"],
|
|
16534
|
+
html: ["marked", "markdown-it", "none"],
|
|
16535
|
+
markdown: ["none"]
|
|
16536
|
+
};
|
|
16537
|
+
REGISTRY_TEMPLATE = `// Custom block definitions for this project. The CLI and your app share
|
|
16538
|
+
// this module \u2014 Node 22.18+ imports TypeScript directly:
|
|
16314
16539
|
//
|
|
16315
|
-
// contentbit validate "content/**/*.md" --registry ./blocks/registry.
|
|
16540
|
+
// contentbit validate "content/**/*.md" --registry ./blocks/registry.ts
|
|
16316
16541
|
//
|
|
16317
|
-
//
|
|
16542
|
+
// Definitions stay framework-free (the CLI and every render target use
|
|
16543
|
+
// them); React components live next door in blocks/components.tsx.
|
|
16318
16544
|
// Docs: https://contentbit.dev/docs/guides/custom-blocks
|
|
16319
|
-
|
|
16320
|
-
|
|
16321
|
-
|
|
16322
|
-
|
|
16323
|
-
|
|
16324
|
-
|
|
16325
|
-
|
|
16326
|
-
|
|
16327
|
-
|
|
16328
|
-
|
|
16329
|
-
|
|
16330
|
-
|
|
16331
|
-
|
|
16332
|
-
|
|
16545
|
+
import { defineBlock, markdownBody, type BlockDefinition } from '@contentbit/core'
|
|
16546
|
+
import { z } from 'zod'
|
|
16547
|
+
|
|
16548
|
+
export const quote = defineBlock({
|
|
16549
|
+
name: 'quote',
|
|
16550
|
+
description: 'A pull quote with an author.',
|
|
16551
|
+
props: z.object({
|
|
16552
|
+
author: z.string().min(1),
|
|
16553
|
+
role: z.string().optional(),
|
|
16554
|
+
}),
|
|
16555
|
+
content: markdownBody({ minLength: 3 }),
|
|
16556
|
+
authoring: {
|
|
16557
|
+
useWhen: ['Quoting a person to support a point'],
|
|
16558
|
+
avoidWhen: ['Highlighting your own remark, use callout instead'],
|
|
16559
|
+
example: ':::quote{author="Ada Lovelace"}\\nThe Analytical Engine weaves algebraic patterns.\\n:::',
|
|
16560
|
+
},
|
|
16561
|
+
})
|
|
16333
16562
|
|
|
16334
|
-
export default []
|
|
16563
|
+
export default [quote] satisfies BlockDefinition<unknown>[]
|
|
16335
16564
|
`;
|
|
16336
16565
|
EXAMPLE_CONTENT = `# Hello, Content Blocks
|
|
16337
16566
|
|
|
@@ -16346,22 +16575,42 @@ Run the validate script and you will get file:line:col diagnostics.
|
|
|
16346
16575
|
2. Run \`contentbit validate "content/**/*.md"\`.
|
|
16347
16576
|
3. Render it with the target you picked at init.
|
|
16348
16577
|
:::
|
|
16578
|
+
|
|
16579
|
+
This one is a **custom block**, defined in \`blocks/registry.ts\` and rendered
|
|
16580
|
+
by the \`QuoteBlock\` component, in about twenty lines:
|
|
16581
|
+
|
|
16582
|
+
:::quote{author="Ada Lovelace" role="Notes on the Analytical Engine, 1843"}
|
|
16583
|
+
The Analytical Engine weaves algebraic patterns just as the Jacquard loom
|
|
16584
|
+
weaves flowers and leaves.
|
|
16585
|
+
:::
|
|
16349
16586
|
`;
|
|
16350
|
-
|
|
16351
|
-
import { createBlockRegistry, parseDocument, validateDocument } from '@contentbit/core'
|
|
16352
|
-
import { ContentBlocks } from '@contentbit/react'
|
|
16587
|
+
TANSTACK_PAGE = `import { createFileRoute } from '@tanstack/react-router'
|
|
16353
16588
|
|
|
16354
|
-
|
|
16589
|
+
import { Content } from '../components/content-blocks'
|
|
16590
|
+
// Vite's ?raw import inlines the Markdown as a string at build time.
|
|
16591
|
+
import source from '../../content/example.md?raw'
|
|
16355
16592
|
|
|
16356
|
-
export
|
|
16357
|
-
|
|
16593
|
+
export const Route = createFileRoute('/example')({ component: ExamplePage })
|
|
16594
|
+
|
|
16595
|
+
function ExamplePage() {
|
|
16358
16596
|
return (
|
|
16359
|
-
<
|
|
16360
|
-
|
|
16361
|
-
|
|
16362
|
-
|
|
16363
|
-
|
|
16364
|
-
|
|
16597
|
+
<main style={{ maxWidth: '42rem', margin: '0 auto', padding: '3rem 1.5rem' }}>
|
|
16598
|
+
<Content source={source} />
|
|
16599
|
+
</main>
|
|
16600
|
+
)
|
|
16601
|
+
}
|
|
16602
|
+
`;
|
|
16603
|
+
NEXT_PAGE = `import { readFile } from 'node:fs/promises'
|
|
16604
|
+
|
|
16605
|
+
// If your project has no "@/" path alias, switch to a relative import.
|
|
16606
|
+
import { Content } from '@/components/content-blocks'
|
|
16607
|
+
|
|
16608
|
+
export default async function ExamplePage() {
|
|
16609
|
+
const source = await readFile('content/example.md', 'utf8')
|
|
16610
|
+
return (
|
|
16611
|
+
<main style={{ maxWidth: '42rem', margin: '0 auto', padding: '3rem 1.5rem' }}>
|
|
16612
|
+
<Content source={source} />
|
|
16613
|
+
</main>
|
|
16365
16614
|
)
|
|
16366
16615
|
}
|
|
16367
16616
|
`;
|
|
@@ -16653,7 +16902,7 @@ var init_docs = __esm({
|
|
|
16653
16902
|
// src/run.ts
|
|
16654
16903
|
var USAGE = `Usage: contentbit <init|validate|render|instructions|docs> [options]
|
|
16655
16904
|
|
|
16656
|
-
init [-t react|html|markdown] [-y] [--no-install]
|
|
16905
|
+
init [-t react|html|markdown] [--md ...] [-y] [--no-install] [--no-page]
|
|
16657
16906
|
|
|
16658
16907
|
validate <globs...> [--registry <module.mjs>] [--strict-warnings]
|
|
16659
16908
|
render <file> --target html|markdown [--registry <module.mjs>] [--out <file>]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "contentbit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "CLI for Content Blocks: validate, render, and generate LLM authoring instructions from your registry.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"@clack/prompts": "^1.5.1",
|
|
39
39
|
"tinyglobby": "^0.2.10",
|
|
40
40
|
"@contentbit/core": "0.1.0",
|
|
41
|
-
"@contentbit/
|
|
42
|
-
"@contentbit/
|
|
41
|
+
"@contentbit/html": "0.1.0",
|
|
42
|
+
"@contentbit/blocks": "0.1.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/node": "^25.0.9",
|
|
@@ -47,6 +47,9 @@
|
|
|
47
47
|
"typescript": "^5.9.3",
|
|
48
48
|
"vitest": "^4.0.17"
|
|
49
49
|
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=22.18"
|
|
52
|
+
},
|
|
50
53
|
"scripts": {
|
|
51
54
|
"build": "tsc -p tsconfig.build.json && node build.mjs",
|
|
52
55
|
"test": "vitest run",
|