contentbit 0.1.1 → 0.3.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/README.md +11 -1
- package/dist/bin.js +1555 -105
- package/dist/commands/agents.d.ts +11 -0
- package/dist/commands/agents.d.ts.map +1 -0
- package/dist/commands/agents.js +243 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +176 -29
- package/dist/commands/links.d.ts +3 -0
- package/dist/commands/links.d.ts.map +1 -0
- package/dist/commands/links.js +97 -0
- package/dist/commands/render.d.ts.map +1 -1
- package/dist/commands/render.js +2 -2
- package/dist/commands/stats.d.ts +3 -0
- package/dist/commands/stats.d.ts.map +1 -0
- package/dist/commands/stats.js +49 -0
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +24 -2
- package/dist/link-options.d.ts +10 -0
- package/dist/link-options.d.ts.map +1 -0
- package/dist/link-options.js +31 -0
- package/dist/links-io.d.ts +3 -0
- package/dist/links-io.d.ts.map +1 -0
- package/dist/links-io.js +14 -0
- package/dist/run.d.ts +1 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +1553 -103
- package/package.json +5 -5
package/dist/run.js
CHANGED
|
@@ -319,6 +319,383 @@ var init_parser = __esm({
|
|
|
319
319
|
}
|
|
320
320
|
});
|
|
321
321
|
|
|
322
|
+
// ../core/dist/frontmatter.js
|
|
323
|
+
function stripFrontmatter(source) {
|
|
324
|
+
const match = FM_RE.exec(source);
|
|
325
|
+
if (!match)
|
|
326
|
+
return source;
|
|
327
|
+
return match[0].replace(/[^\n]+/g, "") + source.slice(match[0].length);
|
|
328
|
+
}
|
|
329
|
+
function extractFrontmatter(source) {
|
|
330
|
+
const match = source.match(FM_RE);
|
|
331
|
+
if (!match)
|
|
332
|
+
return null;
|
|
333
|
+
const blockLines = match[0].replace(/\r?\n$/, "").split(/\r?\n/);
|
|
334
|
+
const inner = blockLines.slice(1, -1);
|
|
335
|
+
const { data, keys } = parseYamlSubset(inner);
|
|
336
|
+
return { raw: inner.join("\n"), data, keys, lines: { start: 1, end: blockLines.length } };
|
|
337
|
+
}
|
|
338
|
+
function parseYamlSubset(lines) {
|
|
339
|
+
const data = {};
|
|
340
|
+
const keys = [];
|
|
341
|
+
let i = 0;
|
|
342
|
+
while (i < lines.length) {
|
|
343
|
+
const line = lines[i];
|
|
344
|
+
const trimmed = line.trim();
|
|
345
|
+
if (trimmed === "" || trimmed.startsWith("#") || /^[ \t]/.test(line)) {
|
|
346
|
+
i++;
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
const m = line.match(KEY_RE2);
|
|
350
|
+
if (!m) {
|
|
351
|
+
i++;
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
const [, key, rawValue] = m;
|
|
355
|
+
const value = rawValue.trim();
|
|
356
|
+
i++;
|
|
357
|
+
const indented = [];
|
|
358
|
+
while (i < lines.length && /^[ \t]/.test(lines[i]) && lines[i].trim() !== "") {
|
|
359
|
+
indented.push(lines[i]);
|
|
360
|
+
i++;
|
|
361
|
+
}
|
|
362
|
+
keys.push(key);
|
|
363
|
+
data[key] = parseValue(value, indented);
|
|
364
|
+
}
|
|
365
|
+
return { data, keys };
|
|
366
|
+
}
|
|
367
|
+
function parseValue(value, indented) {
|
|
368
|
+
if (/^[|>][+-]?$/.test(value))
|
|
369
|
+
return dedent(indented).join("\n");
|
|
370
|
+
if (value === "") {
|
|
371
|
+
if (indented.length === 0)
|
|
372
|
+
return null;
|
|
373
|
+
const items = dedent(indented);
|
|
374
|
+
const list = parseDashList(items);
|
|
375
|
+
if (list)
|
|
376
|
+
return list;
|
|
377
|
+
const mapping = parseNestedMapping(items);
|
|
378
|
+
if (mapping)
|
|
379
|
+
return mapping;
|
|
380
|
+
return items.join("\n");
|
|
381
|
+
}
|
|
382
|
+
return parseScalar(value);
|
|
383
|
+
}
|
|
384
|
+
function parseNestedMapping(items) {
|
|
385
|
+
const out = {};
|
|
386
|
+
for (const line of items) {
|
|
387
|
+
if (/^[ \t]/.test(line))
|
|
388
|
+
return null;
|
|
389
|
+
const m = line.match(KEY_RE2);
|
|
390
|
+
if (!m)
|
|
391
|
+
return null;
|
|
392
|
+
const [, key, rawValue] = m;
|
|
393
|
+
const v = rawValue.trim();
|
|
394
|
+
if (v === "")
|
|
395
|
+
return null;
|
|
396
|
+
out[key] = parseScalar(v);
|
|
397
|
+
}
|
|
398
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
399
|
+
}
|
|
400
|
+
function parseDashList(items) {
|
|
401
|
+
const groups = [];
|
|
402
|
+
let current = null;
|
|
403
|
+
for (const line of items) {
|
|
404
|
+
if (line.startsWith("- ")) {
|
|
405
|
+
if (current)
|
|
406
|
+
groups.push(current);
|
|
407
|
+
current = [line.slice(2)];
|
|
408
|
+
} else if (current && /^[ \t]/.test(line)) {
|
|
409
|
+
current.push(line);
|
|
410
|
+
} else {
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if (current)
|
|
415
|
+
groups.push(current);
|
|
416
|
+
return groups.map((group) => parseDashItem(group));
|
|
417
|
+
}
|
|
418
|
+
function parseDashItem(lines) {
|
|
419
|
+
const first = lines[0].trim();
|
|
420
|
+
if (lines.length === 1)
|
|
421
|
+
return parseNestedMapping([first]) ?? parseScalar(first);
|
|
422
|
+
const rest = dedent(lines.slice(1));
|
|
423
|
+
const mapping = parseNestedMapping([first, ...rest]);
|
|
424
|
+
return mapping ?? lines.join("\n");
|
|
425
|
+
}
|
|
426
|
+
function dedent(lines) {
|
|
427
|
+
const indent = Math.min(...lines.map((l) => l.match(/^[ \t]*/)[0].length));
|
|
428
|
+
return lines.map((l) => l.slice(indent));
|
|
429
|
+
}
|
|
430
|
+
function parseScalar(value) {
|
|
431
|
+
if (value === "" || value === "null" || value === "~")
|
|
432
|
+
return null;
|
|
433
|
+
if (value === "true")
|
|
434
|
+
return true;
|
|
435
|
+
if (value === "false")
|
|
436
|
+
return false;
|
|
437
|
+
if (/^[+-]?\d+$/.test(value))
|
|
438
|
+
return Number.parseInt(value, 10);
|
|
439
|
+
if (/^[+-]?\d*\.\d+$/.test(value))
|
|
440
|
+
return Number.parseFloat(value);
|
|
441
|
+
if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
|
|
442
|
+
try {
|
|
443
|
+
return JSON.parse(value);
|
|
444
|
+
} catch {
|
|
445
|
+
return value.slice(1, -1);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (value.startsWith("'") && value.endsWith("'") && value.length >= 2) {
|
|
449
|
+
return value.slice(1, -1).replace(/''/g, "'");
|
|
450
|
+
}
|
|
451
|
+
if (value.startsWith("[") && value.endsWith("]")) {
|
|
452
|
+
const inner = value.slice(1, -1).trim();
|
|
453
|
+
if (inner === "")
|
|
454
|
+
return [];
|
|
455
|
+
return splitInlineItems(inner).map((item) => parseScalar(item.trim()));
|
|
456
|
+
}
|
|
457
|
+
if (value.startsWith("{") && value.endsWith("}")) {
|
|
458
|
+
const inner = value.slice(1, -1).trim();
|
|
459
|
+
if (inner === "")
|
|
460
|
+
return {};
|
|
461
|
+
const out = {};
|
|
462
|
+
for (const item of splitInlineItems(inner)) {
|
|
463
|
+
const m = item.trim().match(KEY_RE2);
|
|
464
|
+
if (!m)
|
|
465
|
+
return value;
|
|
466
|
+
const [, key, rawValue] = m;
|
|
467
|
+
out[key] = parseScalar(rawValue.trim());
|
|
468
|
+
}
|
|
469
|
+
return out;
|
|
470
|
+
}
|
|
471
|
+
return value;
|
|
472
|
+
}
|
|
473
|
+
function splitInlineItems(inner) {
|
|
474
|
+
const items = [];
|
|
475
|
+
let current = "";
|
|
476
|
+
let quote = null;
|
|
477
|
+
for (const ch of inner) {
|
|
478
|
+
if (quote) {
|
|
479
|
+
current += ch;
|
|
480
|
+
if (ch === quote)
|
|
481
|
+
quote = null;
|
|
482
|
+
} else if (ch === '"' || ch === "'") {
|
|
483
|
+
current += ch;
|
|
484
|
+
quote = ch;
|
|
485
|
+
} else if (ch === ",") {
|
|
486
|
+
items.push(current);
|
|
487
|
+
current = "";
|
|
488
|
+
} else {
|
|
489
|
+
current += ch;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
items.push(current);
|
|
493
|
+
return items;
|
|
494
|
+
}
|
|
495
|
+
var FM_RE, KEY_RE2;
|
|
496
|
+
var init_frontmatter = __esm({
|
|
497
|
+
"../core/dist/frontmatter.js"() {
|
|
498
|
+
"use strict";
|
|
499
|
+
FM_RE = /^---[ \t]*\r?\n(?:[\s\S]*?\r?\n)?---[ \t]*(?:\r?\n|$)/;
|
|
500
|
+
KEY_RE2 = /^([A-Za-z0-9_.-]+):(.*)$/;
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
// ../core/dist/analyze.js
|
|
505
|
+
function utf8Length(source) {
|
|
506
|
+
let bytes = 0;
|
|
507
|
+
for (const ch of source) {
|
|
508
|
+
const cp = ch.codePointAt(0);
|
|
509
|
+
bytes += cp <= 127 ? 1 : cp <= 2047 ? 2 : cp <= 65535 ? 3 : 4;
|
|
510
|
+
}
|
|
511
|
+
return bytes;
|
|
512
|
+
}
|
|
513
|
+
function analyzeDocument(source, options = {}) {
|
|
514
|
+
const frontmatter = extractFrontmatter(source);
|
|
515
|
+
const { document } = parseDocument(stripFrontmatter(source));
|
|
516
|
+
const outline = [];
|
|
517
|
+
const blocks = { total: 0, byName: {}, maxDepth: 0, instances: [] };
|
|
518
|
+
const links = {
|
|
519
|
+
total: 0,
|
|
520
|
+
external: 0,
|
|
521
|
+
internal: 0,
|
|
522
|
+
domains: [],
|
|
523
|
+
items: []
|
|
524
|
+
};
|
|
525
|
+
const images = { total: 0, missingAlt: 0 };
|
|
526
|
+
const code = { fences: 0, languages: [], inlineSpans: 0 };
|
|
527
|
+
const structure = { listItems: 0, tables: 0, blockquotes: 0 };
|
|
528
|
+
const languages = /* @__PURE__ */ new Set();
|
|
529
|
+
const domains = /* @__PURE__ */ new Set();
|
|
530
|
+
let words = 0;
|
|
531
|
+
function addWords(count) {
|
|
532
|
+
words += count;
|
|
533
|
+
const section = outline[outline.length - 1];
|
|
534
|
+
if (section)
|
|
535
|
+
section.words += count;
|
|
536
|
+
}
|
|
537
|
+
function recordLink(url2, text, line) {
|
|
538
|
+
const external = EXTERNAL_URL_RE.test(url2);
|
|
539
|
+
links.total++;
|
|
540
|
+
if (external) {
|
|
541
|
+
links.external++;
|
|
542
|
+
const host = url2.match(/^(?:https?:)?\/\/(?:[^/?#@]*@)?([^/?#:]+)/i);
|
|
543
|
+
if (host)
|
|
544
|
+
domains.add(host[1].toLowerCase());
|
|
545
|
+
} else {
|
|
546
|
+
links.internal++;
|
|
547
|
+
}
|
|
548
|
+
links.items.push({ url: url2, text, line, external });
|
|
549
|
+
}
|
|
550
|
+
function inlineToProse(text, line) {
|
|
551
|
+
return text.replace(CODE_SPAN_RE, () => {
|
|
552
|
+
code.inlineSpans++;
|
|
553
|
+
return " ";
|
|
554
|
+
}).replace(IMAGE_RE, (_, alt) => {
|
|
555
|
+
images.total++;
|
|
556
|
+
if (alt.trim() === "")
|
|
557
|
+
images.missingAlt++;
|
|
558
|
+
return " ";
|
|
559
|
+
}).replace(LINK_RE, (_, label, url2) => {
|
|
560
|
+
recordLink(url2, label, line);
|
|
561
|
+
return label;
|
|
562
|
+
}).replace(AUTOLINK_RE, (_, url2) => {
|
|
563
|
+
recordLink(url2, url2, line);
|
|
564
|
+
return " ";
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
function countWords(text) {
|
|
568
|
+
return text.split(/\s+/).filter((token) => /[\p{L}\p{N}]/u.test(token)).length;
|
|
569
|
+
}
|
|
570
|
+
function scanMarkdown(value, startLine) {
|
|
571
|
+
let fence = null;
|
|
572
|
+
let inBlockquote = false;
|
|
573
|
+
let prevLineHasPipe = false;
|
|
574
|
+
const lines = value.split("\n");
|
|
575
|
+
for (let i = 0; i < lines.length; i++) {
|
|
576
|
+
const line = lines[i];
|
|
577
|
+
const lineNo = startLine + i;
|
|
578
|
+
const trimmed = line.trim();
|
|
579
|
+
const fenceMatch = trimmed.match(CODE_FENCE_RE2);
|
|
580
|
+
if (fence !== null) {
|
|
581
|
+
if (fenceMatch && fenceMatch[1][0] === fence[0] && fenceMatch[1].length >= fence.length) {
|
|
582
|
+
fence = null;
|
|
583
|
+
}
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
if (fenceMatch) {
|
|
587
|
+
fence = fenceMatch[1];
|
|
588
|
+
code.fences++;
|
|
589
|
+
const lang = fenceMatch[2].trim().split(/\s+/)[0];
|
|
590
|
+
if (lang)
|
|
591
|
+
languages.add(lang);
|
|
592
|
+
inBlockquote = false;
|
|
593
|
+
prevLineHasPipe = false;
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
if (trimmed === "") {
|
|
597
|
+
inBlockquote = false;
|
|
598
|
+
prevLineHasPipe = false;
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
const heading = trimmed.match(HEADING_RE);
|
|
602
|
+
if (heading) {
|
|
603
|
+
const raw = heading[2].replace(/\s+#+\s*$/, "");
|
|
604
|
+
const text = inlineToProse(raw, lineNo).replace(/\s+/g, " ").trim();
|
|
605
|
+
outline.push({ level: heading[1].length, text, line: lineNo, words: 0 });
|
|
606
|
+
addWords(countWords(text));
|
|
607
|
+
inBlockquote = false;
|
|
608
|
+
prevLineHasPipe = false;
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
let content = trimmed;
|
|
612
|
+
if (content.startsWith(">")) {
|
|
613
|
+
if (!inBlockquote) {
|
|
614
|
+
structure.blockquotes++;
|
|
615
|
+
inBlockquote = true;
|
|
616
|
+
}
|
|
617
|
+
content = content.replace(/^(>\s*)+/, "");
|
|
618
|
+
} else {
|
|
619
|
+
inBlockquote = false;
|
|
620
|
+
}
|
|
621
|
+
if (LIST_ITEM_RE.test(content)) {
|
|
622
|
+
structure.listItems++;
|
|
623
|
+
content = content.replace(LIST_ITEM_RE, "");
|
|
624
|
+
}
|
|
625
|
+
if (content.includes("|")) {
|
|
626
|
+
if (TABLE_SEPARATOR_RE.test(content) && content.includes("-") && prevLineHasPipe) {
|
|
627
|
+
structure.tables++;
|
|
628
|
+
}
|
|
629
|
+
prevLineHasPipe = true;
|
|
630
|
+
content = inlineToProse(content, lineNo).replaceAll("|", " ");
|
|
631
|
+
} else {
|
|
632
|
+
prevLineHasPipe = false;
|
|
633
|
+
content = inlineToProse(content, lineNo);
|
|
634
|
+
}
|
|
635
|
+
addWords(countWords(content));
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
function walk(nodes, depth) {
|
|
639
|
+
for (const node of nodes) {
|
|
640
|
+
if (node.type === "block") {
|
|
641
|
+
blocks.total++;
|
|
642
|
+
blocks.byName[node.name] = (blocks.byName[node.name] ?? 0) + 1;
|
|
643
|
+
blocks.maxDepth = Math.max(blocks.maxDepth, depth);
|
|
644
|
+
blocks.instances.push({ name: node.name, line: node.openPosition.start.line, depth });
|
|
645
|
+
walk(node.children, depth + 1);
|
|
646
|
+
} else {
|
|
647
|
+
scanMarkdown(node.value, node.position.start.line);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
walk(document.children, 1);
|
|
652
|
+
code.languages = [...languages];
|
|
653
|
+
links.domains = [...domains].sort();
|
|
654
|
+
const sourceLines = source === "" ? 0 : source.split("\n").length - (source.endsWith("\n") ? 1 : 0);
|
|
655
|
+
return {
|
|
656
|
+
file: {
|
|
657
|
+
path: options.path ?? null,
|
|
658
|
+
bytes: utf8Length(source),
|
|
659
|
+
lines: sourceLines
|
|
660
|
+
},
|
|
661
|
+
frontmatter: frontmatter ? {
|
|
662
|
+
present: true,
|
|
663
|
+
keys: frontmatter.keys,
|
|
664
|
+
data: frontmatter.data,
|
|
665
|
+
lines: frontmatter.lines
|
|
666
|
+
} : { present: false, keys: [], data: {}, lines: null },
|
|
667
|
+
length: {
|
|
668
|
+
words,
|
|
669
|
+
characters: source.length,
|
|
670
|
+
readingMinutes: Math.ceil(words / 200),
|
|
671
|
+
approxTokens: Math.ceil(source.length / 4)
|
|
672
|
+
},
|
|
673
|
+
outline,
|
|
674
|
+
blocks,
|
|
675
|
+
links,
|
|
676
|
+
images,
|
|
677
|
+
code,
|
|
678
|
+
structure
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
var CODE_FENCE_RE2, HEADING_RE, LIST_ITEM_RE, TABLE_SEPARATOR_RE, CODE_SPAN_RE, IMAGE_RE, LINK_RE, AUTOLINK_RE, EXTERNAL_URL_RE;
|
|
682
|
+
var init_analyze = __esm({
|
|
683
|
+
"../core/dist/analyze.js"() {
|
|
684
|
+
"use strict";
|
|
685
|
+
init_frontmatter();
|
|
686
|
+
init_parser();
|
|
687
|
+
CODE_FENCE_RE2 = /^(`{3,}|~{3,})(.*)$/;
|
|
688
|
+
HEADING_RE = /^(#{1,6})\s+(.*)$/;
|
|
689
|
+
LIST_ITEM_RE = /^\s*(?:[-*+]|\d+[.)])\s+/;
|
|
690
|
+
TABLE_SEPARATOR_RE = /^\|?[\s:|-]+$/;
|
|
691
|
+
CODE_SPAN_RE = /`[^`]+`/g;
|
|
692
|
+
IMAGE_RE = /!\[([^\]]*)\]\(([^)]*)\)/g;
|
|
693
|
+
LINK_RE = /\[([^\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
|
|
694
|
+
AUTOLINK_RE = /<(https?:\/\/[^>\s]+)>/g;
|
|
695
|
+
EXTERNAL_URL_RE = /^(?:https?:)?\/\//i;
|
|
696
|
+
}
|
|
697
|
+
});
|
|
698
|
+
|
|
322
699
|
// ../core/dist/position.js
|
|
323
700
|
function bodyLineRange(node, bodyLineIndex) {
|
|
324
701
|
const bodyLines = node.body.split("\n");
|
|
@@ -339,6 +716,45 @@ var init_position = __esm({
|
|
|
339
716
|
});
|
|
340
717
|
|
|
341
718
|
// ../core/dist/authoring.js
|
|
719
|
+
function typeLabel(def) {
|
|
720
|
+
switch (def.type) {
|
|
721
|
+
case "enum":
|
|
722
|
+
return `one of ${Object.values(def.entries ?? {}).join("|")}`;
|
|
723
|
+
case "literal":
|
|
724
|
+
return `one of ${(def.values ?? []).map(String).join("|")}`;
|
|
725
|
+
case "union":
|
|
726
|
+
return `one of ${(def.options ?? []).map((o) => typeLabel(o.def).replace(/^one of /, "")).join("|")}`;
|
|
727
|
+
default:
|
|
728
|
+
return def.type;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
function describeProps(schema) {
|
|
732
|
+
const root = schema.def;
|
|
733
|
+
if (root.type !== "object" || !root.shape)
|
|
734
|
+
return [];
|
|
735
|
+
const lines = [];
|
|
736
|
+
for (const [name, field] of Object.entries(root.shape)) {
|
|
737
|
+
let current = field;
|
|
738
|
+
let optional2 = false;
|
|
739
|
+
let defaultValue;
|
|
740
|
+
let description = current.description;
|
|
741
|
+
while (["optional", "default", "nullable"].includes(current.def.type)) {
|
|
742
|
+
optional2 = true;
|
|
743
|
+
if (current.def.type === "default") {
|
|
744
|
+
const dv = current.def.defaultValue;
|
|
745
|
+
defaultValue = typeof dv === "function" ? dv() : dv;
|
|
746
|
+
}
|
|
747
|
+
if (!current.def.innerType)
|
|
748
|
+
break;
|
|
749
|
+
current = current.def.innerType;
|
|
750
|
+
description ??= current.description;
|
|
751
|
+
}
|
|
752
|
+
const presence = defaultValue !== void 0 ? `(optional, default: ${String(defaultValue)})` : optional2 ? "(optional)" : "(required)";
|
|
753
|
+
const suffix = description ? ` \u2014 ${description}` : "";
|
|
754
|
+
lines.push(`- ${name}: ${typeLabel(current.def)} ${presence}${suffix}`);
|
|
755
|
+
}
|
|
756
|
+
return lines;
|
|
757
|
+
}
|
|
342
758
|
function generateAuthoringGuide(defs, opts = {}) {
|
|
343
759
|
const includeExamples = opts.includeExamples ?? true;
|
|
344
760
|
const includeAvoid = opts.includeAvoidRules ?? true;
|
|
@@ -348,6 +764,11 @@ function generateAuthoringGuide(defs, opts = {}) {
|
|
|
348
764
|
for (const def of defs) {
|
|
349
765
|
const lines = [`## ${def.name}`, ""];
|
|
350
766
|
lines.push(def.childOnly ? `${def.description} (child block \u2014 only inside a parent that allows it)` : def.description);
|
|
767
|
+
if (def.props) {
|
|
768
|
+
const props = describeProps(def.props);
|
|
769
|
+
if (props.length > 0)
|
|
770
|
+
lines.push("", "Props:", ...props);
|
|
771
|
+
}
|
|
351
772
|
lines.push("", `Content: ${def.content.describe()}`);
|
|
352
773
|
if (def.authoring.useWhen.length > 0) {
|
|
353
774
|
lines.push("", "Use when:", ...def.authoring.useWhen.map((u) => `- ${u}`));
|
|
@@ -735,19 +1156,6 @@ var init_render_markdown = __esm({
|
|
|
735
1156
|
}
|
|
736
1157
|
});
|
|
737
1158
|
|
|
738
|
-
// ../core/dist/index.js
|
|
739
|
-
var init_dist = __esm({
|
|
740
|
-
"../core/dist/index.js"() {
|
|
741
|
-
"use strict";
|
|
742
|
-
init_diagnostics();
|
|
743
|
-
init_parser();
|
|
744
|
-
init_registry();
|
|
745
|
-
init_content_models();
|
|
746
|
-
init_validate();
|
|
747
|
-
init_render_markdown();
|
|
748
|
-
}
|
|
749
|
-
});
|
|
750
|
-
|
|
751
1159
|
// ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/core.js
|
|
752
1160
|
// @__NO_SIDE_EFFECTS__
|
|
753
1161
|
function $constructor(name, initializer3, params) {
|
|
@@ -812,7 +1220,7 @@ var init_core = __esm({
|
|
|
812
1220
|
NEVER = /* @__PURE__ */ Object.freeze({
|
|
813
1221
|
status: "aborted"
|
|
814
1222
|
});
|
|
815
|
-
$brand = Symbol("zod_brand");
|
|
1223
|
+
$brand = /* @__PURE__ */ Symbol("zod_brand");
|
|
816
1224
|
$ZodAsyncError = class extends Error {
|
|
817
1225
|
constructor() {
|
|
818
1226
|
super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`);
|
|
@@ -6654,13 +7062,13 @@ var init_he = __esm({
|
|
|
6654
7062
|
// no unit
|
|
6655
7063
|
};
|
|
6656
7064
|
const typeEntry = (t) => t ? TypeNames[t] : void 0;
|
|
6657
|
-
const
|
|
7065
|
+
const typeLabel2 = (t) => {
|
|
6658
7066
|
const e = typeEntry(t);
|
|
6659
7067
|
if (e)
|
|
6660
7068
|
return e.label;
|
|
6661
7069
|
return t ?? TypeNames.unknown.label;
|
|
6662
7070
|
};
|
|
6663
|
-
const withDefinite = (t) => `\u05D4${
|
|
7071
|
+
const withDefinite = (t) => `\u05D4${typeLabel2(t)}`;
|
|
6664
7072
|
const verbFor = (t) => {
|
|
6665
7073
|
const e = typeEntry(t);
|
|
6666
7074
|
const gender = e?.gender ?? "m";
|
|
@@ -6710,7 +7118,7 @@ var init_he = __esm({
|
|
|
6710
7118
|
switch (issue2.code) {
|
|
6711
7119
|
case "invalid_type": {
|
|
6712
7120
|
const expectedKey = issue2.expected;
|
|
6713
|
-
const expected = TypeDictionary[expectedKey ?? ""] ??
|
|
7121
|
+
const expected = TypeDictionary[expectedKey ?? ""] ?? typeLabel2(expectedKey);
|
|
6714
7122
|
const receivedType = parsedType(issue2.input);
|
|
6715
7123
|
const received = TypeDictionary[receivedType] ?? TypeNames[receivedType]?.label ?? receivedType;
|
|
6716
7124
|
if (/^[A-Z]/.test(issue2.expected)) {
|
|
@@ -10981,8 +11389,8 @@ function registry() {
|
|
|
10981
11389
|
var _a2, $output, $input, $ZodRegistry, globalRegistry;
|
|
10982
11390
|
var init_registries = __esm({
|
|
10983
11391
|
"../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/registries.js"() {
|
|
10984
|
-
$output = Symbol("ZodOutput");
|
|
10985
|
-
$input = Symbol("ZodInput");
|
|
11392
|
+
$output = /* @__PURE__ */ Symbol("ZodOutput");
|
|
11393
|
+
$input = /* @__PURE__ */ Symbol("ZodInput");
|
|
10986
11394
|
$ZodRegistry = class {
|
|
10987
11395
|
constructor() {
|
|
10988
11396
|
this._map = /* @__PURE__ */ new WeakMap();
|
|
@@ -15829,6 +16237,435 @@ var init_zod = __esm({
|
|
|
15829
16237
|
}
|
|
15830
16238
|
});
|
|
15831
16239
|
|
|
16240
|
+
// ../core/dist/links.js
|
|
16241
|
+
function parseLinkFrontmatter(data, options = {}) {
|
|
16242
|
+
const normalized = normalizeFrontmatter(data, options);
|
|
16243
|
+
if (!("slug" in normalized))
|
|
16244
|
+
return { ok: true, value: null };
|
|
16245
|
+
const parsed = LinkFrontmatter.safeParse(normalized);
|
|
16246
|
+
if (parsed.success)
|
|
16247
|
+
return { ok: true, value: parsed.data };
|
|
16248
|
+
return { ok: false, errors: parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`) };
|
|
16249
|
+
}
|
|
16250
|
+
function buildLinkIndex(inputs, options = {}) {
|
|
16251
|
+
const resolvedOptions = { ...DEFAULT_OPTIONS, ...options };
|
|
16252
|
+
const pages = /* @__PURE__ */ new Map();
|
|
16253
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
16254
|
+
const aliasEntries = [];
|
|
16255
|
+
for (const { path, data } of inputs) {
|
|
16256
|
+
const parsed = parseLinkFrontmatter(data, resolvedOptions);
|
|
16257
|
+
if (!parsed.ok || parsed.value === null)
|
|
16258
|
+
continue;
|
|
16259
|
+
const fm = parsed.value;
|
|
16260
|
+
const page = {
|
|
16261
|
+
slug: fm.slug,
|
|
16262
|
+
key: fm.key,
|
|
16263
|
+
locale: effectiveLocale(fm, resolvedOptions),
|
|
16264
|
+
path,
|
|
16265
|
+
title: fm.title,
|
|
16266
|
+
keywords: fm.keywords,
|
|
16267
|
+
linksTo: [],
|
|
16268
|
+
linkedFrom: [],
|
|
16269
|
+
aliases: fm.aliases ?? [],
|
|
16270
|
+
linkRefs: [],
|
|
16271
|
+
linkedFromRefs: []
|
|
16272
|
+
};
|
|
16273
|
+
pages.set(pageMapKey(page, resolvedOptions), page);
|
|
16274
|
+
}
|
|
16275
|
+
for (const page of pages.values()) {
|
|
16276
|
+
for (const alias of page.aliases) {
|
|
16277
|
+
const replacement = replacementFor(page, resolvedOptions);
|
|
16278
|
+
const key = aliasMapKey(alias, page.locale, resolvedOptions);
|
|
16279
|
+
aliases.set(key, replacement);
|
|
16280
|
+
aliasEntries.push({ alias, locale: page.locale, replacement, page });
|
|
16281
|
+
}
|
|
16282
|
+
}
|
|
16283
|
+
const lookup = buildLookup(pages, resolvedOptions);
|
|
16284
|
+
for (const page of pages.values()) {
|
|
16285
|
+
const source = parseLinkFrontmatter(inputForPage(page, inputs, resolvedOptions), resolvedOptions);
|
|
16286
|
+
if (!source.ok || source.value === null)
|
|
16287
|
+
continue;
|
|
16288
|
+
for (const rawTarget of source.value.linksTo ?? []) {
|
|
16289
|
+
const resolved = resolveTarget(rawTarget, page, lookup, resolvedOptions);
|
|
16290
|
+
if (!resolved.page) {
|
|
16291
|
+
page.linksTo.push(resolved.target);
|
|
16292
|
+
continue;
|
|
16293
|
+
}
|
|
16294
|
+
page.linksTo.push(replacementFor(resolved.page, resolvedOptions));
|
|
16295
|
+
page.linkRefs.push(referenceFor(resolved.page, resolved.target));
|
|
16296
|
+
if (resolved.page === page)
|
|
16297
|
+
continue;
|
|
16298
|
+
const from = replacementFor(page, resolvedOptions);
|
|
16299
|
+
if (!resolved.page.linkedFrom.includes(from))
|
|
16300
|
+
resolved.page.linkedFrom.push(from);
|
|
16301
|
+
if (!resolved.page.linkedFromRefs.some((r) => sameReference(r, page))) {
|
|
16302
|
+
resolved.page.linkedFromRefs.push(referenceFor(page));
|
|
16303
|
+
}
|
|
16304
|
+
}
|
|
16305
|
+
}
|
|
16306
|
+
return { pages, aliases, aliasEntries, options: resolvedOptions };
|
|
16307
|
+
}
|
|
16308
|
+
function serializeLinkIndex(index) {
|
|
16309
|
+
const scoped = [...index.pages.values()].some((page) => page.locale || page.key);
|
|
16310
|
+
const pages = [...index.pages.values()].map((p) => {
|
|
16311
|
+
const base = {
|
|
16312
|
+
slug: p.slug,
|
|
16313
|
+
...p.key ? { key: p.key } : {},
|
|
16314
|
+
...p.locale ? { locale: p.locale } : {},
|
|
16315
|
+
path: p.path,
|
|
16316
|
+
...p.title ? { title: p.title } : {},
|
|
16317
|
+
...p.keywords ? { keywords: p.keywords } : {},
|
|
16318
|
+
linksTo: scoped ? p.linkRefs : [...p.linksTo],
|
|
16319
|
+
linkedFrom: scoped ? sortedRefs(p.linkedFromRefs) : [...p.linkedFrom].sort(),
|
|
16320
|
+
aliases: [...p.aliases]
|
|
16321
|
+
};
|
|
16322
|
+
return base;
|
|
16323
|
+
}).sort((a, b) => sortIdentity(a.locale, a.slug).localeCompare(sortIdentity(b.locale, b.slug)));
|
|
16324
|
+
const aliases = {};
|
|
16325
|
+
if (scoped) {
|
|
16326
|
+
for (const entry of [...index.aliasEntries].sort((a, b) => sortIdentity(a.locale, a.alias).localeCompare(sortIdentity(b.locale, b.alias)))) {
|
|
16327
|
+
aliases[aliasMapKey(entry.alias, entry.locale, index.options)] = referenceFor(entry.page);
|
|
16328
|
+
}
|
|
16329
|
+
} else {
|
|
16330
|
+
for (const key of [...index.aliases.keys()].sort())
|
|
16331
|
+
aliases[key] = index.aliases.get(key);
|
|
16332
|
+
}
|
|
16333
|
+
return { pages, aliases };
|
|
16334
|
+
}
|
|
16335
|
+
function aliasReplacementsForPage(index, data) {
|
|
16336
|
+
const parsed = parseLinkFrontmatter(data, index.options);
|
|
16337
|
+
const out = /* @__PURE__ */ new Map();
|
|
16338
|
+
if (!parsed.ok || parsed.value === null)
|
|
16339
|
+
return out;
|
|
16340
|
+
const locale = effectiveLocale(parsed.value, index.options);
|
|
16341
|
+
for (const entry of index.aliasEntries) {
|
|
16342
|
+
if (index.options.resolve === "global-slug" || entry.locale === locale) {
|
|
16343
|
+
out.set(entry.alias, entry.replacement);
|
|
16344
|
+
}
|
|
16345
|
+
}
|
|
16346
|
+
return out;
|
|
16347
|
+
}
|
|
16348
|
+
function validateLinks(inputs, options = {}) {
|
|
16349
|
+
const resolvedOptions = { ...DEFAULT_OPTIONS, ...options };
|
|
16350
|
+
const out = [];
|
|
16351
|
+
const validInputs = [];
|
|
16352
|
+
const seenSlug = /* @__PURE__ */ new Map();
|
|
16353
|
+
const seenKey = /* @__PURE__ */ new Map();
|
|
16354
|
+
const seenAlias = /* @__PURE__ */ new Map();
|
|
16355
|
+
for (const { path, data } of inputs) {
|
|
16356
|
+
const parsed = parseLinkFrontmatter(data, resolvedOptions);
|
|
16357
|
+
if (!parsed.ok) {
|
|
16358
|
+
for (const e of parsed.errors)
|
|
16359
|
+
out.push(diag(path, "CB_LINK_SHAPE", "error", `invalid link frontmatter: ${e}`));
|
|
16360
|
+
continue;
|
|
16361
|
+
}
|
|
16362
|
+
if (parsed.value === null)
|
|
16363
|
+
continue;
|
|
16364
|
+
const fm = parsed.value;
|
|
16365
|
+
const locale = effectiveLocale(fm, resolvedOptions);
|
|
16366
|
+
validInputs.push({ path, fm });
|
|
16367
|
+
const slugKey = scopedKey(fm.slug, locale, resolvedOptions);
|
|
16368
|
+
const prior = seenSlug.get(slugKey);
|
|
16369
|
+
if (prior)
|
|
16370
|
+
out.push(diag(path, "CB_SLUG_DUPLICATE", "error", `slug "${fm.slug}" also used by ${prior}`));
|
|
16371
|
+
else
|
|
16372
|
+
seenSlug.set(slugKey, path);
|
|
16373
|
+
if (usesKeyResolution(resolvedOptions) && !fm.key) {
|
|
16374
|
+
out.push(diag(path, "CB_KEY_MISSING", "error", `page "${fm.slug}" is missing key`));
|
|
16375
|
+
}
|
|
16376
|
+
if (fm.key) {
|
|
16377
|
+
const keyKey = scopedKey(fm.key, locale, resolvedOptions);
|
|
16378
|
+
const priorKey = seenKey.get(keyKey);
|
|
16379
|
+
if (priorKey)
|
|
16380
|
+
out.push(diag(path, "CB_KEY_DUPLICATE", "error", `key "${fm.key}" also used by ${priorKey}`));
|
|
16381
|
+
else
|
|
16382
|
+
seenKey.set(keyKey, path);
|
|
16383
|
+
}
|
|
16384
|
+
for (const alias of fm.aliases ?? []) {
|
|
16385
|
+
const aliasKey = scopedKey(alias, locale, resolvedOptions);
|
|
16386
|
+
if (seenAlias.has(aliasKey))
|
|
16387
|
+
out.push(diag(path, "CB_ALIAS_CONFLICT", "error", `alias "${alias}" already declared by ${seenAlias.get(aliasKey)}`));
|
|
16388
|
+
else
|
|
16389
|
+
seenAlias.set(aliasKey, path);
|
|
16390
|
+
}
|
|
16391
|
+
}
|
|
16392
|
+
const index = buildLinkIndex(inputs, resolvedOptions);
|
|
16393
|
+
const lookup = buildLookup(index.pages, resolvedOptions);
|
|
16394
|
+
for (const { path, fm } of validInputs) {
|
|
16395
|
+
const page = index.pages.get(pageMapKey(frontmatterIdentity(fm, resolvedOptions), resolvedOptions));
|
|
16396
|
+
if (!page)
|
|
16397
|
+
continue;
|
|
16398
|
+
for (const alias of page.aliases) {
|
|
16399
|
+
if (collidesWithPageIdentity(alias, page.locale, lookup, resolvedOptions))
|
|
16400
|
+
out.push(diag(page.path, "CB_ALIAS_CONFLICT", "error", `alias "${alias}" collides with an existing page identity`));
|
|
16401
|
+
}
|
|
16402
|
+
for (const target of fm.linksTo ?? []) {
|
|
16403
|
+
const resolved = resolveTarget(target, page, lookup, resolvedOptions);
|
|
16404
|
+
if (resolved.page === page) {
|
|
16405
|
+
out.push(diag(page.path, "CB_LINK_SELF", "warning", `page "${page.slug}" links to itself`));
|
|
16406
|
+
continue;
|
|
16407
|
+
}
|
|
16408
|
+
if (resolved.page && resolved.crossLocale) {
|
|
16409
|
+
out.push(diag(page.path, "CB_LINK_CROSS_LOCALE", "warning", `linksTo "${resolved.target}" resolves to locale "${resolved.page.locale}"`));
|
|
16410
|
+
continue;
|
|
16411
|
+
}
|
|
16412
|
+
if (!resolved.page) {
|
|
16413
|
+
if (targetExistsOutsideLocale(resolved.target, page.locale, lookup, resolvedOptions)) {
|
|
16414
|
+
out.push(diag(page.path, "CB_LINK_LOCALE_MISSING", "error", `linksTo "${resolved.target}" exists in another locale but not "${page.locale ?? "default"}"`));
|
|
16415
|
+
continue;
|
|
16416
|
+
}
|
|
16417
|
+
const hint = closest(resolved.target, candidatesFor(page.locale, lookup, resolvedOptions));
|
|
16418
|
+
out.push(diag(page.path, "CB_LINK_UNRESOLVED", "error", `linksTo "${resolved.target}" does not resolve to any page`, hint ? `Did you mean "${hint}"?` : void 0));
|
|
16419
|
+
}
|
|
16420
|
+
}
|
|
16421
|
+
if (page.linkedFrom.length === 0)
|
|
16422
|
+
out.push(diag(page.path, "CB_LINK_ORPHAN", "warning", `page "${page.slug}" has no inbound links`));
|
|
16423
|
+
}
|
|
16424
|
+
return out;
|
|
16425
|
+
}
|
|
16426
|
+
function normalizeFrontmatter(data, options) {
|
|
16427
|
+
const out = { ...data };
|
|
16428
|
+
copyConfiguredField(out, data, options.slugField, "slug");
|
|
16429
|
+
copyConfiguredField(out, data, options.keyField, "key");
|
|
16430
|
+
copyConfiguredField(out, data, options.localeField, "locale");
|
|
16431
|
+
return out;
|
|
16432
|
+
}
|
|
16433
|
+
function copyConfiguredField(out, data, from, to) {
|
|
16434
|
+
if (!from || from === to || !(from in data) || to in out)
|
|
16435
|
+
return;
|
|
16436
|
+
out[to] = data[from];
|
|
16437
|
+
}
|
|
16438
|
+
function effectiveLocale(fm, options) {
|
|
16439
|
+
return fm.locale ?? options.defaultLocale;
|
|
16440
|
+
}
|
|
16441
|
+
function frontmatterIdentity(fm, options) {
|
|
16442
|
+
return { slug: fm.slug, key: fm.key, locale: effectiveLocale(fm, options) };
|
|
16443
|
+
}
|
|
16444
|
+
function pageMapKey(page, options) {
|
|
16445
|
+
if (options.resolve === "global-slug")
|
|
16446
|
+
return page.slug;
|
|
16447
|
+
return scopedKey(page.slug, page.locale, options);
|
|
16448
|
+
}
|
|
16449
|
+
function scopedKey(value, locale, options) {
|
|
16450
|
+
if (options.resolve === "global-slug")
|
|
16451
|
+
return value;
|
|
16452
|
+
return `${locale ?? ""}\0${value}`;
|
|
16453
|
+
}
|
|
16454
|
+
function aliasMapKey(alias, locale, options) {
|
|
16455
|
+
return options.resolve === "global-slug" ? alias : `${locale ?? ""}:${alias}`;
|
|
16456
|
+
}
|
|
16457
|
+
function replacementFor(page, options) {
|
|
16458
|
+
if (options.resolve === "same-locale-key")
|
|
16459
|
+
return page.key ?? page.slug;
|
|
16460
|
+
if (options.resolve === "prefer-same-locale-key-fallback-slug")
|
|
16461
|
+
return page.key ?? page.slug;
|
|
16462
|
+
return page.slug;
|
|
16463
|
+
}
|
|
16464
|
+
function referenceFor(page, target) {
|
|
16465
|
+
return {
|
|
16466
|
+
...target ? { target } : {},
|
|
16467
|
+
...page.locale ? { locale: page.locale } : {},
|
|
16468
|
+
slug: page.slug,
|
|
16469
|
+
...page.key ? { key: page.key } : {}
|
|
16470
|
+
};
|
|
16471
|
+
}
|
|
16472
|
+
function sameReference(ref, page) {
|
|
16473
|
+
return ref.slug === page.slug && ref.locale === page.locale && ref.key === page.key;
|
|
16474
|
+
}
|
|
16475
|
+
function sortedRefs(refs) {
|
|
16476
|
+
return [...refs].sort((a, b) => sortIdentity(a.locale, a.key ?? a.slug).localeCompare(sortIdentity(b.locale, b.key ?? b.slug)));
|
|
16477
|
+
}
|
|
16478
|
+
function sortIdentity(locale, value) {
|
|
16479
|
+
return `${locale ?? ""}\0${value}`;
|
|
16480
|
+
}
|
|
16481
|
+
function buildLookup(pages, options) {
|
|
16482
|
+
const lookup = {
|
|
16483
|
+
bySlug: /* @__PURE__ */ new Map(),
|
|
16484
|
+
byScopedSlug: /* @__PURE__ */ new Map(),
|
|
16485
|
+
byKey: /* @__PURE__ */ new Map(),
|
|
16486
|
+
byScopedKey: /* @__PURE__ */ new Map(),
|
|
16487
|
+
aliasBySlug: /* @__PURE__ */ new Map(),
|
|
16488
|
+
aliasByScopedSlug: /* @__PURE__ */ new Map(),
|
|
16489
|
+
aliasByKey: /* @__PURE__ */ new Map(),
|
|
16490
|
+
aliasByScopedKey: /* @__PURE__ */ new Map()
|
|
16491
|
+
};
|
|
16492
|
+
for (const page of pages.values()) {
|
|
16493
|
+
pushMulti(lookup.bySlug, page.slug, page);
|
|
16494
|
+
lookup.byScopedSlug.set(scopedKey(page.slug, page.locale, options), page);
|
|
16495
|
+
if (page.key) {
|
|
16496
|
+
pushMulti(lookup.byKey, page.key, page);
|
|
16497
|
+
lookup.byScopedKey.set(scopedKey(page.key, page.locale, options), page);
|
|
16498
|
+
}
|
|
16499
|
+
for (const alias of page.aliases) {
|
|
16500
|
+
lookup.aliasBySlug.set(alias, page);
|
|
16501
|
+
lookup.aliasByScopedSlug.set(scopedKey(alias, page.locale, options), page);
|
|
16502
|
+
if (page.key) {
|
|
16503
|
+
lookup.aliasByKey.set(alias, page);
|
|
16504
|
+
lookup.aliasByScopedKey.set(scopedKey(alias, page.locale, options), page);
|
|
16505
|
+
}
|
|
16506
|
+
}
|
|
16507
|
+
}
|
|
16508
|
+
return lookup;
|
|
16509
|
+
}
|
|
16510
|
+
function pushMulti(map2, key, page) {
|
|
16511
|
+
const existing = map2.get(key);
|
|
16512
|
+
if (existing)
|
|
16513
|
+
existing.push(page);
|
|
16514
|
+
else
|
|
16515
|
+
map2.set(key, [page]);
|
|
16516
|
+
}
|
|
16517
|
+
function resolveTarget(rawTarget, source, lookup, options) {
|
|
16518
|
+
if (typeof rawTarget !== "string") {
|
|
16519
|
+
const locale2 = rawTarget.locale ?? source.locale;
|
|
16520
|
+
const page2 = rawTarget.key ? lookup.byScopedKey.get(scopedKey(rawTarget.key, locale2, options)) : rawTarget.slug ? lookup.byScopedSlug.get(scopedKey(rawTarget.slug, locale2, options)) : void 0;
|
|
16521
|
+
const target = rawTarget.key ?? rawTarget.slug ?? "";
|
|
16522
|
+
return {
|
|
16523
|
+
page: page2,
|
|
16524
|
+
target,
|
|
16525
|
+
explicitLocale: rawTarget.locale,
|
|
16526
|
+
crossLocale: Boolean(page2 && rawTarget.locale && rawTarget.locale !== source.locale),
|
|
16527
|
+
matchedBy: rawTarget.key ? "key" : "slug"
|
|
16528
|
+
};
|
|
16529
|
+
}
|
|
16530
|
+
const locale = source.locale;
|
|
16531
|
+
if (options.resolve === "global-slug") {
|
|
16532
|
+
const page2 = lookup.aliasBySlug.get(rawTarget) ?? single(lookup.bySlug.get(rawTarget));
|
|
16533
|
+
return { page: page2, target: rawTarget, crossLocale: false, matchedBy: page2 ? "slug" : void 0 };
|
|
16534
|
+
}
|
|
16535
|
+
if (options.resolve === "same-locale-key") {
|
|
16536
|
+
const scoped2 = scopedKey(rawTarget, locale, options);
|
|
16537
|
+
const page2 = lookup.byScopedKey.get(scoped2) ?? lookup.aliasByScopedKey.get(scoped2);
|
|
16538
|
+
return { page: page2, target: rawTarget, crossLocale: false, matchedBy: page2 ? "key" : void 0 };
|
|
16539
|
+
}
|
|
16540
|
+
if (options.resolve === "prefer-same-locale-key-fallback-slug") {
|
|
16541
|
+
const scoped2 = scopedKey(rawTarget, locale, options);
|
|
16542
|
+
const page2 = lookup.byScopedKey.get(scoped2) ?? lookup.aliasByScopedKey.get(scoped2) ?? lookup.byScopedSlug.get(scoped2) ?? lookup.aliasByScopedSlug.get(scoped2);
|
|
16543
|
+
return { page: page2, target: rawTarget, crossLocale: false, matchedBy: page2 ? "key" : void 0 };
|
|
16544
|
+
}
|
|
16545
|
+
const scoped = scopedKey(rawTarget, locale, options);
|
|
16546
|
+
const page = lookup.byScopedSlug.get(scoped) ?? lookup.aliasByScopedSlug.get(scoped);
|
|
16547
|
+
return { page, target: rawTarget, crossLocale: false, matchedBy: page ? "slug" : void 0 };
|
|
16548
|
+
}
|
|
16549
|
+
function single(values) {
|
|
16550
|
+
return values?.length === 1 ? values[0] : void 0;
|
|
16551
|
+
}
|
|
16552
|
+
function usesKeyResolution(options) {
|
|
16553
|
+
return options.resolve === "same-locale-key";
|
|
16554
|
+
}
|
|
16555
|
+
function collidesWithPageIdentity(alias, locale, lookup, options) {
|
|
16556
|
+
if (options.resolve === "global-slug")
|
|
16557
|
+
return lookup.bySlug.has(alias);
|
|
16558
|
+
const scoped = scopedKey(alias, locale, options);
|
|
16559
|
+
return lookup.byScopedSlug.has(scoped) || usesKeyResolution(options) && lookup.byScopedKey.has(scoped);
|
|
16560
|
+
}
|
|
16561
|
+
function targetExistsOutsideLocale(target, locale, lookup, options) {
|
|
16562
|
+
if (options.resolve === "global-slug")
|
|
16563
|
+
return false;
|
|
16564
|
+
const byIdentity = usesKeyResolution(options) ? lookup.byKey : lookup.bySlug;
|
|
16565
|
+
return (byIdentity.get(target) ?? []).some((page) => page.locale !== locale);
|
|
16566
|
+
}
|
|
16567
|
+
function candidatesFor(locale, lookup, options) {
|
|
16568
|
+
if (options.resolve === "global-slug")
|
|
16569
|
+
return [...lookup.bySlug.keys()];
|
|
16570
|
+
const out = [];
|
|
16571
|
+
for (const page of lookup.byScopedSlug.values()) {
|
|
16572
|
+
if (page.locale === locale)
|
|
16573
|
+
out.push(usesKeyResolution(options) && page.key ? page.key : page.slug);
|
|
16574
|
+
}
|
|
16575
|
+
return out;
|
|
16576
|
+
}
|
|
16577
|
+
function inputForPage(page, inputs, options) {
|
|
16578
|
+
for (const input of inputs) {
|
|
16579
|
+
const parsed = parseLinkFrontmatter(input.data, options);
|
|
16580
|
+
if (!parsed.ok || parsed.value === null)
|
|
16581
|
+
continue;
|
|
16582
|
+
const identity = frontmatterIdentity(parsed.value, options);
|
|
16583
|
+
if (pageMapKey(identity, options) === pageMapKey(page, options))
|
|
16584
|
+
return input.data;
|
|
16585
|
+
}
|
|
16586
|
+
return {};
|
|
16587
|
+
}
|
|
16588
|
+
function diag(file2, code, severity, message, hint) {
|
|
16589
|
+
return { file: file2, diagnostic: { code, severity, message, hint, position: FM_POSITION } };
|
|
16590
|
+
}
|
|
16591
|
+
function editDistance(a, b) {
|
|
16592
|
+
const dp = Array.from({ length: a.length + 1 }, (_, i) => [i, ...Array(b.length).fill(0)]);
|
|
16593
|
+
for (let j = 0; j <= b.length; j++)
|
|
16594
|
+
dp[0][j] = j;
|
|
16595
|
+
for (let i = 1; i <= a.length; i++) {
|
|
16596
|
+
for (let j = 1; j <= b.length; j++) {
|
|
16597
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
16598
|
+
dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
|
|
16599
|
+
}
|
|
16600
|
+
}
|
|
16601
|
+
return dp[a.length][b.length];
|
|
16602
|
+
}
|
|
16603
|
+
function closest(target, candidates) {
|
|
16604
|
+
let best;
|
|
16605
|
+
let bestD = Infinity;
|
|
16606
|
+
for (const c of candidates) {
|
|
16607
|
+
const d = editDistance(target, c);
|
|
16608
|
+
if (d < bestD) {
|
|
16609
|
+
bestD = d;
|
|
16610
|
+
best = c;
|
|
16611
|
+
}
|
|
16612
|
+
}
|
|
16613
|
+
return best && bestD <= Math.max(2, Math.floor(target.length / 3)) ? best : void 0;
|
|
16614
|
+
}
|
|
16615
|
+
var Keywords, LinkTarget, LinkFrontmatter, DEFAULT_OPTIONS, FM_POSITION;
|
|
16616
|
+
var init_links = __esm({
|
|
16617
|
+
"../core/dist/links.js"() {
|
|
16618
|
+
"use strict";
|
|
16619
|
+
init_zod();
|
|
16620
|
+
Keywords = external_exports.object({
|
|
16621
|
+
primary: external_exports.string().optional(),
|
|
16622
|
+
secondary: external_exports.array(external_exports.string()).optional()
|
|
16623
|
+
});
|
|
16624
|
+
LinkTarget = external_exports.union([
|
|
16625
|
+
external_exports.string(),
|
|
16626
|
+
external_exports.object({
|
|
16627
|
+
slug: external_exports.string().min(1).optional(),
|
|
16628
|
+
key: external_exports.string().min(1).optional(),
|
|
16629
|
+
locale: external_exports.string().min(1).optional()
|
|
16630
|
+
}).refine((target) => target.slug || target.key, {
|
|
16631
|
+
message: "object linksTo targets must include slug or key"
|
|
16632
|
+
})
|
|
16633
|
+
]);
|
|
16634
|
+
LinkFrontmatter = external_exports.object({
|
|
16635
|
+
slug: external_exports.string().min(1),
|
|
16636
|
+
key: external_exports.string().min(1).optional(),
|
|
16637
|
+
locale: external_exports.string().min(1).optional(),
|
|
16638
|
+
title: external_exports.string().optional(),
|
|
16639
|
+
linksTo: external_exports.array(LinkTarget).optional(),
|
|
16640
|
+
aliases: external_exports.array(external_exports.string()).optional(),
|
|
16641
|
+
keywords: Keywords.optional()
|
|
16642
|
+
});
|
|
16643
|
+
DEFAULT_OPTIONS = {
|
|
16644
|
+
resolve: "global-slug"
|
|
16645
|
+
};
|
|
16646
|
+
FM_POSITION = {
|
|
16647
|
+
start: { line: 1, column: 1, offset: 0 },
|
|
16648
|
+
end: { line: 1, column: 1, offset: 0 }
|
|
16649
|
+
};
|
|
16650
|
+
}
|
|
16651
|
+
});
|
|
16652
|
+
|
|
16653
|
+
// ../core/dist/index.js
|
|
16654
|
+
var init_dist = __esm({
|
|
16655
|
+
"../core/dist/index.js"() {
|
|
16656
|
+
"use strict";
|
|
16657
|
+
init_diagnostics();
|
|
16658
|
+
init_parser();
|
|
16659
|
+
init_frontmatter();
|
|
16660
|
+
init_analyze();
|
|
16661
|
+
init_registry();
|
|
16662
|
+
init_content_models();
|
|
16663
|
+
init_validate();
|
|
16664
|
+
init_render_markdown();
|
|
16665
|
+
init_links();
|
|
16666
|
+
}
|
|
16667
|
+
});
|
|
16668
|
+
|
|
15832
16669
|
// ../blocks/dist/blocks/callout.js
|
|
15833
16670
|
var calloutBlock, calloutMarkdown;
|
|
15834
16671
|
var init_callout = __esm({
|
|
@@ -16181,16 +17018,269 @@ var init_load_registry = __esm({
|
|
|
16181
17018
|
}
|
|
16182
17019
|
});
|
|
16183
17020
|
|
|
17021
|
+
// src/commands/agents.ts
|
|
17022
|
+
var agents_exports = {};
|
|
17023
|
+
__export(agents_exports, {
|
|
17024
|
+
agentsCommand: () => agentsCommand,
|
|
17025
|
+
installAgentIntegration: () => installAgentIntegration
|
|
17026
|
+
});
|
|
17027
|
+
import { existsSync } from "node:fs";
|
|
17028
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
17029
|
+
import { join } from "node:path";
|
|
17030
|
+
import { parseArgs } from "node:util";
|
|
17031
|
+
function upsertBlock(existing) {
|
|
17032
|
+
const start = existing.indexOf(START);
|
|
17033
|
+
const end = existing.indexOf(END);
|
|
17034
|
+
if (start !== -1 && end !== -1) {
|
|
17035
|
+
return existing.slice(0, start) + AGENTS_MD_BLOCK + existing.slice(end + END.length);
|
|
17036
|
+
}
|
|
17037
|
+
if (existing.trim() === "") return `${AGENTS_MD_BLOCK}
|
|
17038
|
+
`;
|
|
17039
|
+
return `${existing.replace(/\n*$/, "\n\n")}${AGENTS_MD_BLOCK}
|
|
17040
|
+
`;
|
|
17041
|
+
}
|
|
17042
|
+
async function installAgentIntegration(cwd, options, io) {
|
|
17043
|
+
const claude = options.claude ?? existsSync(join(cwd, ".claude"));
|
|
17044
|
+
const agentsMd = options.agentsMd ?? true;
|
|
17045
|
+
if (agentsMd) {
|
|
17046
|
+
const path = join(cwd, "AGENTS.md");
|
|
17047
|
+
let existing = "";
|
|
17048
|
+
try {
|
|
17049
|
+
existing = await readFile(path, "utf8");
|
|
17050
|
+
} catch {
|
|
17051
|
+
}
|
|
17052
|
+
const created = existing === "";
|
|
17053
|
+
await writeFile(path, upsertBlock(existing), "utf8");
|
|
17054
|
+
io.stdout(`${created ? "created" : "updated"}: AGENTS.md (contentbit block)`);
|
|
17055
|
+
}
|
|
17056
|
+
if (claude) {
|
|
17057
|
+
const skills = [
|
|
17058
|
+
["contentbit-author", AUTHOR_SKILL],
|
|
17059
|
+
["contentbit-audit", AUDIT_SKILL]
|
|
17060
|
+
];
|
|
17061
|
+
for (const [name, content] of skills) {
|
|
17062
|
+
const dir = join(cwd, ".claude/skills", name);
|
|
17063
|
+
await mkdir(dir, { recursive: true });
|
|
17064
|
+
await writeFile(join(dir, "SKILL.md"), content, "utf8");
|
|
17065
|
+
io.stdout(`installed: .claude/skills/${name}/SKILL.md`);
|
|
17066
|
+
}
|
|
17067
|
+
}
|
|
17068
|
+
}
|
|
17069
|
+
async function agentsCommand(args, io) {
|
|
17070
|
+
const { values } = parseArgs({
|
|
17071
|
+
args,
|
|
17072
|
+
options: {
|
|
17073
|
+
claude: { type: "boolean", default: false },
|
|
17074
|
+
"no-agents-md": { type: "boolean", default: false },
|
|
17075
|
+
cwd: { type: "string", default: process.cwd() }
|
|
17076
|
+
}
|
|
17077
|
+
});
|
|
17078
|
+
await installAgentIntegration(
|
|
17079
|
+
values.cwd,
|
|
17080
|
+
{
|
|
17081
|
+
claude: values.claude || void 0,
|
|
17082
|
+
// false means "detect", not "skip"
|
|
17083
|
+
agentsMd: !values["no-agents-md"]
|
|
17084
|
+
},
|
|
17085
|
+
io
|
|
17086
|
+
);
|
|
17087
|
+
return 0;
|
|
17088
|
+
}
|
|
17089
|
+
var TEMPLATE_VERSION, AUTHOR_SKILL, AUDIT_SKILL, AGENTS_MD_BLOCK, START, END;
|
|
17090
|
+
var init_agents = __esm({
|
|
17091
|
+
"src/commands/agents.ts"() {
|
|
17092
|
+
"use strict";
|
|
17093
|
+
TEMPLATE_VERSION = 2;
|
|
17094
|
+
AUTHOR_SKILL = `---
|
|
17095
|
+
name: contentbit-author
|
|
17096
|
+
description: |
|
|
17097
|
+
Write or edit contentbit Markdown content (directive blocks like :::callout).
|
|
17098
|
+
Use when asked to create or modify content documents in a project that uses
|
|
17099
|
+
contentbit \u2014 blog posts, docs pages, changelogs, any Markdown covered by
|
|
17100
|
+
\`contentbit validate\`.
|
|
17101
|
+
version: ${TEMPLATE_VERSION}
|
|
17102
|
+
---
|
|
17103
|
+
|
|
17104
|
+
# Writing contentbit content
|
|
17105
|
+
|
|
17106
|
+
contentbit documents are plain Markdown plus directive blocks
|
|
17107
|
+
(\`:::name{props} ... :::\`). Every block has a schema. Never guess block names,
|
|
17108
|
+
props, or body shapes \u2014 fetch the live guide from the project's registry first.
|
|
17109
|
+
|
|
17110
|
+
## Find the project conventions
|
|
17111
|
+
|
|
17112
|
+
Check \`package.json\` for a \`content:check\` script. It holds the canonical
|
|
17113
|
+
validate invocation for this project: the content glob and, if present, the
|
|
17114
|
+
\`--registry <path>\` flag pointing at custom block definitions. Reuse both
|
|
17115
|
+
below. No script? Default to \`content/**/*.md\` with no \`--registry\` flag.
|
|
17116
|
+
If the project has a \`content:links\` script, use it for the internal-link
|
|
17117
|
+
index; otherwise run \`contentbit links <content glob>\` directly.
|
|
17118
|
+
|
|
17119
|
+
## The loop
|
|
17120
|
+
|
|
17121
|
+
1. **Fetch the authoring guide** (always \u2014 it covers this project's custom blocks):
|
|
17122
|
+
|
|
17123
|
+
\`\`\`sh
|
|
17124
|
+
contentbit instructions --audience llm [--registry <path from content:check>]
|
|
17125
|
+
\`\`\`
|
|
17126
|
+
|
|
17127
|
+
Read it before writing. It documents every available block: props, body
|
|
17128
|
+
shape, and when to use or avoid it.
|
|
17129
|
+
|
|
17130
|
+
2. **Write the document.** Plain Markdown everywhere; blocks only where the
|
|
17131
|
+
guide's use-when guidance fits. Keep frontmatter consistent with sibling
|
|
17132
|
+
documents in the same folder. If sibling documents use \`slug\`, \`linksTo\`,
|
|
17133
|
+
\`aliases\`, or \`keywords\`, run the link index first:
|
|
17134
|
+
|
|
17135
|
+
\`\`\`sh
|
|
17136
|
+
contentbit links <content glob>
|
|
17137
|
+
\`\`\`
|
|
17138
|
+
|
|
17139
|
+
Read \`.contentbit/link-index.json\` to pick existing slugs and related
|
|
17140
|
+
pages. Author only \`slug\`, \`linksTo\`, \`aliases\`, and \`keywords\` in
|
|
17141
|
+
frontmatter; never write derived \`linkedFrom\` into source files. When
|
|
17142
|
+
creating a linked page, include \`keywords.primary\` and
|
|
17143
|
+
\`keywords.secondary\` with search-intent phrases that would help future
|
|
17144
|
+
agents choose this page as a \`linksTo\` target.
|
|
17145
|
+
|
|
17146
|
+
3. **Validate and fix until clean:**
|
|
17147
|
+
|
|
17148
|
+
\`\`\`sh
|
|
17149
|
+
contentbit validate <file> [--registry <path>]
|
|
17150
|
+
\`\`\`
|
|
17151
|
+
|
|
17152
|
+
Diagnostics print to stderr as \`file:line:col severity CODE message\`, often
|
|
17153
|
+
with a \`hint:\` line suggesting the fix. Exit 0 means clean; exit 1 means
|
|
17154
|
+
errors remain. If the document has link frontmatter, validate the full
|
|
17155
|
+
content glob so cross-file links are checked against the whole graph. Fix
|
|
17156
|
+
every diagnostic and re-run. Never finish with a failing validate.
|
|
17157
|
+
|
|
17158
|
+
4. **Refresh internal links when present:**
|
|
17159
|
+
|
|
17160
|
+
\`\`\`sh
|
|
17161
|
+
contentbit links <content glob> --fix
|
|
17162
|
+
\`\`\`
|
|
17163
|
+
|
|
17164
|
+
\`--fix\` only rewrites \`linksTo\` values that point at known aliases. It
|
|
17165
|
+
does not invent links, remove aliases, or write backlinks. Re-run validate
|
|
17166
|
+
after it changes files.
|
|
17167
|
+
|
|
17168
|
+
## Failure modes
|
|
17169
|
+
|
|
17170
|
+
- \`contentbit\` not found or no registry resolvable: the project is not set up.
|
|
17171
|
+
Say so and suggest \`npx contentbit@latest init\` \u2014 do not invent block syntax.
|
|
17172
|
+
- A block you want does not exist: use plain Markdown, or ask whether to define
|
|
17173
|
+
a custom block in the registry. Never emit an unregistered block name.
|
|
17174
|
+
`;
|
|
17175
|
+
AUDIT_SKILL = `---
|
|
17176
|
+
name: contentbit-audit
|
|
17177
|
+
description: |
|
|
17178
|
+
Audit contentbit Markdown content health using document stats. Use when asked
|
|
17179
|
+
to audit, review, or find improvements across content \u2014 thin pages, missing
|
|
17180
|
+
structure, validation issues \u2014 in a project that uses contentbit.
|
|
17181
|
+
version: ${TEMPLATE_VERSION}
|
|
17182
|
+
---
|
|
17183
|
+
|
|
17184
|
+
# Auditing contentbit content
|
|
17185
|
+
|
|
17186
|
+
\`contentbit stats\` analyzes documents and prints JSON to stdout. It is a read
|
|
17187
|
+
tool: it always exits 0, even when documents have validation errors.
|
|
17188
|
+
\`contentbit links\` builds the frontmatter-authored internal-link graph and
|
|
17189
|
+
prints link diagnostics.
|
|
17190
|
+
|
|
17191
|
+
## Gather
|
|
17192
|
+
|
|
17193
|
+
Check \`package.json\` for the \`content:check\` script to find this project's
|
|
17194
|
+
content glob and \`--registry\` flag, then:
|
|
17195
|
+
|
|
17196
|
+
\`\`\`sh
|
|
17197
|
+
contentbit stats "content/**/*.md" [--registry <path>]
|
|
17198
|
+
contentbit links "content/**/*.md"
|
|
17199
|
+
\`\`\`
|
|
17200
|
+
|
|
17201
|
+
One matched file prints a single stats object; multiple files print an array.
|
|
17202
|
+
Each entry includes the file path, frontmatter data, a heading \`outline\` with
|
|
17203
|
+
per-section word counts, \`blocks.byName\` usage counts, \`links.domains\`, and
|
|
17204
|
+
a \`validation\` summary (\`errors\`/\`warnings\`).
|
|
17205
|
+
\`contentbit links\` also writes \`.contentbit/link-index.json\`, whose pages
|
|
17206
|
+
contain \`slug\`, resolved \`linksTo\`, derived \`linkedFrom\`, \`aliases\`, and
|
|
17207
|
+
\`keywords\`.
|
|
17208
|
+
|
|
17209
|
+
## Interpret
|
|
17210
|
+
|
|
17211
|
+
Prioritize findings in this order:
|
|
17212
|
+
|
|
17213
|
+
1. **Validation errors and warnings** \u2014 broken content ships broken pages.
|
|
17214
|
+
2. **Internal-link errors** \u2014 unresolved links, duplicate slugs, and alias
|
|
17215
|
+
conflicts from \`contentbit links\`.
|
|
17216
|
+
3. **Orphans and self-links** \u2014 link warnings that point to isolated or noisy
|
|
17217
|
+
pages.
|
|
17218
|
+
4. **Thin documents** \u2014 outline sections with very low word counts.
|
|
17219
|
+
5. **Block-less documents** \u2014 \`blocks.byName\` empty where sibling documents
|
|
17220
|
+
use blocks; structure (steps, callouts, comparisons, faq) may be missing.
|
|
17221
|
+
6. **Missing or inconsistent frontmatter** compared to sibling documents.
|
|
17222
|
+
7. **Structural imbalance** \u2014 skipped heading levels, single-section walls of text.
|
|
17223
|
+
|
|
17224
|
+
## Report
|
|
17225
|
+
|
|
17226
|
+
Report findings per file with concrete suggestions, ordered by priority. Do not
|
|
17227
|
+
edit files during the audit. To fix a finding, follow the contentbit-author
|
|
17228
|
+
skill (fetch the guide, edit, validate until clean) \u2014 offer that as a follow-up.
|
|
17229
|
+
`;
|
|
17230
|
+
AGENTS_MD_BLOCK = `<!-- contentbit:start -->
|
|
17231
|
+
|
|
17232
|
+
## contentbit content (generated \u2014 edits inside this block are overwritten)
|
|
17233
|
+
|
|
17234
|
+
This project validates Markdown content with contentbit. Documents are plain
|
|
17235
|
+
Markdown plus directive blocks (\`:::name{props} ... :::\`), each with a schema.
|
|
17236
|
+
The \`content:check\` script in package.json holds the canonical validate
|
|
17237
|
+
command \u2014 the content glob and the \`--registry\` flag \u2014 reuse its arguments.
|
|
17238
|
+
If the project has a \`content:links\` script, use it to build the internal-link
|
|
17239
|
+
index; otherwise run \`contentbit links <content glob>\`.
|
|
17240
|
+
|
|
17241
|
+
When writing or editing content:
|
|
17242
|
+
|
|
17243
|
+
1. Fetch the live authoring guide first \u2014 never guess block syntax:
|
|
17244
|
+
\`contentbit instructions --audience llm [--registry <path>]\`
|
|
17245
|
+
2. Write plain Markdown; use blocks where the guide's use-when guidance fits.
|
|
17246
|
+
3. If sibling documents use \`slug\` / \`linksTo\`, read
|
|
17247
|
+
\`.contentbit/link-index.json\` from \`contentbit links <content glob>\` and
|
|
17248
|
+
author frontmatter links with existing slugs. When creating a linked page,
|
|
17249
|
+
include \`keywords.primary\` and \`keywords.secondary\` with search-intent
|
|
17250
|
+
phrases future agents can use to choose related pages.
|
|
17251
|
+
4. Validate until clean (exit 0): \`contentbit validate <file> [--registry <path>]\`.
|
|
17252
|
+
Diagnostics print as \`file:line:col severity CODE message\` with fix hints.
|
|
17253
|
+
For link frontmatter, validate the full content glob so cross-file checks run.
|
|
17254
|
+
|
|
17255
|
+
When auditing content health:
|
|
17256
|
+
|
|
17257
|
+
- \`contentbit stats "content/**/*.md" [--registry <path>]\` prints JSON stats
|
|
17258
|
+
and always exits 0: outline word counts, block usage, link domains, and
|
|
17259
|
+
validation error/warning counts. Flag validation issues, thin documents, and
|
|
17260
|
+
block-less pages first.
|
|
17261
|
+
- \`contentbit links "content/**/*.md" [--fix]\` builds
|
|
17262
|
+
\`.contentbit/link-index.json\`, reports dangling links/orphans, and rewrites
|
|
17263
|
+
alias references in \`linksTo\` when \`--fix\` is used.
|
|
17264
|
+
|
|
17265
|
+
If \`contentbit\` is unavailable, suggest \`npx contentbit@latest init\` instead
|
|
17266
|
+
of inventing block syntax.
|
|
17267
|
+
|
|
17268
|
+
<!-- contentbit:end -->`;
|
|
17269
|
+
START = "<!-- contentbit:start -->";
|
|
17270
|
+
END = "<!-- contentbit:end -->";
|
|
17271
|
+
}
|
|
17272
|
+
});
|
|
17273
|
+
|
|
16184
17274
|
// src/commands/init.ts
|
|
16185
17275
|
var init_exports = {};
|
|
16186
17276
|
__export(init_exports, {
|
|
16187
17277
|
initCommand: () => initCommand
|
|
16188
17278
|
});
|
|
16189
17279
|
import { spawn } from "node:child_process";
|
|
16190
|
-
import { existsSync } from "node:fs";
|
|
16191
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
16192
|
-
import { join } from "node:path";
|
|
16193
|
-
import { parseArgs } from "node:util";
|
|
17280
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
17281
|
+
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
17282
|
+
import { join as join2 } from "node:path";
|
|
17283
|
+
import { parseArgs as parseArgs2 } from "node:util";
|
|
16194
17284
|
function blockComponentsTemplate(styled) {
|
|
16195
17285
|
const body = styled ? ` return (
|
|
16196
17286
|
<figure className="my-6 border-s-2 ps-4">
|
|
@@ -16238,7 +17328,7 @@ import { ContentRenderer } from '@/components/content-blocks/content-renderer'`
|
|
|
16238
17328
|
return `'use client'
|
|
16239
17329
|
|
|
16240
17330
|
import { genericBlocks } from '@contentbit/blocks'
|
|
16241
|
-
import { createBlockRegistry, parseDocument, validateDocument } from '@contentbit/core'
|
|
17331
|
+
import { createBlockRegistry, parseDocument, stripFrontmatter, validateDocument } from '@contentbit/core'
|
|
16242
17332
|
${reactImport}${mdImport}${rendererImport}
|
|
16243
17333
|
// Everything block-related lives in the blocks/ folder: definitions in
|
|
16244
17334
|
// registry.ts (shared with the validate CLI), components in components.tsx.
|
|
@@ -16248,7 +17338,7 @@ import { blockComponents } from '${blocksImport}/components'
|
|
|
16248
17338
|
const registry = createBlockRegistry().use(genericBlocks()).use(customBlocks)
|
|
16249
17339
|
|
|
16250
17340
|
export function Content({ source }: { source: string }) {
|
|
16251
|
-
const result = validateDocument(parseDocument(source), registry)
|
|
17341
|
+
const result = validateDocument(parseDocument(stripFrontmatter(source)), registry)
|
|
16252
17342
|
return (
|
|
16253
17343
|
<${renderer}
|
|
16254
17344
|
document={result.document}
|
|
@@ -16268,21 +17358,21 @@ const renderMarkdown = (md) => mdIt.render(md)` : `// TODO: plug a Markdown libr
|
|
|
16268
17358
|
const renderMarkdown = undefined`;
|
|
16269
17359
|
return `// Render content/example.md to example.html. Run: node scripts/render-example.mjs
|
|
16270
17360
|
import { genericBlocks } from '@contentbit/blocks'
|
|
16271
|
-
import { createBlockRegistry, parseDocument, validateDocument } from '@contentbit/core'
|
|
17361
|
+
import { createBlockRegistry, parseDocument, stripFrontmatter, validateDocument } from '@contentbit/core'
|
|
16272
17362
|
import { renderToHtml } from '@contentbit/html'
|
|
16273
17363
|
import { readFile, writeFile } from 'node:fs/promises'
|
|
16274
17364
|
${wiring}
|
|
16275
17365
|
|
|
16276
17366
|
const source = await readFile('content/example.md', 'utf8')
|
|
16277
17367
|
const registry = createBlockRegistry().use(genericBlocks())
|
|
16278
|
-
const result = validateDocument(parseDocument(source), registry)
|
|
17368
|
+
const result = validateDocument(parseDocument(stripFrontmatter(source)), registry)
|
|
16279
17369
|
const html = renderToHtml(result.document, { renderMarkdown })
|
|
16280
17370
|
await writeFile('example.html', html, 'utf8')
|
|
16281
17371
|
console.log('wrote example.html')
|
|
16282
17372
|
`;
|
|
16283
17373
|
}
|
|
16284
17374
|
function detectFramework(cwd, deps) {
|
|
16285
|
-
if ((deps["@tanstack/react-start"] || deps["@tanstack/react-router"]) &&
|
|
17375
|
+
if ((deps["@tanstack/react-start"] || deps["@tanstack/react-router"]) && existsSync2(join2(cwd, "src/routes"))) {
|
|
16286
17376
|
return {
|
|
16287
17377
|
framework: "tanstack",
|
|
16288
17378
|
componentPath: "src/components/content-blocks.tsx",
|
|
@@ -16290,8 +17380,8 @@ function detectFramework(cwd, deps) {
|
|
|
16290
17380
|
};
|
|
16291
17381
|
}
|
|
16292
17382
|
if (deps.next) {
|
|
16293
|
-
const appDir =
|
|
16294
|
-
if (
|
|
17383
|
+
const appDir = existsSync2(join2(cwd, "src/app")) ? "src/app" : "app";
|
|
17384
|
+
if (existsSync2(join2(cwd, appDir))) {
|
|
16295
17385
|
return {
|
|
16296
17386
|
framework: "next",
|
|
16297
17387
|
componentPath: "components/content-blocks.tsx",
|
|
@@ -16301,6 +17391,34 @@ function detectFramework(cwd, deps) {
|
|
|
16301
17391
|
}
|
|
16302
17392
|
return { framework: null, componentPath: "components/content-blocks.tsx", pagePath: null };
|
|
16303
17393
|
}
|
|
17394
|
+
function astroPage(styled) {
|
|
17395
|
+
const importLine = styled ? "import ContentRenderer from '../components/content-blocks/content-renderer.astro'" : "import { ContentBlocks } from '@contentbit/astro/components'";
|
|
17396
|
+
const renderer = styled ? "ContentRenderer" : "ContentBlocks";
|
|
17397
|
+
return `---
|
|
17398
|
+
import { genericBlocks } from '@contentbit/blocks'
|
|
17399
|
+
import { createBlockRegistry, parseDocument, validateDocument } from '@contentbit/core'
|
|
17400
|
+
import { getEntry } from 'astro:content'
|
|
17401
|
+
|
|
17402
|
+
${importLine}
|
|
17403
|
+
|
|
17404
|
+
// Definitions in blocks/registry.ts are shared with the validate CLI.
|
|
17405
|
+
import customBlocks from '../../blocks/registry'
|
|
17406
|
+
import QuoteBlock from '../../blocks/QuoteBlock.astro'
|
|
17407
|
+
|
|
17408
|
+
// Entry ids are the file path relative to the collection base, minus ".md".
|
|
17409
|
+
const entry = await getEntry('articles', 'example')
|
|
17410
|
+
if (!entry?.body) throw new Error('Entry "example" not found in the articles collection.')
|
|
17411
|
+
|
|
17412
|
+
const registry = createBlockRegistry().use(genericBlocks()).use(customBlocks)
|
|
17413
|
+
// Static pages render at build time, so invalid blocks fail the build here.
|
|
17414
|
+
const result = validateDocument(parseDocument(entry.body), registry)
|
|
17415
|
+
---
|
|
17416
|
+
|
|
17417
|
+
<main style="max-width: 42rem; margin: 0 auto; padding: 3rem 1.5rem;">
|
|
17418
|
+
<${renderer} document={result.document} components={{ quote: QuoteBlock }} />
|
|
17419
|
+
</main>
|
|
17420
|
+
`;
|
|
17421
|
+
}
|
|
16304
17422
|
function detectPackageManager(cwd) {
|
|
16305
17423
|
const locks = [
|
|
16306
17424
|
["pnpm-lock.yaml", "pnpm"],
|
|
@@ -16310,7 +17428,7 @@ function detectPackageManager(cwd) {
|
|
|
16310
17428
|
["package-lock.json", "npm"]
|
|
16311
17429
|
];
|
|
16312
17430
|
for (const [file2, pm] of locks) {
|
|
16313
|
-
if (
|
|
17431
|
+
if (existsSync2(join2(cwd, file2))) return pm;
|
|
16314
17432
|
}
|
|
16315
17433
|
const agent = process.env.npm_config_user_agent ?? "";
|
|
16316
17434
|
for (const pm of ["pnpm", "yarn", "bun"]) {
|
|
@@ -16335,18 +17453,38 @@ function runInstall(pm, args, cwd) {
|
|
|
16335
17453
|
child.on("error", () => resolve(1));
|
|
16336
17454
|
});
|
|
16337
17455
|
}
|
|
17456
|
+
async function installStyledPack(cwd, pack, noInstall, io) {
|
|
17457
|
+
const componentsJsonPath = join2(cwd, "components.json");
|
|
17458
|
+
const componentsJson = JSON.parse(await readFile2(componentsJsonPath, "utf8"));
|
|
17459
|
+
componentsJson.registries ??= {};
|
|
17460
|
+
if (!componentsJson.registries["@contentbit"]) {
|
|
17461
|
+
componentsJson.registries["@contentbit"] = "https://contentbit.dev/r/{name}.json";
|
|
17462
|
+
await writeFile2(componentsJsonPath, `${JSON.stringify(componentsJson, null, 2)}
|
|
17463
|
+
`, "utf8");
|
|
17464
|
+
io.stdout("added @contentbit registry to components.json");
|
|
17465
|
+
}
|
|
17466
|
+
if (noInstall) {
|
|
17467
|
+
io.stdout(`skipped: shadcn add ${pack}`);
|
|
17468
|
+
return true;
|
|
17469
|
+
}
|
|
17470
|
+
const [bin, prefix] = dlxCommand(detectPackageManager(cwd));
|
|
17471
|
+
io.stdout(`installing the styled pack: shadcn add ${pack}`);
|
|
17472
|
+
const code = await runInstall(bin, [...prefix, "shadcn@latest", "add", pack, "--yes"], cwd);
|
|
17473
|
+
if (code !== 0) io.stderr("styled pack install failed; falling back to headless defaults");
|
|
17474
|
+
return code === 0;
|
|
17475
|
+
}
|
|
16338
17476
|
async function scaffold(path, content) {
|
|
16339
17477
|
try {
|
|
16340
|
-
await
|
|
17478
|
+
await readFile2(path, "utf8");
|
|
16341
17479
|
return "skipped";
|
|
16342
17480
|
} catch {
|
|
16343
|
-
await
|
|
16344
|
-
await
|
|
17481
|
+
await mkdir2(join2(path, ".."), { recursive: true });
|
|
17482
|
+
await writeFile2(path, content, "utf8");
|
|
16345
17483
|
return "created";
|
|
16346
17484
|
}
|
|
16347
17485
|
}
|
|
16348
17486
|
async function initCommand(args, io) {
|
|
16349
|
-
const { values } =
|
|
17487
|
+
const { values } = parseArgs2({
|
|
16350
17488
|
args,
|
|
16351
17489
|
options: {
|
|
16352
17490
|
target: { type: "string", short: "t" },
|
|
@@ -16355,20 +17493,22 @@ async function initCommand(args, io) {
|
|
|
16355
17493
|
cwd: { type: "string", default: process.cwd() },
|
|
16356
17494
|
"no-install": { type: "boolean", default: false },
|
|
16357
17495
|
"no-page": { type: "boolean", default: false },
|
|
16358
|
-
"no-styled": { type: "boolean", default: false }
|
|
17496
|
+
"no-styled": { type: "boolean", default: false },
|
|
17497
|
+
"no-agents": { type: "boolean", default: false }
|
|
16359
17498
|
}
|
|
16360
17499
|
});
|
|
16361
17500
|
const cwd = values.cwd;
|
|
16362
17501
|
let pkg;
|
|
16363
|
-
const pkgPath =
|
|
17502
|
+
const pkgPath = join2(cwd, "package.json");
|
|
16364
17503
|
try {
|
|
16365
|
-
pkg = JSON.parse(await
|
|
17504
|
+
pkg = JSON.parse(await readFile2(pkgPath, "utf8"));
|
|
16366
17505
|
} catch {
|
|
16367
17506
|
io.stderr("No package.json found. Run this inside a project (npm init first).");
|
|
16368
17507
|
return 1;
|
|
16369
17508
|
}
|
|
16370
17509
|
const hasReact = Boolean(pkg.dependencies?.react ?? pkg.devDependencies?.react);
|
|
16371
|
-
const
|
|
17510
|
+
const hasAstro = Boolean(pkg.dependencies?.astro ?? pkg.devDependencies?.astro);
|
|
17511
|
+
const detected = hasAstro ? "astro" : hasReact ? "react" : "html";
|
|
16372
17512
|
let target;
|
|
16373
17513
|
if (values.target) {
|
|
16374
17514
|
if (!TARGETS.includes(values.target)) {
|
|
@@ -16383,6 +17523,7 @@ async function initCommand(args, io) {
|
|
|
16383
17523
|
initialValue: detected,
|
|
16384
17524
|
options: [
|
|
16385
17525
|
{ value: "react", label: "React", hint: "ContentBlocks component" },
|
|
17526
|
+
{ value: "astro", label: "Astro", hint: "content collections + .astro components" },
|
|
16386
17527
|
{ value: "html", label: "Static HTML", hint: "renderToHtml, no framework" },
|
|
16387
17528
|
{ value: "markdown", label: "Plain Markdown", hint: "fallback rendering only" }
|
|
16388
17529
|
]
|
|
@@ -16419,6 +17560,7 @@ async function initCommand(args, io) {
|
|
|
16419
17560
|
const runtime = ["@contentbit/core", "@contentbit/blocks", "zod"];
|
|
16420
17561
|
if (target === "react") runtime.push("@contentbit/react");
|
|
16421
17562
|
if (target === "html") runtime.push("@contentbit/html");
|
|
17563
|
+
if (target === "astro") runtime.push("@contentbit/astro");
|
|
16422
17564
|
if (md !== "none") runtime.push(md);
|
|
16423
17565
|
if (values["no-install"]) {
|
|
16424
17566
|
io.stdout(`skipped install: ${runtime.join(" ")} + contentbit (dev)`);
|
|
@@ -16436,34 +17578,14 @@ async function initCommand(args, io) {
|
|
|
16436
17578
|
}
|
|
16437
17579
|
const files = [
|
|
16438
17580
|
["blocks/registry.ts", REGISTRY_TEMPLATE],
|
|
16439
|
-
["content/example.md", EXAMPLE_CONTENT]
|
|
17581
|
+
["content/example.md", EXAMPLE_CONTENT],
|
|
17582
|
+
["content/related.md", RELATED_CONTENT]
|
|
16440
17583
|
];
|
|
16441
17584
|
const layout = detectFramework(cwd, { ...pkg.dependencies, ...pkg.devDependencies });
|
|
16442
17585
|
let styled = false;
|
|
16443
|
-
const componentsJsonPath =
|
|
16444
|
-
if (target === "react" && !values["no-styled"] &&
|
|
16445
|
-
|
|
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
|
-
}
|
|
17586
|
+
const componentsJsonPath = join2(cwd, "components.json");
|
|
17587
|
+
if (target === "react" && !values["no-styled"] && existsSync2(componentsJsonPath)) {
|
|
17588
|
+
styled = await installStyledPack(cwd, "@contentbit/generic-pack", values["no-install"], io);
|
|
16467
17589
|
}
|
|
16468
17590
|
if (target === "react") {
|
|
16469
17591
|
const depth = layout.componentPath.split("/").length - 1;
|
|
@@ -16483,30 +17605,62 @@ async function initCommand(args, io) {
|
|
|
16483
17605
|
htmlRenderScript(md)
|
|
16484
17606
|
]);
|
|
16485
17607
|
}
|
|
17608
|
+
if (target === "astro") {
|
|
17609
|
+
let astroStyled = false;
|
|
17610
|
+
if (!values["no-styled"] && existsSync2(componentsJsonPath)) {
|
|
17611
|
+
astroStyled = await installStyledPack(cwd, "@contentbit/astro-pack", values["no-install"], io);
|
|
17612
|
+
}
|
|
17613
|
+
files.push(["blocks/QuoteBlock.astro", ASTRO_QUOTE_BLOCK]);
|
|
17614
|
+
const configCandidates = ["ts", "mts", "mjs", "js"].flatMap((ext) => [
|
|
17615
|
+
`src/content.config.${ext}`,
|
|
17616
|
+
`src/content/config.${ext}`
|
|
17617
|
+
]);
|
|
17618
|
+
const existingConfig = configCandidates.find((p) => existsSync2(join2(cwd, p)));
|
|
17619
|
+
if (existingConfig) {
|
|
17620
|
+
io.stdout(`content config exists (${existingConfig}); add this collection manually:`);
|
|
17621
|
+
io.stdout(ASTRO_CONTENT_CONFIG);
|
|
17622
|
+
io.stdout('the example page expects the "articles" collection above');
|
|
17623
|
+
} else {
|
|
17624
|
+
files.push(["src/content.config.ts", ASTRO_CONTENT_CONFIG]);
|
|
17625
|
+
}
|
|
17626
|
+
if (!values["no-page"]) files.push(["src/pages/example.astro", astroPage(astroStyled)]);
|
|
17627
|
+
}
|
|
16486
17628
|
for (const [rel, content] of files) {
|
|
16487
|
-
const result = await scaffold(
|
|
17629
|
+
const result = await scaffold(join2(cwd, rel), content);
|
|
16488
17630
|
io.stdout(`${result}: ${rel}`);
|
|
16489
17631
|
}
|
|
16490
|
-
const fresh = JSON.parse(await
|
|
17632
|
+
const fresh = JSON.parse(await readFile2(pkgPath, "utf8"));
|
|
16491
17633
|
fresh.scripts ??= {};
|
|
16492
17634
|
if (!fresh.scripts["content:check"]) {
|
|
16493
17635
|
fresh.scripts["content:check"] = 'contentbit validate "content/**/*.md" --registry ./blocks/registry.ts';
|
|
16494
|
-
await writeFile(pkgPath, `${JSON.stringify(fresh, null, 2)}
|
|
16495
|
-
`, "utf8");
|
|
16496
17636
|
io.stdout("added script: content:check");
|
|
16497
17637
|
}
|
|
17638
|
+
if (!fresh.scripts["content:links"]) {
|
|
17639
|
+
fresh.scripts["content:links"] = 'contentbit links "content/**/*.md"';
|
|
17640
|
+
io.stdout("added script: content:links");
|
|
17641
|
+
}
|
|
17642
|
+
if (!pkg.scripts?.["content:check"] || !pkg.scripts?.["content:links"]) {
|
|
17643
|
+
await writeFile2(pkgPath, `${JSON.stringify(fresh, null, 2)}
|
|
17644
|
+
`, "utf8");
|
|
17645
|
+
}
|
|
16498
17646
|
let registry2;
|
|
16499
17647
|
try {
|
|
16500
|
-
registry2 = await loadRegistry(
|
|
17648
|
+
registry2 = await loadRegistry(join2(cwd, "blocks/registry.ts"));
|
|
16501
17649
|
} catch {
|
|
16502
17650
|
registry2 = await loadRegistry();
|
|
16503
17651
|
}
|
|
16504
17652
|
const guide = registry2.toAuthoringGuide({ audience: "llm", includeExamples: true });
|
|
16505
|
-
await
|
|
17653
|
+
await writeFile2(join2(cwd, "contentbit-guide.md"), guide, "utf8");
|
|
16506
17654
|
io.stdout("created: contentbit-guide.md (LLM authoring instructions)");
|
|
17655
|
+
if (!values["no-agents"]) {
|
|
17656
|
+
await installAgentIntegration(cwd, {}, io);
|
|
17657
|
+
io.stdout("Agent integration installed \u2014 try asking your agent:");
|
|
17658
|
+
io.stdout(' "write a blog post about X" or "audit my content"');
|
|
17659
|
+
}
|
|
16507
17660
|
io.stdout("");
|
|
16508
17661
|
io.stdout("Done. Next steps:");
|
|
16509
17662
|
io.stdout(` 1. Validate the starter content: ${detectPackageManager(cwd)} run content:check`);
|
|
17663
|
+
io.stdout(` Build the link index: ${detectPackageManager(cwd)} run content:links`);
|
|
16510
17664
|
if (target === "react") {
|
|
16511
17665
|
if (!values["no-page"] && layout.pagePath) {
|
|
16512
17666
|
io.stdout(" 2. Start the dev server and open /example to see the article rendered.");
|
|
@@ -16515,6 +17669,9 @@ async function initCommand(args, io) {
|
|
|
16515
17669
|
io.stdout(" <Content source={...content/example.md as a string} />");
|
|
16516
17670
|
}
|
|
16517
17671
|
io.stdout(" 3. Styled components: pnpm dlx shadcn@latest add @contentbit/generic-pack");
|
|
17672
|
+
} else if (target === "astro") {
|
|
17673
|
+
io.stdout(" 2. Start the dev server and open /example to see the article rendered.");
|
|
17674
|
+
io.stdout(" 3. Styled components: pnpm dlx shadcn@latest add @contentbit/astro-pack");
|
|
16518
17675
|
} else if (target === "html") {
|
|
16519
17676
|
io.stdout(" 2. Render it: node scripts/render-example.mjs && open example.html");
|
|
16520
17677
|
} else {
|
|
@@ -16523,16 +17680,19 @@ async function initCommand(args, io) {
|
|
|
16523
17680
|
io.stdout(" Docs: https://contentbit.dev/docs");
|
|
16524
17681
|
return 0;
|
|
16525
17682
|
}
|
|
16526
|
-
var TARGETS, MD_CHOICES, REGISTRY_TEMPLATE, EXAMPLE_CONTENT, TANSTACK_PAGE, NEXT_PAGE;
|
|
17683
|
+
var TARGETS, MD_CHOICES, REGISTRY_TEMPLATE, EXAMPLE_CONTENT, RELATED_CONTENT, TANSTACK_PAGE, NEXT_PAGE, ASTRO_CONTENT_CONFIG, ASTRO_QUOTE_BLOCK;
|
|
16527
17684
|
var init_init = __esm({
|
|
16528
17685
|
"src/commands/init.ts"() {
|
|
16529
17686
|
"use strict";
|
|
16530
17687
|
init_load_registry();
|
|
16531
|
-
|
|
17688
|
+
init_agents();
|
|
17689
|
+
TARGETS = ["react", "html", "markdown", "astro"];
|
|
16532
17690
|
MD_CHOICES = {
|
|
16533
17691
|
react: ["react-markdown", "none"],
|
|
16534
17692
|
html: ["marked", "markdown-it", "none"],
|
|
16535
|
-
markdown: ["none"]
|
|
17693
|
+
markdown: ["none"],
|
|
17694
|
+
// @contentbit/astro ships its own marked-based default; nothing to install.
|
|
17695
|
+
astro: ["none"]
|
|
16536
17696
|
};
|
|
16537
17697
|
REGISTRY_TEMPLATE = `// Custom block definitions for this project. The CLI and your app share
|
|
16538
17698
|
// this module \u2014 Node 22.18+ imports TypeScript directly:
|
|
@@ -16562,7 +17722,18 @@ export const quote = defineBlock({
|
|
|
16562
17722
|
|
|
16563
17723
|
export default [quote] satisfies BlockDefinition<unknown>[]
|
|
16564
17724
|
`;
|
|
16565
|
-
EXAMPLE_CONTENT =
|
|
17725
|
+
EXAMPLE_CONTENT = `---
|
|
17726
|
+
slug: hello-content-blocks
|
|
17727
|
+
linksTo:
|
|
17728
|
+
- related-contentbit-workflows
|
|
17729
|
+
aliases:
|
|
17730
|
+
- getting-started-contentbit
|
|
17731
|
+
keywords:
|
|
17732
|
+
primary: validated Markdown blocks
|
|
17733
|
+
secondary: [content workflow, agent writing]
|
|
17734
|
+
---
|
|
17735
|
+
|
|
17736
|
+
# Hello, Content Blocks
|
|
16566
17737
|
|
|
16567
17738
|
Regular Markdown works everywhere. Blocks add validated structure:
|
|
16568
17739
|
|
|
@@ -16583,6 +17754,27 @@ by the \`QuoteBlock\` component, in about twenty lines:
|
|
|
16583
17754
|
The Analytical Engine weaves algebraic patterns just as the Jacquard loom
|
|
16584
17755
|
weaves flowers and leaves.
|
|
16585
17756
|
:::
|
|
17757
|
+
`;
|
|
17758
|
+
RELATED_CONTENT = `---
|
|
17759
|
+
slug: related-contentbit-workflows
|
|
17760
|
+
linksTo:
|
|
17761
|
+
- hello-content-blocks
|
|
17762
|
+
keywords:
|
|
17763
|
+
primary: contentbit workflow
|
|
17764
|
+
secondary: [validation loop, internal links]
|
|
17765
|
+
---
|
|
17766
|
+
|
|
17767
|
+
# Related contentbit workflows
|
|
17768
|
+
|
|
17769
|
+
This supporting page exists to show internal links in frontmatter. The link
|
|
17770
|
+
graph is authored once with \`slug\` and \`linksTo\`, then contentbit derives
|
|
17771
|
+
\`linkedFrom\` in \`.contentbit/link-index.json\`.
|
|
17772
|
+
|
|
17773
|
+
:::callout{type="note"}
|
|
17774
|
+
Run \`contentbit links "content/**/*.md" --fix\` after renaming a page. Alias
|
|
17775
|
+
references in \`linksTo\` are rewritten to the current slug, while \`aliases\`
|
|
17776
|
+
stays as the rename record.
|
|
17777
|
+
:::
|
|
16586
17778
|
`;
|
|
16587
17779
|
TANSTACK_PAGE = `import { createFileRoute } from '@tanstack/react-router'
|
|
16588
17780
|
|
|
@@ -16614,6 +17806,66 @@ export default async function ExamplePage() {
|
|
|
16614
17806
|
)
|
|
16615
17807
|
}
|
|
16616
17808
|
`;
|
|
17809
|
+
ASTRO_CONTENT_CONFIG = `import { defineCollection } from 'astro:content'
|
|
17810
|
+
import { glob } from 'astro/loaders'
|
|
17811
|
+
|
|
17812
|
+
export const collections = {
|
|
17813
|
+
articles: defineCollection({
|
|
17814
|
+
// Astro's builtin Markdown loader. Entry bodies are parsed and validated
|
|
17815
|
+
// where they render (see src/pages/example.astro); \`contentbit validate\`
|
|
17816
|
+
// covers the same files in CI.
|
|
17817
|
+
loader: glob({ pattern: '**/*.md', base: './content' }),
|
|
17818
|
+
}),
|
|
17819
|
+
}
|
|
17820
|
+
`;
|
|
17821
|
+
ASTRO_QUOTE_BLOCK = `---
|
|
17822
|
+
// The Astro component for the custom \`quote\` block defined in blocks/registry.ts.
|
|
17823
|
+
// Block props arrive as component props; nested content arrives via <slot />.
|
|
17824
|
+
interface Props {
|
|
17825
|
+
author: string
|
|
17826
|
+
role?: string
|
|
17827
|
+
}
|
|
17828
|
+
|
|
17829
|
+
const { author, role } = Astro.props
|
|
17830
|
+
---
|
|
17831
|
+
|
|
17832
|
+
<figure style="margin: 1.5rem 0; border-left: 2px solid #d4d4d4; padding-left: 1rem;">
|
|
17833
|
+
<blockquote style="font-style: italic;"><slot /></blockquote>
|
|
17834
|
+
<figcaption style="margin-top: 0.5rem; font-size: 0.875rem; opacity: 0.7;">
|
|
17835
|
+
\u2014 {author}{role ? \`, \${role}\` : null}
|
|
17836
|
+
</figcaption>
|
|
17837
|
+
</figure>
|
|
17838
|
+
`;
|
|
17839
|
+
}
|
|
17840
|
+
});
|
|
17841
|
+
|
|
17842
|
+
// src/link-options.ts
|
|
17843
|
+
function linkResolverOptions(values) {
|
|
17844
|
+
const out = {};
|
|
17845
|
+
const resolve = stringValue(values["link-resolve"]);
|
|
17846
|
+
if (resolve) {
|
|
17847
|
+
if (!isResolveMode(resolve)) throw new Error(`invalid --link-resolve ${resolve}`);
|
|
17848
|
+
out.resolve = resolve;
|
|
17849
|
+
}
|
|
17850
|
+
const localeField = stringValue(values["locale-field"]);
|
|
17851
|
+
const slugField = stringValue(values["slug-field"]);
|
|
17852
|
+
const keyField = stringValue(values["key-field"]);
|
|
17853
|
+
const defaultLocale = stringValue(values["default-locale"]);
|
|
17854
|
+
if (localeField) out.localeField = localeField;
|
|
17855
|
+
if (slugField) out.slugField = slugField;
|
|
17856
|
+
if (keyField) out.keyField = keyField;
|
|
17857
|
+
if (defaultLocale) out.defaultLocale = defaultLocale;
|
|
17858
|
+
return out;
|
|
17859
|
+
}
|
|
17860
|
+
function stringValue(value) {
|
|
17861
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
17862
|
+
}
|
|
17863
|
+
function isResolveMode(value) {
|
|
17864
|
+
return value === "global-slug" || value === "same-locale-slug" || value === "same-locale-key" || value === "prefer-same-locale-key-fallback-slug";
|
|
17865
|
+
}
|
|
17866
|
+
var init_link_options = __esm({
|
|
17867
|
+
"src/link-options.ts"() {
|
|
17868
|
+
"use strict";
|
|
16617
17869
|
}
|
|
16618
17870
|
});
|
|
16619
17871
|
|
|
@@ -16622,16 +17874,21 @@ var validate_exports = {};
|
|
|
16622
17874
|
__export(validate_exports, {
|
|
16623
17875
|
validateCommand: () => validateCommand
|
|
16624
17876
|
});
|
|
16625
|
-
import { readFile as
|
|
16626
|
-
import { parseArgs as
|
|
17877
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
17878
|
+
import { parseArgs as parseArgs3 } from "node:util";
|
|
16627
17879
|
import { glob } from "tinyglobby";
|
|
16628
17880
|
async function validateCommand(args, io) {
|
|
16629
|
-
const { values, positionals } =
|
|
17881
|
+
const { values, positionals } = parseArgs3({
|
|
16630
17882
|
args,
|
|
16631
17883
|
allowPositionals: true,
|
|
16632
17884
|
options: {
|
|
16633
17885
|
registry: { type: "string" },
|
|
16634
|
-
"strict-warnings": { type: "boolean", default: false }
|
|
17886
|
+
"strict-warnings": { type: "boolean", default: false },
|
|
17887
|
+
"link-resolve": { type: "string" },
|
|
17888
|
+
"locale-field": { type: "string" },
|
|
17889
|
+
"slug-field": { type: "string" },
|
|
17890
|
+
"key-field": { type: "string" },
|
|
17891
|
+
"default-locale": { type: "string" }
|
|
16635
17892
|
}
|
|
16636
17893
|
});
|
|
16637
17894
|
if (positionals.length === 0) {
|
|
@@ -16644,17 +17901,27 @@ async function validateCommand(args, io) {
|
|
|
16644
17901
|
return 2;
|
|
16645
17902
|
}
|
|
16646
17903
|
const registry2 = await loadRegistry(values.registry);
|
|
17904
|
+
const linkOptions = linkResolverOptions(values);
|
|
16647
17905
|
let errors = 0;
|
|
16648
17906
|
let warnings = 0;
|
|
17907
|
+
const linkInputs = [];
|
|
16649
17908
|
for (const file2 of files.sort()) {
|
|
16650
|
-
const source = await
|
|
16651
|
-
|
|
17909
|
+
const source = await readFile3(file2, "utf8");
|
|
17910
|
+
linkInputs.push({ path: file2, data: extractFrontmatter(source)?.data ?? {} });
|
|
17911
|
+
const result = validateDocument(parseDocument(stripFrontmatter(source)), registry2);
|
|
16652
17912
|
for (const d of result.diagnostics) {
|
|
16653
17913
|
io.stderr(formatDiagnostic(d, file2));
|
|
16654
17914
|
if (d.severity === "error") errors++;
|
|
16655
17915
|
else if (d.severity === "warning") warnings++;
|
|
16656
17916
|
}
|
|
16657
17917
|
}
|
|
17918
|
+
if (linkInputs.some((i) => "slug" in i.data)) {
|
|
17919
|
+
for (const { file: file2, diagnostic } of validateLinks(linkInputs, linkOptions)) {
|
|
17920
|
+
io.stderr(formatDiagnostic(diagnostic, file2));
|
|
17921
|
+
if (diagnostic.severity === "error") errors++;
|
|
17922
|
+
else if (diagnostic.severity === "warning") warnings++;
|
|
17923
|
+
}
|
|
17924
|
+
}
|
|
16658
17925
|
io.stdout(`${files.length} file(s): ${errors} errors, ${warnings} warnings`);
|
|
16659
17926
|
if (errors > 0) return 1;
|
|
16660
17927
|
if (warnings > 0 && values["strict-warnings"]) return 1;
|
|
@@ -16662,6 +17929,64 @@ async function validateCommand(args, io) {
|
|
|
16662
17929
|
}
|
|
16663
17930
|
var init_validate2 = __esm({
|
|
16664
17931
|
"src/commands/validate.ts"() {
|
|
17932
|
+
"use strict";
|
|
17933
|
+
init_dist();
|
|
17934
|
+
init_link_options();
|
|
17935
|
+
init_load_registry();
|
|
17936
|
+
}
|
|
17937
|
+
});
|
|
17938
|
+
|
|
17939
|
+
// src/commands/stats.ts
|
|
17940
|
+
var stats_exports = {};
|
|
17941
|
+
__export(stats_exports, {
|
|
17942
|
+
statsCommand: () => statsCommand
|
|
17943
|
+
});
|
|
17944
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
17945
|
+
import { parseArgs as parseArgs4 } from "node:util";
|
|
17946
|
+
import { glob as glob2 } from "tinyglobby";
|
|
17947
|
+
async function fileStats(file2, registry2) {
|
|
17948
|
+
const source = await readFile4(file2, "utf8");
|
|
17949
|
+
const stats = analyzeDocument(source, { path: file2 });
|
|
17950
|
+
if (registry2) {
|
|
17951
|
+
const result = validateDocument(parseDocument(stripFrontmatter(source)), registry2);
|
|
17952
|
+
let errors = 0;
|
|
17953
|
+
let warnings = 0;
|
|
17954
|
+
for (const d of result.diagnostics) {
|
|
17955
|
+
if (d.severity === "error") errors++;
|
|
17956
|
+
else if (d.severity === "warning") warnings++;
|
|
17957
|
+
}
|
|
17958
|
+
stats.validation = { errors, warnings };
|
|
17959
|
+
}
|
|
17960
|
+
return stats;
|
|
17961
|
+
}
|
|
17962
|
+
async function statsCommand(args, io) {
|
|
17963
|
+
const { values, positionals } = parseArgs4({
|
|
17964
|
+
args,
|
|
17965
|
+
allowPositionals: true,
|
|
17966
|
+
options: {
|
|
17967
|
+
registry: { type: "string" },
|
|
17968
|
+
"no-validate": { type: "boolean", default: false }
|
|
17969
|
+
}
|
|
17970
|
+
});
|
|
17971
|
+
if (positionals.length === 0) {
|
|
17972
|
+
io.stderr("stats: provide at least one file or glob.");
|
|
17973
|
+
return 2;
|
|
17974
|
+
}
|
|
17975
|
+
const files = await glob2(positionals, { absolute: true });
|
|
17976
|
+
if (files.length === 0) {
|
|
17977
|
+
io.stderr(`stats: no files matched ${positionals.join(" ")}`);
|
|
17978
|
+
return 2;
|
|
17979
|
+
}
|
|
17980
|
+
const registry2 = values["no-validate"] ? null : await loadRegistry(values.registry);
|
|
17981
|
+
const all = [];
|
|
17982
|
+
for (const file2 of files.sort()) {
|
|
17983
|
+
all.push(await fileStats(file2, registry2));
|
|
17984
|
+
}
|
|
17985
|
+
io.stdout(JSON.stringify(all.length === 1 ? all[0] : all, null, 2));
|
|
17986
|
+
return 0;
|
|
17987
|
+
}
|
|
17988
|
+
var init_stats = __esm({
|
|
17989
|
+
"src/commands/stats.ts"() {
|
|
16665
17990
|
"use strict";
|
|
16666
17991
|
init_dist();
|
|
16667
17992
|
init_load_registry();
|
|
@@ -16748,12 +18073,18 @@ var init_blocks = __esm({
|
|
|
16748
18073
|
});
|
|
16749
18074
|
|
|
16750
18075
|
// ../html/dist/render.js
|
|
16751
|
-
function
|
|
18076
|
+
function fallbackMarkdown(md) {
|
|
16752
18077
|
return md.trim().split(/\n{2,}/).map((p) => `<p>${escapeHtml(p)}</p>`).join("\n");
|
|
16753
18078
|
}
|
|
18079
|
+
function invalidBlockHtml(node, prefix) {
|
|
18080
|
+
return `<div class="${prefix}invalid" data-cb-invalid="${escapeHtml(node.name)}"><pre>${escapeHtml(node.body)}</pre></div>`;
|
|
18081
|
+
}
|
|
18082
|
+
function unrenderableBlockError(name) {
|
|
18083
|
+
return new Error(`Cannot render block "${name}": not validated or no renderer registered.`);
|
|
18084
|
+
}
|
|
16754
18085
|
function renderToHtml(document, opts = {}) {
|
|
16755
18086
|
const prefix = opts.classPrefix ?? "cb-";
|
|
16756
|
-
const renderMarkdown = opts.renderMarkdown ??
|
|
18087
|
+
const renderMarkdown = opts.renderMarkdown ?? fallbackMarkdown;
|
|
16757
18088
|
const renderers = { ...genericHtmlRenderers, ...opts.renderers };
|
|
16758
18089
|
const onInvalid = opts.onInvalid ?? "fallback";
|
|
16759
18090
|
const ctx = {
|
|
@@ -16767,13 +18098,11 @@ function renderToHtml(document, opts = {}) {
|
|
|
16767
18098
|
const renderer = renderers[node.name];
|
|
16768
18099
|
if (renderer && isValidatedBlock(node))
|
|
16769
18100
|
return renderer(node, ctx);
|
|
16770
|
-
if (onInvalid === "strict")
|
|
16771
|
-
throw
|
|
16772
|
-
|
|
16773
|
-
|
|
16774
|
-
|
|
16775
|
-
}
|
|
16776
|
-
return defaultMarkdown(node.body);
|
|
18101
|
+
if (onInvalid === "strict")
|
|
18102
|
+
throw unrenderableBlockError(node.name);
|
|
18103
|
+
if (onInvalid === "annotated")
|
|
18104
|
+
return invalidBlockHtml(node, prefix);
|
|
18105
|
+
return fallbackMarkdown(node.body);
|
|
16777
18106
|
}).join("\n");
|
|
16778
18107
|
}
|
|
16779
18108
|
};
|
|
@@ -16801,10 +18130,10 @@ var render_exports = {};
|
|
|
16801
18130
|
__export(render_exports, {
|
|
16802
18131
|
renderCommand: () => renderCommand
|
|
16803
18132
|
});
|
|
16804
|
-
import { readFile as
|
|
16805
|
-
import { parseArgs as
|
|
18133
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
18134
|
+
import { parseArgs as parseArgs5 } from "node:util";
|
|
16806
18135
|
async function renderCommand(args, io) {
|
|
16807
|
-
const { values, positionals } =
|
|
18136
|
+
const { values, positionals } = parseArgs5({
|
|
16808
18137
|
args,
|
|
16809
18138
|
allowPositionals: true,
|
|
16810
18139
|
options: {
|
|
@@ -16819,8 +18148,8 @@ async function renderCommand(args, io) {
|
|
|
16819
18148
|
return 2;
|
|
16820
18149
|
}
|
|
16821
18150
|
const registry2 = await loadRegistry(values.registry);
|
|
16822
|
-
const source = await
|
|
16823
|
-
const result = validateDocument(parseDocument(source), registry2);
|
|
18151
|
+
const source = await readFile5(file2, "utf8");
|
|
18152
|
+
const result = validateDocument(parseDocument(stripFrontmatter(source)), registry2);
|
|
16824
18153
|
if (!result.ok) {
|
|
16825
18154
|
for (const d of result.diagnostics) io.stderr(formatDiagnostic(d, file2));
|
|
16826
18155
|
return 1;
|
|
@@ -16845,9 +18174,9 @@ var instructions_exports = {};
|
|
|
16845
18174
|
__export(instructions_exports, {
|
|
16846
18175
|
instructionsCommand: () => instructionsCommand
|
|
16847
18176
|
});
|
|
16848
|
-
import { parseArgs as
|
|
18177
|
+
import { parseArgs as parseArgs6 } from "node:util";
|
|
16849
18178
|
async function instructionsCommand(args, io) {
|
|
16850
|
-
const { values } =
|
|
18179
|
+
const { values } = parseArgs6({
|
|
16851
18180
|
args,
|
|
16852
18181
|
options: {
|
|
16853
18182
|
audience: { type: "string", default: "llm" },
|
|
@@ -16877,9 +18206,9 @@ var docs_exports = {};
|
|
|
16877
18206
|
__export(docs_exports, {
|
|
16878
18207
|
docsCommand: () => docsCommand
|
|
16879
18208
|
});
|
|
16880
|
-
import { parseArgs as
|
|
18209
|
+
import { parseArgs as parseArgs7 } from "node:util";
|
|
16881
18210
|
async function docsCommand(args, io) {
|
|
16882
|
-
const { values } =
|
|
18211
|
+
const { values } = parseArgs7({
|
|
16883
18212
|
args,
|
|
16884
18213
|
options: {
|
|
16885
18214
|
registry: { type: "string" },
|
|
@@ -16899,21 +18228,142 @@ var init_docs = __esm({
|
|
|
16899
18228
|
}
|
|
16900
18229
|
});
|
|
16901
18230
|
|
|
18231
|
+
// src/links-io.ts
|
|
18232
|
+
import { readFile as readFile6 } from "node:fs/promises";
|
|
18233
|
+
async function collectLinkInputs(files) {
|
|
18234
|
+
const inputs = [];
|
|
18235
|
+
for (const path of files) {
|
|
18236
|
+
const source = await readFile6(path, "utf8");
|
|
18237
|
+
const fm = extractFrontmatter(source);
|
|
18238
|
+
inputs.push({ path, data: fm?.data ?? {} });
|
|
18239
|
+
}
|
|
18240
|
+
return inputs;
|
|
18241
|
+
}
|
|
18242
|
+
var init_links_io = __esm({
|
|
18243
|
+
"src/links-io.ts"() {
|
|
18244
|
+
"use strict";
|
|
18245
|
+
init_dist();
|
|
18246
|
+
}
|
|
18247
|
+
});
|
|
18248
|
+
|
|
18249
|
+
// src/commands/links.ts
|
|
18250
|
+
var links_exports = {};
|
|
18251
|
+
__export(links_exports, {
|
|
18252
|
+
linksCommand: () => linksCommand
|
|
18253
|
+
});
|
|
18254
|
+
import { mkdir as mkdir3, readFile as readFile7 } from "node:fs/promises";
|
|
18255
|
+
import { dirname, join as join3 } from "node:path";
|
|
18256
|
+
import { parseArgs as parseArgs8 } from "node:util";
|
|
18257
|
+
import { glob as glob3 } from "tinyglobby";
|
|
18258
|
+
async function linksCommand(args, io) {
|
|
18259
|
+
const { values, positionals } = parseArgs8({
|
|
18260
|
+
args,
|
|
18261
|
+
allowPositionals: true,
|
|
18262
|
+
options: {
|
|
18263
|
+
out: { type: "string" },
|
|
18264
|
+
fix: { type: "boolean", default: false },
|
|
18265
|
+
"link-resolve": { type: "string" },
|
|
18266
|
+
"locale-field": { type: "string" },
|
|
18267
|
+
"slug-field": { type: "string" },
|
|
18268
|
+
"key-field": { type: "string" },
|
|
18269
|
+
"default-locale": { type: "string" }
|
|
18270
|
+
}
|
|
18271
|
+
});
|
|
18272
|
+
if (positionals.length === 0) {
|
|
18273
|
+
io.stderr("links: provide at least one file or glob.");
|
|
18274
|
+
return 2;
|
|
18275
|
+
}
|
|
18276
|
+
const files = (await glob3(positionals, { absolute: true })).sort();
|
|
18277
|
+
if (files.length === 0) {
|
|
18278
|
+
io.stderr(`links: no files matched ${positionals.join(" ")}`);
|
|
18279
|
+
return 2;
|
|
18280
|
+
}
|
|
18281
|
+
const inputs = await collectLinkInputs(files);
|
|
18282
|
+
const linkOptions = linkResolverOptions(values);
|
|
18283
|
+
let errors = 0;
|
|
18284
|
+
let warnings = 0;
|
|
18285
|
+
for (const { file: file2, diagnostic } of validateLinks(inputs, linkOptions)) {
|
|
18286
|
+
io.stderr(formatDiagnostic(diagnostic, file2));
|
|
18287
|
+
if (diagnostic.severity === "error") errors++;
|
|
18288
|
+
else if (diagnostic.severity === "warning") warnings++;
|
|
18289
|
+
}
|
|
18290
|
+
const index = buildLinkIndex(inputs, linkOptions);
|
|
18291
|
+
if (values.fix && errors > 0) {
|
|
18292
|
+
io.stderr("links: --fix skipped because link errors must be resolved first.");
|
|
18293
|
+
} else if (values.fix && index.aliases.size > 0) {
|
|
18294
|
+
for (const file2 of files) {
|
|
18295
|
+
const source = await readFile7(file2, "utf8");
|
|
18296
|
+
const fm = extractFrontmatter(source);
|
|
18297
|
+
if (!fm) continue;
|
|
18298
|
+
const lines = source.split("\n");
|
|
18299
|
+
let changed = false;
|
|
18300
|
+
let inLinksTo = false;
|
|
18301
|
+
for (let i = 0; i < fm.lines.end && i < lines.length; i++) {
|
|
18302
|
+
const line = lines[i];
|
|
18303
|
+
const topKey = line.match(/^([A-Za-z0-9_.-]+):(.*)$/);
|
|
18304
|
+
if (topKey) inLinksTo = topKey[1] === "linksTo";
|
|
18305
|
+
if (!inLinksTo) continue;
|
|
18306
|
+
let next = line;
|
|
18307
|
+
for (const [alias, current] of aliasReplacementsForPage(index, fm.data)) {
|
|
18308
|
+
const re = new RegExp(`(^|[\\s\\[,'"-])${escapeRe(alias)}($|[\\s\\],'"])`, "g");
|
|
18309
|
+
next = next.replace(re, (_m, p1, p2) => `${p1}${current}${p2}`);
|
|
18310
|
+
}
|
|
18311
|
+
if (next !== line) {
|
|
18312
|
+
lines[i] = next;
|
|
18313
|
+
changed = true;
|
|
18314
|
+
}
|
|
18315
|
+
}
|
|
18316
|
+
if (changed) {
|
|
18317
|
+
await io.writeFile(file2, lines.join("\n"));
|
|
18318
|
+
io.stdout(`fixed alias references in ${file2}`);
|
|
18319
|
+
}
|
|
18320
|
+
}
|
|
18321
|
+
}
|
|
18322
|
+
const outPath = values.out ?? join3(process.cwd(), ".contentbit", "link-index.json");
|
|
18323
|
+
await mkdir3(dirname(outPath), { recursive: true });
|
|
18324
|
+
await io.writeFile(outPath, JSON.stringify(serializeLinkIndex(index), null, 2) + "\n");
|
|
18325
|
+
let edges = 0;
|
|
18326
|
+
for (const p of index.pages.values()) edges += p.linksTo.length;
|
|
18327
|
+
const orphans = [...index.pages.values()].filter((p) => p.linkedFrom.length === 0).length;
|
|
18328
|
+
io.stdout(
|
|
18329
|
+
`${index.pages.size} page(s), ${edges} link(s), ${orphans} orphan(s): ${errors} errors, ${warnings} warnings`
|
|
18330
|
+
);
|
|
18331
|
+
io.stdout(`index written to ${outPath}`);
|
|
18332
|
+
return errors > 0 ? 1 : 0;
|
|
18333
|
+
}
|
|
18334
|
+
function escapeRe(s) {
|
|
18335
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
18336
|
+
}
|
|
18337
|
+
var init_links2 = __esm({
|
|
18338
|
+
"src/commands/links.ts"() {
|
|
18339
|
+
"use strict";
|
|
18340
|
+
init_dist();
|
|
18341
|
+
init_link_options();
|
|
18342
|
+
init_links_io();
|
|
18343
|
+
}
|
|
18344
|
+
});
|
|
18345
|
+
|
|
16902
18346
|
// src/run.ts
|
|
16903
|
-
var USAGE = `Usage: contentbit <init|validate|render|instructions|docs> [options]
|
|
18347
|
+
var USAGE = `Usage: contentbit <init|validate|stats|render|instructions|docs|agents|links> [options]
|
|
16904
18348
|
|
|
16905
|
-
init [-t react|html|markdown] [--md ...] [-y] [--no-install] [--no-page]
|
|
18349
|
+
init [-t react|html|markdown|astro] [--md ...] [-y] [--no-install] [--no-page] [--no-agents]
|
|
18350
|
+
agents [--claude] [--no-agents-md]
|
|
16906
18351
|
|
|
16907
|
-
validate <globs...> [--registry <module.mjs>] [--strict-warnings]
|
|
18352
|
+
validate <globs...> [--registry <module.mjs>] [--strict-warnings] [--link-resolve <mode>]
|
|
18353
|
+
stats <globs...> [--registry <module.mjs>] [--no-validate]
|
|
16908
18354
|
render <file> --target html|markdown [--registry <module.mjs>] [--out <file>]
|
|
16909
18355
|
instructions [--audience llm|human] [--no-examples] [--registry <module.mjs>] [--out <file>]
|
|
16910
|
-
docs [--registry <module.mjs>] [--out <file>]
|
|
18356
|
+
docs [--registry <module.mjs>] [--out <file>]
|
|
18357
|
+
links <globs...> [--fix] [--out <file>] [--link-resolve <mode>]`;
|
|
16911
18358
|
var commands = {
|
|
16912
18359
|
init: async () => (await Promise.resolve().then(() => (init_init(), init_exports))).initCommand,
|
|
16913
18360
|
validate: async () => (await Promise.resolve().then(() => (init_validate2(), validate_exports))).validateCommand,
|
|
18361
|
+
stats: async () => (await Promise.resolve().then(() => (init_stats(), stats_exports))).statsCommand,
|
|
16914
18362
|
render: async () => (await Promise.resolve().then(() => (init_render2(), render_exports))).renderCommand,
|
|
16915
18363
|
instructions: async () => (await Promise.resolve().then(() => (init_instructions(), instructions_exports))).instructionsCommand,
|
|
16916
|
-
docs: async () => (await Promise.resolve().then(() => (init_docs(), docs_exports))).docsCommand
|
|
18364
|
+
docs: async () => (await Promise.resolve().then(() => (init_docs(), docs_exports))).docsCommand,
|
|
18365
|
+
agents: async () => (await Promise.resolve().then(() => (init_agents(), agents_exports))).agentsCommand,
|
|
18366
|
+
links: async () => (await Promise.resolve().then(() => (init_links2(), links_exports))).linksCommand
|
|
16917
18367
|
};
|
|
16918
18368
|
async function run(argv, io) {
|
|
16919
18369
|
const [name, ...rest] = argv;
|