canopycms 0.0.26 → 0.0.28

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.
Files changed (113) hide show
  1. package/dist/ai/generate.d.ts +3 -0
  2. package/dist/ai/generate.d.ts.map +1 -1
  3. package/dist/ai/generate.js +19 -7
  4. package/dist/ai/generate.js.map +1 -1
  5. package/dist/ai/handler.d.ts.map +1 -1
  6. package/dist/ai/handler.js +1 -0
  7. package/dist/ai/handler.js.map +1 -1
  8. package/dist/ai/json-to-markdown.js +1 -1
  9. package/dist/ai/json-to-markdown.js.map +1 -1
  10. package/dist/api/content.d.ts +10 -4
  11. package/dist/api/content.d.ts.map +1 -1
  12. package/dist/api/content.js +24 -12
  13. package/dist/api/content.js.map +1 -1
  14. package/dist/api/schema.d.ts +42 -42
  15. package/dist/build/generate-ai-content.d.ts.map +1 -1
  16. package/dist/build/generate-ai-content.js +1 -0
  17. package/dist/build/generate-ai-content.js.map +1 -1
  18. package/dist/cli/generate-ai-content.js +313 -157
  19. package/dist/cli/init.js +3 -2
  20. package/dist/client.d.ts +2 -0
  21. package/dist/client.d.ts.map +1 -1
  22. package/dist/client.js +1 -0
  23. package/dist/client.js.map +1 -1
  24. package/dist/config/helpers.d.ts.map +1 -1
  25. package/dist/config/helpers.js +2 -1
  26. package/dist/config/helpers.js.map +1 -1
  27. package/dist/config/schemas/collection.d.ts +14 -14
  28. package/dist/config/schemas/collection.js +1 -1
  29. package/dist/config/schemas/collection.js.map +1 -1
  30. package/dist/config/schemas/config.d.ts +17 -13
  31. package/dist/config/schemas/config.d.ts.map +1 -1
  32. package/dist/config/schemas/config.js +1 -0
  33. package/dist/config/schemas/config.js.map +1 -1
  34. package/dist/config/types.d.ts +7 -2
  35. package/dist/config/types.d.ts.map +1 -1
  36. package/dist/config/types.js.map +1 -1
  37. package/dist/content-listing.d.ts +2 -2
  38. package/dist/content-listing.d.ts.map +1 -1
  39. package/dist/content-listing.js +6 -2
  40. package/dist/content-listing.js.map +1 -1
  41. package/dist/content-reader.d.ts +2 -0
  42. package/dist/content-reader.d.ts.map +1 -1
  43. package/dist/content-reader.js +13 -4
  44. package/dist/content-reader.js.map +1 -1
  45. package/dist/content-store.d.ts +8 -1
  46. package/dist/content-store.d.ts.map +1 -1
  47. package/dist/content-store.js +47 -39
  48. package/dist/content-store.js.map +1 -1
  49. package/dist/editor/CanopyEditor.d.ts.map +1 -1
  50. package/dist/editor/CanopyEditor.js +1 -1
  51. package/dist/editor/CanopyEditor.js.map +1 -1
  52. package/dist/editor/Editor.d.ts +1 -0
  53. package/dist/editor/Editor.d.ts.map +1 -1
  54. package/dist/editor/Editor.js +32 -9
  55. package/dist/editor/Editor.js.map +1 -1
  56. package/dist/editor/components/EntryCreateModal.d.ts +1 -1
  57. package/dist/editor/components/EntryCreateModal.d.ts.map +1 -1
  58. package/dist/editor/editor-utils.d.ts.map +1 -1
  59. package/dist/editor/editor-utils.js +4 -3
  60. package/dist/editor/editor-utils.js.map +1 -1
  61. package/dist/editor/fields/MarkdownField.d.ts.map +1 -1
  62. package/dist/editor/fields/MarkdownField.js +8 -2
  63. package/dist/editor/fields/MarkdownField.js.map +1 -1
  64. package/dist/editor/fields/entry-link/EntryLinkContext.d.ts +20 -0
  65. package/dist/editor/fields/entry-link/EntryLinkContext.d.ts.map +1 -0
  66. package/dist/editor/fields/entry-link/EntryLinkContext.js +12 -0
  67. package/dist/editor/fields/entry-link/EntryLinkContext.js.map +1 -0
  68. package/dist/editor/fields/entry-link/InsertEntryLink.d.ts +16 -0
  69. package/dist/editor/fields/entry-link/InsertEntryLink.d.ts.map +1 -0
  70. package/dist/editor/fields/entry-link/InsertEntryLink.js +62 -0
  71. package/dist/editor/fields/entry-link/InsertEntryLink.js.map +1 -0
  72. package/dist/editor/fields/entry-link/index.d.ts +3 -0
  73. package/dist/editor/fields/entry-link/index.d.ts.map +1 -0
  74. package/dist/editor/fields/entry-link/index.js +3 -0
  75. package/dist/editor/fields/entry-link/index.js.map +1 -0
  76. package/dist/editor/hooks/useEntryLinkResolution.d.ts +26 -0
  77. package/dist/editor/hooks/useEntryLinkResolution.d.ts.map +1 -0
  78. package/dist/editor/hooks/useEntryLinkResolution.js +96 -0
  79. package/dist/editor/hooks/useEntryLinkResolution.js.map +1 -0
  80. package/dist/editor/hooks/useEntryManager.d.ts.map +1 -1
  81. package/dist/editor/hooks/useEntryManager.js +4 -1
  82. package/dist/editor/hooks/useEntryManager.js.map +1 -1
  83. package/dist/editor/schema-editor/EntryTypeEditor.d.ts.map +1 -1
  84. package/dist/editor/schema-editor/EntryTypeEditor.js +2 -1
  85. package/dist/editor/schema-editor/EntryTypeEditor.js.map +1 -1
  86. package/dist/entry-link-resolver.d.ts +67 -0
  87. package/dist/entry-link-resolver.d.ts.map +1 -0
  88. package/dist/entry-link-resolver.js +226 -0
  89. package/dist/entry-link-resolver.js.map +1 -0
  90. package/dist/schema/meta-loader.d.ts +1 -1
  91. package/dist/schema/meta-loader.d.ts.map +1 -1
  92. package/dist/schema/meta-loader.js +1 -1
  93. package/dist/schema/meta-loader.js.map +1 -1
  94. package/dist/schema/schema-store.d.ts +21 -21
  95. package/dist/schema/schema-store.js +2 -2
  96. package/dist/schema/schema-store.js.map +1 -1
  97. package/dist/server.d.ts +3 -0
  98. package/dist/server.d.ts.map +1 -1
  99. package/dist/server.js +2 -0
  100. package/dist/server.js.map +1 -1
  101. package/dist/utils/entry-url.d.ts +21 -0
  102. package/dist/utils/entry-url.d.ts.map +1 -0
  103. package/dist/utils/entry-url.js +41 -0
  104. package/dist/utils/entry-url.js.map +1 -0
  105. package/dist/utils/format.d.ts +6 -2
  106. package/dist/utils/format.d.ts.map +1 -1
  107. package/dist/utils/format.js +10 -2
  108. package/dist/utils/format.js.map +1 -1
  109. package/dist/validation/entry-link-validator.d.ts +27 -0
  110. package/dist/validation/entry-link-validator.d.ts.map +1 -0
  111. package/dist/validation/entry-link-validator.js +49 -0
  112. package/dist/validation/entry-link-validator.js.map +1 -0
  113. package/package.json +3 -2
@@ -23,6 +23,15 @@ function createLogicalPath(...segments) {
23
23
  }
24
24
  return normalized;
25
25
  }
26
+ function trimSlashes(path13) {
27
+ let start = 0;
28
+ let end = path13.length;
29
+ while (start < end && path13[start] === "/")
30
+ start++;
31
+ while (end > start && path13[end - 1] === "/")
32
+ end--;
33
+ return path13.slice(start, end);
34
+ }
26
35
  var init_normalize = __esm({
27
36
  "dist/paths/normalize.js"() {
28
37
  "use strict";
@@ -237,7 +246,7 @@ var init_collection = __esm({
237
246
  }).transform((val) => val.split(/[\\/]+/).filter(Boolean).join("/"));
238
247
  entryTypeSchema = z2.object({
239
248
  name: z2.string().min(1),
240
- format: z2.enum(["md", "mdx", "json"]),
249
+ format: z2.enum(["md", "mdx", "json", "yaml"]),
241
250
  schema: z2.array(z2.lazy(() => fieldSchema)).min(1),
242
251
  label: z2.string().optional(),
243
252
  description: z2.string().optional(),
@@ -348,7 +357,8 @@ var init_config = __esm({
348
357
  contentRoot: contentRootSchema.default("content"),
349
358
  sourceRoot: sourceRootSchema.optional(),
350
359
  editor: editorConfigSchema.optional(),
351
- authPlugin: z4.custom().optional()
360
+ authPlugin: z4.custom().optional(),
361
+ entryLinkUrl: z4.custom().optional()
352
362
  });
353
363
  DEFAULT_PROD_WORKSPACE = "/mnt/efs/workspace";
354
364
  }
@@ -691,6 +701,7 @@ import path11 from "node:path";
691
701
  import fs4 from "node:fs/promises";
692
702
  import path5 from "node:path";
693
703
  import matter from "gray-matter";
704
+ import { parse as yamlParse, stringify as yamlStringify } from "yaml";
694
705
 
695
706
  // dist/utils/atomic-write.js
696
707
  import fs from "node:fs/promises";
@@ -1046,8 +1057,12 @@ var getFormatExtension = (format) => {
1046
1057
  return ".md";
1047
1058
  if (format === "mdx")
1048
1059
  return ".mdx";
1060
+ if (format === "yaml")
1061
+ return ".yaml";
1049
1062
  return ".json";
1050
1063
  };
1064
+ var isDataOnlyFormat = (format) => format === "json" || format === "yaml";
1065
+ var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
1051
1066
 
1052
1067
  // dist/paths/index.js
1053
1068
  init_normalize();
@@ -1288,7 +1303,7 @@ var ContentStore = class {
1288
1303
  fields = defaultEntry?.schema || [];
1289
1304
  }
1290
1305
  if (format === "json") {
1291
- const data = JSON.parse(raw);
1306
+ const data = asRecord(JSON.parse(raw));
1292
1307
  doc = {
1293
1308
  collection: schemaItem.logicalPath,
1294
1309
  collectionName: schemaItem.name,
@@ -1297,6 +1312,16 @@ var ContentStore = class {
1297
1312
  relativePath,
1298
1313
  absolutePath
1299
1314
  };
1315
+ } else if (format === "yaml") {
1316
+ const data = asRecord(yamlParse(raw));
1317
+ doc = {
1318
+ collection: schemaItem.logicalPath,
1319
+ collectionName: schemaItem.name,
1320
+ format: "yaml",
1321
+ data,
1322
+ relativePath,
1323
+ absolutePath
1324
+ };
1300
1325
  } else {
1301
1326
  const parsed = matter(raw);
1302
1327
  doc = {
@@ -1343,25 +1368,28 @@ var ContentStore = class {
1343
1368
  entryTypeName
1344
1369
  });
1345
1370
  await fs4.mkdir(path5.dirname(absolutePath), { recursive: true });
1371
+ const updateIdIndex = () => {
1372
+ if (!id)
1373
+ return;
1374
+ const existing = idIndex.findById(id);
1375
+ if (existing) {
1376
+ if (existing.relativePath !== relativePath) {
1377
+ idIndex.updatePath(existing.id, relativePath);
1378
+ }
1379
+ } else {
1380
+ idIndex.add({
1381
+ type: "entry",
1382
+ relativePath,
1383
+ collection: collectionPath,
1384
+ slug: slug || void 0
1385
+ });
1386
+ }
1387
+ };
1346
1388
  if (input.format === "json") {
1347
1389
  const json = JSON.stringify(input.data ?? {}, null, 2);
1348
1390
  await atomicWriteFile(absolutePath, `${json}
1349
1391
  `);
1350
- if (id) {
1351
- const existing = idIndex.findById(id);
1352
- if (existing) {
1353
- if (existing.relativePath !== relativePath) {
1354
- idIndex.updatePath(existing.id, relativePath);
1355
- }
1356
- } else {
1357
- idIndex.add({
1358
- type: "entry",
1359
- relativePath,
1360
- collection: collectionPath,
1361
- slug: slug || void 0
1362
- });
1363
- }
1364
- }
1392
+ updateIdIndex();
1365
1393
  return {
1366
1394
  collection: schemaItem.logicalPath,
1367
1395
  collectionName: schemaItem.name,
@@ -1371,23 +1399,22 @@ var ContentStore = class {
1371
1399
  absolutePath
1372
1400
  };
1373
1401
  }
1402
+ if (input.format === "yaml") {
1403
+ const yaml = yamlStringify(input.data ?? {});
1404
+ await atomicWriteFile(absolutePath, yaml);
1405
+ updateIdIndex();
1406
+ return {
1407
+ collection: schemaItem.logicalPath,
1408
+ collectionName: schemaItem.name,
1409
+ format: "yaml",
1410
+ data: input.data ?? {},
1411
+ relativePath,
1412
+ absolutePath
1413
+ };
1414
+ }
1374
1415
  const file = matter.stringify(input.body, input.data ?? {});
1375
1416
  await atomicWriteFile(absolutePath, file);
1376
- if (id) {
1377
- const existing = idIndex.findById(id);
1378
- if (existing) {
1379
- if (existing.relativePath !== relativePath) {
1380
- idIndex.updatePath(existing.id, relativePath);
1381
- }
1382
- } else {
1383
- idIndex.add({
1384
- type: "entry",
1385
- relativePath,
1386
- collection: collectionPath,
1387
- slug: slug || void 0
1388
- });
1389
- }
1390
- }
1417
+ updateIdIndex();
1391
1418
  return {
1392
1419
  collection: schemaItem.logicalPath,
1393
1420
  collectionName: schemaItem.name,
@@ -1613,7 +1640,7 @@ import { z as z6 } from "zod";
1613
1640
  import chokidar from "chokidar";
1614
1641
  var entryTypeMetaSchema = z6.object({
1615
1642
  name: z6.string().min(1),
1616
- format: z6.enum(["md", "mdx", "json"]),
1643
+ format: z6.enum(["md", "mdx", "json", "yaml"]),
1617
1644
  schema: z6.string().min(1),
1618
1645
  // Entry schema registry key (validated at resolution time)
1619
1646
  label: z6.string().optional(),
@@ -2366,10 +2393,223 @@ function yamlValue(value) {
2366
2393
  return value;
2367
2394
  }
2368
2395
 
2396
+ // dist/utils/debug.js
2397
+ var LOG_LEVELS = {
2398
+ DEBUG: 0,
2399
+ INFO: 1,
2400
+ WARN: 2,
2401
+ ERROR: 3
2402
+ };
2403
+ var DebugLogger = class {
2404
+ constructor(options = {}) {
2405
+ this.timers = /* @__PURE__ */ new Map();
2406
+ this.options = options;
2407
+ }
2408
+ shouldLog(level) {
2409
+ const enabled = this.options.enabled ?? process.env.CANOPYCMS_DEBUG === "true";
2410
+ if (!enabled)
2411
+ return false;
2412
+ const minLevel = this.options.minLevel ?? "DEBUG";
2413
+ return LOG_LEVELS[level] >= LOG_LEVELS[minLevel];
2414
+ }
2415
+ formatMessage(level, category, message) {
2416
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2417
+ const prefix = this.options.prefix ?? "CanopyCMS";
2418
+ return `[${timestamp}] [${prefix}:${category}] [${level}] ${message}`;
2419
+ }
2420
+ debug(category, message, data) {
2421
+ if (this.shouldLog("DEBUG")) {
2422
+ console.log(this.formatMessage("DEBUG", category, message), data ?? "");
2423
+ }
2424
+ }
2425
+ info(category, message, data) {
2426
+ if (this.shouldLog("INFO")) {
2427
+ console.log(this.formatMessage("INFO", category, message), data ?? "");
2428
+ }
2429
+ }
2430
+ warn(category, message, data) {
2431
+ if (this.shouldLog("WARN")) {
2432
+ console.warn(this.formatMessage("WARN", category, message), data ?? "");
2433
+ }
2434
+ }
2435
+ error(category, message, data) {
2436
+ const msg = this.formatMessage("ERROR", category, message);
2437
+ if (this.shouldLog("ERROR")) {
2438
+ console.error(msg, data ?? "");
2439
+ }
2440
+ const throwOnError = this.options.throwOnError ?? false;
2441
+ if (throwOnError) {
2442
+ const errorMsg = data ? `${message}: ${JSON.stringify(data)}` : message;
2443
+ throw new Error(errorMsg);
2444
+ }
2445
+ }
2446
+ /**
2447
+ * Start timing an operation
2448
+ */
2449
+ time(label) {
2450
+ this.timers.set(label, Date.now());
2451
+ }
2452
+ /**
2453
+ * End timing an operation and log the duration
2454
+ */
2455
+ timeEnd(category, label) {
2456
+ const start = this.timers.get(label);
2457
+ if (start === void 0) {
2458
+ this.warn(category, `Timer '${label}' does not exist`);
2459
+ return;
2460
+ }
2461
+ const duration = Date.now() - start;
2462
+ this.timers.delete(label);
2463
+ this.debug(category, `${label} completed`, { durationMs: duration });
2464
+ return duration;
2465
+ }
2466
+ /**
2467
+ * Wrap an async function with automatic timing
2468
+ */
2469
+ async timed(category, label, fn) {
2470
+ this.time(label);
2471
+ try {
2472
+ return await fn();
2473
+ } finally {
2474
+ this.timeEnd(category, label);
2475
+ }
2476
+ }
2477
+ };
2478
+ function createDebugLogger(options) {
2479
+ return new DebugLogger(options);
2480
+ }
2481
+ var testLogger = createDebugLogger({
2482
+ enabled: process.env.E2E_DEBUG === "true",
2483
+ prefix: "E2E",
2484
+ throwOnError: false
2485
+ });
2486
+
2487
+ // dist/utils/entry-url.js
2488
+ init_normalize();
2489
+ function computeEntryUrl(collection, slug, contentRoot) {
2490
+ const root = trimSlashes(contentRoot);
2491
+ let stripped = collection;
2492
+ if (root && collection.startsWith(`${root}/`)) {
2493
+ stripped = collection.slice(root.length + 1);
2494
+ } else if (collection === root) {
2495
+ stripped = "";
2496
+ }
2497
+ const segments = stripped.split("/").filter(Boolean);
2498
+ if (slug && slug !== "index") {
2499
+ segments.push(slug);
2500
+ }
2501
+ const path13 = segments.length > 0 ? `/${segments.join("/")}` : "/";
2502
+ return path13.toLowerCase();
2503
+ }
2504
+
2505
+ // dist/entry-link-resolver.js
2506
+ var log = createDebugLogger({ prefix: "EntryLinks" });
2507
+ var BASE58_CHAR = "[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]";
2508
+ var ENTRY_LINK_PATTERN = new RegExp(`entry:(${BASE58_CHAR}{12})(#[^\\s)>"']*)?`, "g");
2509
+ function resolveEntryUrl(location, contentRoot) {
2510
+ return computeEntryUrl(location.collection ?? "", location.slug ?? "", contentRoot);
2511
+ }
2512
+ function resolveEntryLinksInText(text, idIndex, contentRoot, customResolver) {
2513
+ const parts = splitByCodeRegions(text);
2514
+ return parts.map((part) => {
2515
+ if (part.isCode)
2516
+ return part.text;
2517
+ return part.text.replace(ENTRY_LINK_PATTERN, (_match, id, anchor) => {
2518
+ const location = idIndex.findById(id);
2519
+ if (!location || location.type !== "entry" || !location.collection || !location.slug) {
2520
+ log.warn("resolve", `Entry link target not found: entry:${id}`);
2521
+ return anchor ?? "#";
2522
+ }
2523
+ let url;
2524
+ if (customResolver) {
2525
+ url = customResolver({
2526
+ collection: location.collection,
2527
+ slug: location.slug,
2528
+ id
2529
+ });
2530
+ } else {
2531
+ url = resolveEntryUrl(location, contentRoot);
2532
+ }
2533
+ return `${url}${anchor ?? ""}`;
2534
+ });
2535
+ }).join("");
2536
+ }
2537
+ function splitByCodeRegions(text) {
2538
+ const parts = [];
2539
+ let current = "";
2540
+ let i = 0;
2541
+ while (i < text.length) {
2542
+ if ((text[i] === "`" || text[i] === "~") && i + 2 < text.length && text[i + 1] === text[i] && text[i + 2] === text[i]) {
2543
+ const fence = text[i];
2544
+ let fenceLen = 0;
2545
+ while (i + fenceLen < text.length && text[i + fenceLen] === fence)
2546
+ fenceLen++;
2547
+ const lineEnd = text.indexOf("\n", i + fenceLen);
2548
+ if (lineEnd === -1) {
2549
+ if (current)
2550
+ parts.push({ text: current, isCode: false });
2551
+ parts.push({ text: text.slice(i), isCode: true });
2552
+ return parts;
2553
+ }
2554
+ const closingPattern = fence.repeat(fenceLen);
2555
+ let closeStart = lineEnd + 1;
2556
+ let found = false;
2557
+ while (closeStart < text.length) {
2558
+ const nextNewline = text.indexOf("\n", closeStart);
2559
+ const lineContent = nextNewline === -1 ? text.slice(closeStart) : text.slice(closeStart, nextNewline);
2560
+ if (lineContent.trim().startsWith(closingPattern)) {
2561
+ const endPos = nextNewline === -1 ? text.length : nextNewline + 1;
2562
+ if (current)
2563
+ parts.push({ text: current, isCode: false });
2564
+ current = "";
2565
+ parts.push({ text: text.slice(i, endPos), isCode: true });
2566
+ i = endPos;
2567
+ found = true;
2568
+ break;
2569
+ }
2570
+ if (nextNewline === -1)
2571
+ break;
2572
+ closeStart = nextNewline + 1;
2573
+ }
2574
+ if (!found) {
2575
+ if (current)
2576
+ parts.push({ text: current, isCode: false });
2577
+ parts.push({ text: text.slice(i), isCode: true });
2578
+ return parts;
2579
+ }
2580
+ continue;
2581
+ }
2582
+ if (text[i] === "`") {
2583
+ let ticks = 0;
2584
+ while (i + ticks < text.length && text[i + ticks] === "`")
2585
+ ticks++;
2586
+ const closer = "`".repeat(ticks);
2587
+ const closeIdx = text.indexOf(closer, i + ticks);
2588
+ if (closeIdx !== -1) {
2589
+ if (current)
2590
+ parts.push({ text: current, isCode: false });
2591
+ current = "";
2592
+ parts.push({ text: text.slice(i, closeIdx + ticks), isCode: true });
2593
+ i = closeIdx + ticks;
2594
+ continue;
2595
+ }
2596
+ current += text.slice(i, i + ticks);
2597
+ i += ticks;
2598
+ continue;
2599
+ }
2600
+ current += text[i];
2601
+ i++;
2602
+ }
2603
+ if (current)
2604
+ parts.push({ text: current, isCode: false });
2605
+ return parts;
2606
+ }
2607
+
2369
2608
  // dist/ai/generate.js
2370
2609
  async function generateAIContent(options) {
2371
- const { store, flatSchema, contentRoot, config } = options;
2610
+ const { store, flatSchema, contentRoot, config, entryLinkUrl } = options;
2372
2611
  const files = /* @__PURE__ */ new Map();
2612
+ const idIndex = await store.idIndex();
2373
2613
  const collections = flatSchema.filter((item) => item.type === "collection");
2374
2614
  const allEntries = [];
2375
2615
  const manifestCollections = [];
@@ -2381,7 +2621,7 @@ async function generateAIContent(options) {
2381
2621
  continue;
2382
2622
  if (collection.parentPath && collection.parentPath !== contentRoot)
2383
2623
  continue;
2384
- const collectionResult = await processCollection(store, collection, flatSchema, contentRoot, config);
2624
+ const collectionResult = await processCollection(store, collection, flatSchema, contentRoot, config, idIndex, entryLinkUrl);
2385
2625
  allEntries.push(...collectionResult.entries);
2386
2626
  for (const [filePath, content] of collectionResult.files) {
2387
2627
  files.set(filePath, content);
@@ -2390,7 +2630,7 @@ async function generateAIContent(options) {
2390
2630
  }
2391
2631
  const rootCollection = collections.find((c) => c.logicalPath === contentRoot);
2392
2632
  if (rootCollection?.entries) {
2393
- const rootResult = await processRootEntries(store, rootCollection, contentRoot, config);
2633
+ const rootResult = await processRootEntries(store, rootCollection, contentRoot, config, idIndex, entryLinkUrl);
2394
2634
  allEntries.push(...rootResult.entries);
2395
2635
  for (const [filePath, content] of rootResult.files) {
2396
2636
  files.set(filePath, content);
@@ -2426,7 +2666,7 @@ async function generateAIContent(options) {
2426
2666
  files.set("manifest.json", JSON.stringify(manifest, null, 2));
2427
2667
  return { manifest, files };
2428
2668
  }
2429
- async function processCollection(store, collection, flatSchema, contentRoot, config) {
2669
+ async function processCollection(store, collection, flatSchema, contentRoot, config, idIndex, entryLinkUrl) {
2430
2670
  const files = /* @__PURE__ */ new Map();
2431
2671
  const entries = [];
2432
2672
  const cleanPath = stripContentRoot(collection.logicalPath, contentRoot);
@@ -2447,6 +2687,9 @@ async function processCollection(store, collection, flatSchema, contentRoot, con
2447
2687
  resolveReferences: false
2448
2688
  });
2449
2689
  const aiEntry = docToAIEntry(doc, listEntry.slug, entryTypeName, entryTypeConfig, cleanPath);
2690
+ if (aiEntry.body && idIndex) {
2691
+ aiEntry.body = resolveEntryLinksInText(aiEntry.body, idIndex, contentRoot, entryLinkUrl);
2692
+ }
2450
2693
  if (config?.exclude?.where?.(aiEntry))
2451
2694
  continue;
2452
2695
  entries.push(aiEntry);
@@ -2468,7 +2711,7 @@ async function processCollection(store, collection, flatSchema, contentRoot, con
2468
2711
  for (const sub of subcollections) {
2469
2712
  if (isCollectionExcluded(sub.logicalPath, contentRoot, config))
2470
2713
  continue;
2471
- const subResult = await processCollection(store, sub, flatSchema, contentRoot, config);
2714
+ const subResult = await processCollection(store, sub, flatSchema, contentRoot, config, idIndex, entryLinkUrl);
2472
2715
  entries.push(...subResult.entries);
2473
2716
  for (const [filePath, content] of subResult.files) {
2474
2717
  files.set(filePath, content);
@@ -2492,7 +2735,7 @@ async function processCollection(store, collection, flatSchema, contentRoot, con
2492
2735
  };
2493
2736
  return { entries, files, manifestCollection };
2494
2737
  }
2495
- async function processRootEntries(store, rootCollection, contentRoot, config) {
2738
+ async function processRootEntries(store, rootCollection, contentRoot, config, idIndex, entryLinkUrl) {
2496
2739
  const files = /* @__PURE__ */ new Map();
2497
2740
  const entries = [];
2498
2741
  const manifestEntries = [];
@@ -2512,6 +2755,9 @@ async function processRootEntries(store, rootCollection, contentRoot, config) {
2512
2755
  resolveReferences: false
2513
2756
  });
2514
2757
  const aiEntry = docToAIEntry(doc, listEntry.slug, entryTypeName, entryTypeConfig, "");
2758
+ if (aiEntry.body && idIndex) {
2759
+ aiEntry.body = resolveEntryLinksInText(aiEntry.body, idIndex, contentRoot, entryLinkUrl);
2760
+ }
2515
2761
  if (config?.exclude?.where?.(aiEntry))
2516
2762
  continue;
2517
2763
  entries.push(aiEntry);
@@ -2556,7 +2802,7 @@ function docToAIEntry(doc, slug, entryTypeName, entryTypeConfig, cleanCollection
2556
2802
  entryType: entryTypeName,
2557
2803
  format: doc.format,
2558
2804
  data: doc.data,
2559
- body: doc.format !== "json" ? doc.body : void 0,
2805
+ body: !isDataOnlyFormat(doc.format) ? doc.body : void 0,
2560
2806
  fields: entryTypeConfig.schema
2561
2807
  };
2562
2808
  }
@@ -2902,97 +3148,6 @@ import fs9 from "node:fs/promises";
2902
3148
  import path10 from "node:path";
2903
3149
  import { simpleGit as simpleGit2 } from "simple-git";
2904
3150
 
2905
- // dist/utils/debug.js
2906
- var LOG_LEVELS = {
2907
- DEBUG: 0,
2908
- INFO: 1,
2909
- WARN: 2,
2910
- ERROR: 3
2911
- };
2912
- var DebugLogger = class {
2913
- constructor(options = {}) {
2914
- this.timers = /* @__PURE__ */ new Map();
2915
- this.options = options;
2916
- }
2917
- shouldLog(level) {
2918
- const enabled = this.options.enabled ?? process.env.CANOPYCMS_DEBUG === "true";
2919
- if (!enabled)
2920
- return false;
2921
- const minLevel = this.options.minLevel ?? "DEBUG";
2922
- return LOG_LEVELS[level] >= LOG_LEVELS[minLevel];
2923
- }
2924
- formatMessage(level, category, message) {
2925
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2926
- const prefix = this.options.prefix ?? "CanopyCMS";
2927
- return `[${timestamp}] [${prefix}:${category}] [${level}] ${message}`;
2928
- }
2929
- debug(category, message, data) {
2930
- if (this.shouldLog("DEBUG")) {
2931
- console.log(this.formatMessage("DEBUG", category, message), data ?? "");
2932
- }
2933
- }
2934
- info(category, message, data) {
2935
- if (this.shouldLog("INFO")) {
2936
- console.log(this.formatMessage("INFO", category, message), data ?? "");
2937
- }
2938
- }
2939
- warn(category, message, data) {
2940
- if (this.shouldLog("WARN")) {
2941
- console.warn(this.formatMessage("WARN", category, message), data ?? "");
2942
- }
2943
- }
2944
- error(category, message, data) {
2945
- const msg = this.formatMessage("ERROR", category, message);
2946
- if (this.shouldLog("ERROR")) {
2947
- console.error(msg, data ?? "");
2948
- }
2949
- const throwOnError = this.options.throwOnError ?? false;
2950
- if (throwOnError) {
2951
- const errorMsg = data ? `${message}: ${JSON.stringify(data)}` : message;
2952
- throw new Error(errorMsg);
2953
- }
2954
- }
2955
- /**
2956
- * Start timing an operation
2957
- */
2958
- time(label) {
2959
- this.timers.set(label, Date.now());
2960
- }
2961
- /**
2962
- * End timing an operation and log the duration
2963
- */
2964
- timeEnd(category, label) {
2965
- const start = this.timers.get(label);
2966
- if (start === void 0) {
2967
- this.warn(category, `Timer '${label}' does not exist`);
2968
- return;
2969
- }
2970
- const duration = Date.now() - start;
2971
- this.timers.delete(label);
2972
- this.debug(category, `${label} completed`, { durationMs: duration });
2973
- return duration;
2974
- }
2975
- /**
2976
- * Wrap an async function with automatic timing
2977
- */
2978
- async timed(category, label, fn) {
2979
- this.time(label);
2980
- try {
2981
- return await fn();
2982
- } finally {
2983
- this.timeEnd(category, label);
2984
- }
2985
- }
2986
- };
2987
- function createDebugLogger(options) {
2988
- return new DebugLogger(options);
2989
- }
2990
- var testLogger = createDebugLogger({
2991
- enabled: process.env.E2E_DEBUG === "true",
2992
- prefix: "E2E",
2993
- throwOnError: false
2994
- });
2995
-
2996
3151
  // dist/utils/git.js
2997
3152
  import { simpleGit } from "simple-git";
2998
3153
  async function detectHeadBranch(repoRoot, fallback = "main") {
@@ -3006,7 +3161,7 @@ async function detectHeadBranch(repoRoot, fallback = "main") {
3006
3161
  }
3007
3162
 
3008
3163
  // dist/git-manager.js
3009
- var log = createDebugLogger({ prefix: "GitManager" });
3164
+ var log2 = createDebugLogger({ prefix: "GitManager" });
3010
3165
  var remoteInitLocks = /* @__PURE__ */ new Map();
3011
3166
  var GitManager = class _GitManager {
3012
3167
  constructor(options, gitOptions) {
@@ -3017,14 +3172,14 @@ var GitManager = class _GitManager {
3017
3172
  this.git.env("GIT_CEILING_DIRECTORIES", path10.dirname(this.repoPath));
3018
3173
  }
3019
3174
  static async cloneRepo(remoteUrl, targetPath, baseBranch = "main") {
3020
- log.debug("git", "Cloning repository", {
3175
+ log2.debug("git", "Cloning repository", {
3021
3176
  remoteUrl,
3022
3177
  targetPath,
3023
3178
  baseBranch
3024
3179
  });
3025
3180
  const git = simpleGit2();
3026
3181
  await git.clone(remoteUrl, targetPath, ["--branch", baseBranch, "--single-branch"]);
3027
- log.debug("git", "Clone complete");
3182
+ log2.debug("git", "Clone complete");
3028
3183
  }
3029
3184
  /**
3030
3185
  * Initializes a local bare git repository to simulate a remote for dev mode.
@@ -3040,32 +3195,32 @@ var GitManager = class _GitManager {
3040
3195
  static async ensureLocalSimulatedRemote(options) {
3041
3196
  const existingLock = remoteInitLocks.get(options.remotePath);
3042
3197
  if (existingLock) {
3043
- log.debug("git", "Waiting for existing remote initialization", {
3198
+ log2.debug("git", "Waiting for existing remote initialization", {
3044
3199
  remotePath: options.remotePath
3045
3200
  });
3046
3201
  await existingLock;
3047
3202
  try {
3048
3203
  const stat = await fs9.stat(options.remotePath);
3049
3204
  if (stat.isDirectory()) {
3050
- log.debug("git", "Remote exists after waiting for lock");
3205
+ log2.debug("git", "Remote exists after waiting for lock");
3051
3206
  return;
3052
3207
  }
3053
3208
  } catch (err) {
3054
3209
  if (!isNotFoundError(err))
3055
3210
  throw err;
3056
- log.debug("git", "Remote does not exist after lock, will retry initialization");
3211
+ log2.debug("git", "Remote does not exist after lock, will retry initialization");
3057
3212
  }
3058
3213
  }
3059
- const lockPromise = log.timed("git", "ensureLocalSimulatedRemote", async () => {
3214
+ const lockPromise = log2.timed("git", "ensureLocalSimulatedRemote", async () => {
3060
3215
  try {
3061
- log.debug("git", "Initializing local simulated remote", {
3216
+ log2.debug("git", "Initializing local simulated remote", {
3062
3217
  remotePath: options.remotePath,
3063
3218
  baseBranch: options.baseBranch
3064
3219
  });
3065
3220
  try {
3066
3221
  const stat = await fs9.stat(options.remotePath);
3067
3222
  if (stat.isDirectory()) {
3068
- log.debug("git", "Remote already exists, skipping");
3223
+ log2.debug("git", "Remote already exists, skipping");
3069
3224
  return;
3070
3225
  }
3071
3226
  } catch (err) {
@@ -3088,8 +3243,8 @@ var GitManager = class _GitManager {
3088
3243
  }
3089
3244
  let hasCommits = false;
3090
3245
  try {
3091
- const log3 = await sourceGit.log(["-1"]);
3092
- hasCommits = log3.total > 0;
3246
+ const log4 = await sourceGit.log(["-1"]);
3247
+ hasCommits = log4.total > 0;
3093
3248
  } catch {
3094
3249
  hasCommits = false;
3095
3250
  }
@@ -3100,7 +3255,7 @@ var GitManager = class _GitManager {
3100
3255
  if (!branches.all.includes(options.baseBranch)) {
3101
3256
  throw new Error(`Cannot initialize local simulated remote: base branch '${options.baseBranch}' does not exist locally. Please checkout '${options.baseBranch}' first or provide an explicit remoteUrl.`);
3102
3257
  }
3103
- log.debug("git", "Creating bare remote repository");
3258
+ log2.debug("git", "Creating bare remote repository");
3104
3259
  await fs9.mkdir(path10.dirname(options.remotePath), { recursive: true });
3105
3260
  await simpleGit2().raw([
3106
3261
  "init",
@@ -3140,7 +3295,7 @@ var GitManager = class _GitManager {
3140
3295
  } catch {
3141
3296
  }
3142
3297
  }
3143
- log.debug("git", "Remote initialization complete");
3298
+ log2.debug("git", "Remote initialization complete");
3144
3299
  } finally {
3145
3300
  remoteInitLocks.delete(options.remotePath);
3146
3301
  }
@@ -3209,7 +3364,7 @@ var GitManager = class _GitManager {
3209
3364
  try {
3210
3365
  const stat = await fs9.stat(config.autoDetectRemotePath);
3211
3366
  if (stat.isDirectory()) {
3212
- log.debug("git", "Auto-detected local remote", {
3367
+ log2.debug("git", "Auto-detected local remote", {
3213
3368
  path: config.autoDetectRemotePath
3214
3369
  });
3215
3370
  return config.autoDetectRemotePath;
@@ -3262,7 +3417,7 @@ var GitManager = class _GitManager {
3262
3417
  try {
3263
3418
  const stat = await fs9.stat(gitPath);
3264
3419
  if (stat.isDirectory()) {
3265
- log.debug("git", "Removing corrupt .git directory", {
3420
+ log2.debug("git", "Removing corrupt .git directory", {
3266
3421
  workspacePath: options.workspacePath
3267
3422
  });
3268
3423
  await fs9.rm(gitPath, { recursive: true });
@@ -3300,7 +3455,7 @@ var GitManager = class _GitManager {
3300
3455
  await git.git.addConfig("canopycms.managed", "true");
3301
3456
  await git.git.addConfig("user.name", options.gitBotAuthorName);
3302
3457
  await git.git.addConfig("user.email", options.gitBotAuthorEmail);
3303
- log.debug("git", "Marked workspace as CanopyCMS-managed", {
3458
+ log2.debug("git", "Marked workspace as CanopyCMS-managed", {
3304
3459
  workspacePath: options.workspacePath
3305
3460
  });
3306
3461
  if (!justCloned) {
@@ -3463,13 +3618,13 @@ var GitManager = class _GitManager {
3463
3618
  }
3464
3619
  const lines = content.split("\n");
3465
3620
  if (lines.some((line) => line.trim() === pattern)) {
3466
- log.debug("git", "Pattern already in .git/info/exclude", { pattern });
3621
+ log2.debug("git", "Pattern already in .git/info/exclude", { pattern });
3467
3622
  return;
3468
3623
  }
3469
3624
  const needsLeadingNewline = content.length > 0 && !content.endsWith("\n");
3470
3625
  const newContent = content + (needsLeadingNewline ? "\n" : "") + pattern + "\n";
3471
3626
  await fs9.writeFile(excludePath, newContent, "utf-8");
3472
- log.debug("git", "Added pattern to .git/info/exclude", { pattern });
3627
+ log2.debug("git", "Added pattern to .git/info/exclude", { pattern });
3473
3628
  }
3474
3629
  /**
3475
3630
  * Create an orphan branch for settings (permissions/groups).
@@ -3483,10 +3638,10 @@ var GitManager = class _GitManager {
3483
3638
  * @param initialFiles - Files to commit to the new branch (e.g., { 'permissions.json': '{}', 'groups.json': '{}' })
3484
3639
  */
3485
3640
  async createOrphanSettingsBranch(branchName, initialFiles) {
3486
- log.debug("git", "Creating orphan settings branch", { branchName });
3641
+ log2.debug("git", "Creating orphan settings branch", { branchName });
3487
3642
  const branches = await this.git.branch();
3488
3643
  if (branches.all.includes(branchName)) {
3489
- log.debug("git", "Orphan branch already exists", { branchName });
3644
+ log2.debug("git", "Orphan branch already exists", { branchName });
3490
3645
  await this.git.checkout(branchName);
3491
3646
  return;
3492
3647
  }
@@ -3502,19 +3657,19 @@ var GitManager = class _GitManager {
3502
3657
  await this.git.add(filePath);
3503
3658
  }
3504
3659
  await this.git.commit("Initialize settings branch", ["--allow-empty"]);
3505
- log.debug("git", "Orphan settings branch created", { branchName });
3660
+ log2.debug("git", "Orphan settings branch created", { branchName });
3506
3661
  }
3507
3662
  };
3508
3663
 
3509
3664
  // dist/branch-workspace.js
3510
- var log2 = createDebugLogger({ prefix: "BranchWorkspace" });
3665
+ var log3 = createDebugLogger({ prefix: "BranchWorkspace" });
3511
3666
  var workspaceInitLocks = /* @__PURE__ */ new Map();
3512
3667
  var BranchWorkspaceManager = class {
3513
3668
  constructor(config) {
3514
3669
  this.config = config;
3515
3670
  }
3516
3671
  async ensureGitWorkspace(options) {
3517
- return log2.timed("workspace", "ensureGitWorkspace", async () => {
3672
+ return log3.timed("workspace", "ensureGitWorkspace", async () => {
3518
3673
  const existingLock = workspaceInitLocks.get(options.branchRoot);
3519
3674
  if (existingLock) {
3520
3675
  await existingLock;
@@ -3522,7 +3677,7 @@ var BranchWorkspaceManager = class {
3522
3677
  }
3523
3678
  const lockPromise = (async () => {
3524
3679
  try {
3525
- log2.debug("workspace", "Ensuring git workspace", {
3680
+ log3.debug("workspace", "Ensuring git workspace", {
3526
3681
  branchName: options.branchName,
3527
3682
  mode: options.mode
3528
3683
  });
@@ -3651,7 +3806,8 @@ async function generateAIContentFiles(options) {
3651
3806
  store,
3652
3807
  flatSchema,
3653
3808
  contentRoot: contentRootName,
3654
- config: aiConfig
3809
+ config: aiConfig,
3810
+ entryLinkUrl: config.entryLinkUrl
3655
3811
  });
3656
3812
  const absoluteOutputDir = path11.resolve(outputDir) + path11.sep;
3657
3813
  let fileCount = 0;