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