canopycms 0.0.25 → 0.0.27

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 (92) 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 +17 -6
  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/index.d.ts +1 -1
  9. package/dist/ai/index.d.ts.map +1 -1
  10. package/dist/ai/json-to-markdown.d.ts.map +1 -1
  11. package/dist/ai/json-to-markdown.js +10 -2
  12. package/dist/ai/json-to-markdown.js.map +1 -1
  13. package/dist/ai/transform-components.d.ts +27 -0
  14. package/dist/ai/transform-components.d.ts.map +1 -0
  15. package/dist/ai/transform-components.js +168 -0
  16. package/dist/ai/transform-components.js.map +1 -0
  17. package/dist/ai/types.d.ts +50 -0
  18. package/dist/ai/types.d.ts.map +1 -1
  19. package/dist/ai/types.js.map +1 -1
  20. package/dist/api/content.d.ts +6 -0
  21. package/dist/api/content.d.ts.map +1 -1
  22. package/dist/api/content.js +18 -1
  23. package/dist/api/content.js.map +1 -1
  24. package/dist/api/schema.d.ts +20 -20
  25. package/dist/build/generate-ai-content.d.ts.map +1 -1
  26. package/dist/build/generate-ai-content.js +1 -0
  27. package/dist/build/generate-ai-content.js.map +1 -1
  28. package/dist/cli/generate-ai-content.js +383 -124
  29. package/dist/cli/init.js +2 -1
  30. package/dist/client.d.ts +2 -0
  31. package/dist/client.d.ts.map +1 -1
  32. package/dist/client.js +1 -0
  33. package/dist/client.js.map +1 -1
  34. package/dist/config/helpers.d.ts.map +1 -1
  35. package/dist/config/helpers.js +2 -1
  36. package/dist/config/helpers.js.map +1 -1
  37. package/dist/config/schemas/collection.d.ts +6 -6
  38. package/dist/config/schemas/config.d.ts +10 -6
  39. package/dist/config/schemas/config.d.ts.map +1 -1
  40. package/dist/config/schemas/config.js +1 -0
  41. package/dist/config/schemas/config.js.map +1 -1
  42. package/dist/config/types.d.ts +6 -1
  43. package/dist/config/types.d.ts.map +1 -1
  44. package/dist/config/types.js.map +1 -1
  45. package/dist/content-reader.d.ts +2 -0
  46. package/dist/content-reader.d.ts.map +1 -1
  47. package/dist/content-reader.js +13 -4
  48. package/dist/content-reader.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/fields/MarkdownField.d.ts.map +1 -1
  57. package/dist/editor/fields/MarkdownField.js +8 -2
  58. package/dist/editor/fields/MarkdownField.js.map +1 -1
  59. package/dist/editor/fields/entry-link/EntryLinkContext.d.ts +20 -0
  60. package/dist/editor/fields/entry-link/EntryLinkContext.d.ts.map +1 -0
  61. package/dist/editor/fields/entry-link/EntryLinkContext.js +12 -0
  62. package/dist/editor/fields/entry-link/EntryLinkContext.js.map +1 -0
  63. package/dist/editor/fields/entry-link/InsertEntryLink.d.ts +16 -0
  64. package/dist/editor/fields/entry-link/InsertEntryLink.d.ts.map +1 -0
  65. package/dist/editor/fields/entry-link/InsertEntryLink.js +62 -0
  66. package/dist/editor/fields/entry-link/InsertEntryLink.js.map +1 -0
  67. package/dist/editor/fields/entry-link/index.d.ts +3 -0
  68. package/dist/editor/fields/entry-link/index.d.ts.map +1 -0
  69. package/dist/editor/fields/entry-link/index.js +3 -0
  70. package/dist/editor/fields/entry-link/index.js.map +1 -0
  71. package/dist/editor/hooks/useEntryLinkResolution.d.ts +26 -0
  72. package/dist/editor/hooks/useEntryLinkResolution.d.ts.map +1 -0
  73. package/dist/editor/hooks/useEntryLinkResolution.js +96 -0
  74. package/dist/editor/hooks/useEntryLinkResolution.js.map +1 -0
  75. package/dist/entry-link-resolver.d.ts +67 -0
  76. package/dist/entry-link-resolver.d.ts.map +1 -0
  77. package/dist/entry-link-resolver.js +226 -0
  78. package/dist/entry-link-resolver.js.map +1 -0
  79. package/dist/schema/schema-store.d.ts +10 -10
  80. package/dist/server.d.ts +3 -0
  81. package/dist/server.d.ts.map +1 -1
  82. package/dist/server.js +2 -0
  83. package/dist/server.js.map +1 -1
  84. package/dist/utils/entry-url.d.ts +21 -0
  85. package/dist/utils/entry-url.d.ts.map +1 -0
  86. package/dist/utils/entry-url.js +41 -0
  87. package/dist/utils/entry-url.js.map +1 -0
  88. package/dist/validation/entry-link-validator.d.ts +27 -0
  89. package/dist/validation/entry-link-validator.d.ts.map +1 -0
  90. package/dist/validation/entry-link-validator.js +49 -0
  91. package/dist/validation/entry-link-validator.js.map +1 -0
  92. package/package.json +1 -1
@@ -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";
@@ -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
  }
@@ -1974,6 +1984,119 @@ function stripMdxImports(body) {
1974
1984
  return result.join("\n").replace(/\n{3,}/g, "\n\n").trim();
1975
1985
  }
1976
1986
 
1987
+ // dist/ai/transform-components.js
1988
+ function parseComponentProps(attrString) {
1989
+ const props = {};
1990
+ if (!attrString)
1991
+ return props;
1992
+ const attrRegex = /(\w+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|\{([^}]*)\}))?/g;
1993
+ let match;
1994
+ while ((match = attrRegex.exec(attrString)) !== null) {
1995
+ const name = match[1];
1996
+ const value = match[2] ?? match[3] ?? match[4] ?? "true";
1997
+ props[name] = value;
1998
+ }
1999
+ return props;
2000
+ }
2001
+ var ATTR_CONTENT = `(?:[^>"']|"[^"]*"|'[^']*')*`;
2002
+ var BLOCK_PREFIX = "<<CODEBLOCK";
2003
+ var BLOCK_SUFFIX = ">>";
2004
+ var BLOCK_RESTORE_RE = /<<CODEBLOCK(\d+)>>/g;
2005
+ var INLINE_PREFIX = "<<INLINECODE";
2006
+ var INLINE_SUFFIX = ">>";
2007
+ var INLINE_RESTORE_RE = /<<INLINECODE(\d+)>>/g;
2008
+ function maskCodeBlocks(body) {
2009
+ const blocks = [];
2010
+ const inlines = [];
2011
+ let masked = body.replace(/^(```|~~~).*\n[\s\S]*?\n\1\s*$/gm, (block) => {
2012
+ const idx = blocks.length;
2013
+ blocks.push(block);
2014
+ return `${BLOCK_PREFIX}${idx}${BLOCK_SUFFIX}`;
2015
+ });
2016
+ masked = masked.replace(/``[^`]+``|`[^`]+`/g, (span) => {
2017
+ const idx = inlines.length;
2018
+ inlines.push(span);
2019
+ return `${INLINE_PREFIX}${idx}${INLINE_SUFFIX}`;
2020
+ });
2021
+ return {
2022
+ masked,
2023
+ restore: (s) => {
2024
+ s = s.replace(INLINE_RESTORE_RE, (_, i) => inlines[Number(i)]);
2025
+ s = s.replace(BLOCK_RESTORE_RE, (_, i) => blocks[Number(i)]);
2026
+ return s;
2027
+ }
2028
+ };
2029
+ }
2030
+ function applyComponentTransforms(body, transforms) {
2031
+ const names = Object.keys(transforms);
2032
+ if (names.length === 0)
2033
+ return body;
2034
+ const { masked, restore } = maskCodeBlocks(body);
2035
+ let result = masked;
2036
+ const MAX_PASSES = 10;
2037
+ for (let pass = 0; pass < MAX_PASSES; pass++) {
2038
+ let changed = false;
2039
+ for (const name of names) {
2040
+ const transform = transforms[name];
2041
+ const selfClosingRegex = new RegExp(`<${escapeRegex(name)}(\\s${ATTR_CONTENT}?)?\\s*/>`, "g");
2042
+ result = result.replace(selfClosingRegex, (raw, attrStr) => {
2043
+ const props = parseComponentProps(attrStr?.trim() ?? "");
2044
+ const replacement = transform(props, "");
2045
+ if (replacement === void 0)
2046
+ return raw;
2047
+ changed = true;
2048
+ return replacement;
2049
+ });
2050
+ const openRegex = new RegExp(`<${escapeRegex(name)}(\\s${ATTR_CONTENT})?>`, "g");
2051
+ let openMatch;
2052
+ while ((openMatch = openRegex.exec(result)) !== null) {
2053
+ const openStart = openMatch.index;
2054
+ const openEnd = openStart + openMatch[0].length;
2055
+ const attrStr = openMatch[1]?.trim() ?? "";
2056
+ const closeTag = `</${name}>`;
2057
+ const closeIdx = findMatchingClose(result, openEnd, name, closeTag);
2058
+ if (closeIdx === -1)
2059
+ break;
2060
+ const children = result.slice(openEnd, closeIdx);
2061
+ const fullEnd = closeIdx + closeTag.length;
2062
+ const props = parseComponentProps(attrStr);
2063
+ const replacement = transform(props, children);
2064
+ if (replacement === void 0) {
2065
+ openRegex.lastIndex = fullEnd;
2066
+ continue;
2067
+ }
2068
+ result = result.slice(0, openStart) + replacement + result.slice(fullEnd);
2069
+ changed = true;
2070
+ openRegex.lastIndex = openStart + replacement.length;
2071
+ }
2072
+ }
2073
+ if (!changed)
2074
+ break;
2075
+ }
2076
+ return restore(result);
2077
+ }
2078
+ function findMatchingClose(body, startFrom, name, closeTag) {
2079
+ let depth = 1;
2080
+ const pos = startFrom;
2081
+ const tagRegex = new RegExp(`<${escapeRegex(name)}(?:\\s${ATTR_CONTENT})?>|<${escapeRegex(name)}(?:\\s${ATTR_CONTENT})?\\s*/>|${escapeRegex(closeTag)}`, "g");
2082
+ tagRegex.lastIndex = pos;
2083
+ let match;
2084
+ while ((match = tagRegex.exec(body)) !== null) {
2085
+ const tag = match[0];
2086
+ if (tag === closeTag) {
2087
+ depth--;
2088
+ if (depth === 0)
2089
+ return match.index;
2090
+ } else if (!tag.endsWith("/>")) {
2091
+ depth++;
2092
+ }
2093
+ }
2094
+ return -1;
2095
+ }
2096
+ function escapeRegex(s) {
2097
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2098
+ }
2099
+
1977
2100
  // dist/ai/json-to-markdown.js
1978
2101
  function entryToMarkdown(entry, config) {
1979
2102
  const parts = [];
@@ -2017,7 +2140,14 @@ function renderMarkdownEntry(entry, config, skipFields) {
2017
2140
  parts.push("");
2018
2141
  }
2019
2142
  if (entry.body) {
2020
- const body = entry.format === "mdx" ? stripMdxImports(entry.body) : entry.body;
2143
+ let body = entry.format === "mdx" ? stripMdxImports(entry.body) : entry.body;
2144
+ if (config?.componentTransforms && Object.keys(config.componentTransforms).length > 0) {
2145
+ body = applyComponentTransforms(body, config.componentTransforms);
2146
+ }
2147
+ const bodyTransformFn = config?.bodyTransforms?.[entry.entryType];
2148
+ if (bodyTransformFn) {
2149
+ body = bodyTransformFn(body, entry);
2150
+ }
2021
2151
  parts.push(body.trim());
2022
2152
  parts.push("");
2023
2153
  }
@@ -2246,10 +2376,223 @@ function yamlValue(value) {
2246
2376
  return value;
2247
2377
  }
2248
2378
 
2379
+ // dist/utils/debug.js
2380
+ var LOG_LEVELS = {
2381
+ DEBUG: 0,
2382
+ INFO: 1,
2383
+ WARN: 2,
2384
+ ERROR: 3
2385
+ };
2386
+ var DebugLogger = class {
2387
+ constructor(options = {}) {
2388
+ this.timers = /* @__PURE__ */ new Map();
2389
+ this.options = options;
2390
+ }
2391
+ shouldLog(level) {
2392
+ const enabled = this.options.enabled ?? process.env.CANOPYCMS_DEBUG === "true";
2393
+ if (!enabled)
2394
+ return false;
2395
+ const minLevel = this.options.minLevel ?? "DEBUG";
2396
+ return LOG_LEVELS[level] >= LOG_LEVELS[minLevel];
2397
+ }
2398
+ formatMessage(level, category, message) {
2399
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2400
+ const prefix = this.options.prefix ?? "CanopyCMS";
2401
+ return `[${timestamp}] [${prefix}:${category}] [${level}] ${message}`;
2402
+ }
2403
+ debug(category, message, data) {
2404
+ if (this.shouldLog("DEBUG")) {
2405
+ console.log(this.formatMessage("DEBUG", category, message), data ?? "");
2406
+ }
2407
+ }
2408
+ info(category, message, data) {
2409
+ if (this.shouldLog("INFO")) {
2410
+ console.log(this.formatMessage("INFO", category, message), data ?? "");
2411
+ }
2412
+ }
2413
+ warn(category, message, data) {
2414
+ if (this.shouldLog("WARN")) {
2415
+ console.warn(this.formatMessage("WARN", category, message), data ?? "");
2416
+ }
2417
+ }
2418
+ error(category, message, data) {
2419
+ const msg = this.formatMessage("ERROR", category, message);
2420
+ if (this.shouldLog("ERROR")) {
2421
+ console.error(msg, data ?? "");
2422
+ }
2423
+ const throwOnError = this.options.throwOnError ?? false;
2424
+ if (throwOnError) {
2425
+ const errorMsg = data ? `${message}: ${JSON.stringify(data)}` : message;
2426
+ throw new Error(errorMsg);
2427
+ }
2428
+ }
2429
+ /**
2430
+ * Start timing an operation
2431
+ */
2432
+ time(label) {
2433
+ this.timers.set(label, Date.now());
2434
+ }
2435
+ /**
2436
+ * End timing an operation and log the duration
2437
+ */
2438
+ timeEnd(category, label) {
2439
+ const start = this.timers.get(label);
2440
+ if (start === void 0) {
2441
+ this.warn(category, `Timer '${label}' does not exist`);
2442
+ return;
2443
+ }
2444
+ const duration = Date.now() - start;
2445
+ this.timers.delete(label);
2446
+ this.debug(category, `${label} completed`, { durationMs: duration });
2447
+ return duration;
2448
+ }
2449
+ /**
2450
+ * Wrap an async function with automatic timing
2451
+ */
2452
+ async timed(category, label, fn) {
2453
+ this.time(label);
2454
+ try {
2455
+ return await fn();
2456
+ } finally {
2457
+ this.timeEnd(category, label);
2458
+ }
2459
+ }
2460
+ };
2461
+ function createDebugLogger(options) {
2462
+ return new DebugLogger(options);
2463
+ }
2464
+ var testLogger = createDebugLogger({
2465
+ enabled: process.env.E2E_DEBUG === "true",
2466
+ prefix: "E2E",
2467
+ throwOnError: false
2468
+ });
2469
+
2470
+ // dist/utils/entry-url.js
2471
+ init_normalize();
2472
+ function computeEntryUrl(collection, slug, contentRoot) {
2473
+ const root = trimSlashes(contentRoot);
2474
+ let stripped = collection;
2475
+ if (root && collection.startsWith(`${root}/`)) {
2476
+ stripped = collection.slice(root.length + 1);
2477
+ } else if (collection === root) {
2478
+ stripped = "";
2479
+ }
2480
+ const segments = stripped.split("/").filter(Boolean);
2481
+ if (slug && slug !== "index") {
2482
+ segments.push(slug);
2483
+ }
2484
+ const path13 = segments.length > 0 ? `/${segments.join("/")}` : "/";
2485
+ return path13.toLowerCase();
2486
+ }
2487
+
2488
+ // dist/entry-link-resolver.js
2489
+ var log = createDebugLogger({ prefix: "EntryLinks" });
2490
+ var BASE58_CHAR = "[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]";
2491
+ var ENTRY_LINK_PATTERN = new RegExp(`entry:(${BASE58_CHAR}{12})(#[^\\s)>"']*)?`, "g");
2492
+ function resolveEntryUrl(location, contentRoot) {
2493
+ return computeEntryUrl(location.collection ?? "", location.slug ?? "", contentRoot);
2494
+ }
2495
+ function resolveEntryLinksInText(text, idIndex, contentRoot, customResolver) {
2496
+ const parts = splitByCodeRegions(text);
2497
+ return parts.map((part) => {
2498
+ if (part.isCode)
2499
+ return part.text;
2500
+ return part.text.replace(ENTRY_LINK_PATTERN, (_match, id, anchor) => {
2501
+ const location = idIndex.findById(id);
2502
+ if (!location || location.type !== "entry" || !location.collection || !location.slug) {
2503
+ log.warn("resolve", `Entry link target not found: entry:${id}`);
2504
+ return anchor ?? "#";
2505
+ }
2506
+ let url;
2507
+ if (customResolver) {
2508
+ url = customResolver({
2509
+ collection: location.collection,
2510
+ slug: location.slug,
2511
+ id
2512
+ });
2513
+ } else {
2514
+ url = resolveEntryUrl(location, contentRoot);
2515
+ }
2516
+ return `${url}${anchor ?? ""}`;
2517
+ });
2518
+ }).join("");
2519
+ }
2520
+ function splitByCodeRegions(text) {
2521
+ const parts = [];
2522
+ let current = "";
2523
+ let i = 0;
2524
+ while (i < text.length) {
2525
+ if ((text[i] === "`" || text[i] === "~") && i + 2 < text.length && text[i + 1] === text[i] && text[i + 2] === text[i]) {
2526
+ const fence = text[i];
2527
+ let fenceLen = 0;
2528
+ while (i + fenceLen < text.length && text[i + fenceLen] === fence)
2529
+ fenceLen++;
2530
+ const lineEnd = text.indexOf("\n", i + fenceLen);
2531
+ if (lineEnd === -1) {
2532
+ if (current)
2533
+ parts.push({ text: current, isCode: false });
2534
+ parts.push({ text: text.slice(i), isCode: true });
2535
+ return parts;
2536
+ }
2537
+ const closingPattern = fence.repeat(fenceLen);
2538
+ let closeStart = lineEnd + 1;
2539
+ let found = false;
2540
+ while (closeStart < text.length) {
2541
+ const nextNewline = text.indexOf("\n", closeStart);
2542
+ const lineContent = nextNewline === -1 ? text.slice(closeStart) : text.slice(closeStart, nextNewline);
2543
+ if (lineContent.trim().startsWith(closingPattern)) {
2544
+ const endPos = nextNewline === -1 ? text.length : nextNewline + 1;
2545
+ if (current)
2546
+ parts.push({ text: current, isCode: false });
2547
+ current = "";
2548
+ parts.push({ text: text.slice(i, endPos), isCode: true });
2549
+ i = endPos;
2550
+ found = true;
2551
+ break;
2552
+ }
2553
+ if (nextNewline === -1)
2554
+ break;
2555
+ closeStart = nextNewline + 1;
2556
+ }
2557
+ if (!found) {
2558
+ if (current)
2559
+ parts.push({ text: current, isCode: false });
2560
+ parts.push({ text: text.slice(i), isCode: true });
2561
+ return parts;
2562
+ }
2563
+ continue;
2564
+ }
2565
+ if (text[i] === "`") {
2566
+ let ticks = 0;
2567
+ while (i + ticks < text.length && text[i + ticks] === "`")
2568
+ ticks++;
2569
+ const closer = "`".repeat(ticks);
2570
+ const closeIdx = text.indexOf(closer, i + ticks);
2571
+ if (closeIdx !== -1) {
2572
+ if (current)
2573
+ parts.push({ text: current, isCode: false });
2574
+ current = "";
2575
+ parts.push({ text: text.slice(i, closeIdx + ticks), isCode: true });
2576
+ i = closeIdx + ticks;
2577
+ continue;
2578
+ }
2579
+ current += text.slice(i, i + ticks);
2580
+ i += ticks;
2581
+ continue;
2582
+ }
2583
+ current += text[i];
2584
+ i++;
2585
+ }
2586
+ if (current)
2587
+ parts.push({ text: current, isCode: false });
2588
+ return parts;
2589
+ }
2590
+
2249
2591
  // dist/ai/generate.js
2250
2592
  async function generateAIContent(options) {
2251
- const { store, flatSchema, contentRoot, config } = options;
2593
+ const { store, flatSchema, contentRoot, config, entryLinkUrl } = options;
2252
2594
  const files = /* @__PURE__ */ new Map();
2595
+ const idIndex = await store.idIndex();
2253
2596
  const collections = flatSchema.filter((item) => item.type === "collection");
2254
2597
  const allEntries = [];
2255
2598
  const manifestCollections = [];
@@ -2261,7 +2604,7 @@ async function generateAIContent(options) {
2261
2604
  continue;
2262
2605
  if (collection.parentPath && collection.parentPath !== contentRoot)
2263
2606
  continue;
2264
- const collectionResult = await processCollection(store, collection, flatSchema, contentRoot, config);
2607
+ const collectionResult = await processCollection(store, collection, flatSchema, contentRoot, config, idIndex, entryLinkUrl);
2265
2608
  allEntries.push(...collectionResult.entries);
2266
2609
  for (const [filePath, content] of collectionResult.files) {
2267
2610
  files.set(filePath, content);
@@ -2270,7 +2613,7 @@ async function generateAIContent(options) {
2270
2613
  }
2271
2614
  const rootCollection = collections.find((c) => c.logicalPath === contentRoot);
2272
2615
  if (rootCollection?.entries) {
2273
- const rootResult = await processRootEntries(store, rootCollection, contentRoot, config);
2616
+ const rootResult = await processRootEntries(store, rootCollection, contentRoot, config, idIndex, entryLinkUrl);
2274
2617
  allEntries.push(...rootResult.entries);
2275
2618
  for (const [filePath, content] of rootResult.files) {
2276
2619
  files.set(filePath, content);
@@ -2306,7 +2649,7 @@ async function generateAIContent(options) {
2306
2649
  files.set("manifest.json", JSON.stringify(manifest, null, 2));
2307
2650
  return { manifest, files };
2308
2651
  }
2309
- async function processCollection(store, collection, flatSchema, contentRoot, config) {
2652
+ async function processCollection(store, collection, flatSchema, contentRoot, config, idIndex, entryLinkUrl) {
2310
2653
  const files = /* @__PURE__ */ new Map();
2311
2654
  const entries = [];
2312
2655
  const cleanPath = stripContentRoot(collection.logicalPath, contentRoot);
@@ -2327,6 +2670,9 @@ async function processCollection(store, collection, flatSchema, contentRoot, con
2327
2670
  resolveReferences: false
2328
2671
  });
2329
2672
  const aiEntry = docToAIEntry(doc, listEntry.slug, entryTypeName, entryTypeConfig, cleanPath);
2673
+ if (aiEntry.body && idIndex) {
2674
+ aiEntry.body = resolveEntryLinksInText(aiEntry.body, idIndex, contentRoot, entryLinkUrl);
2675
+ }
2330
2676
  if (config?.exclude?.where?.(aiEntry))
2331
2677
  continue;
2332
2678
  entries.push(aiEntry);
@@ -2348,7 +2694,7 @@ async function processCollection(store, collection, flatSchema, contentRoot, con
2348
2694
  for (const sub of subcollections) {
2349
2695
  if (isCollectionExcluded(sub.logicalPath, contentRoot, config))
2350
2696
  continue;
2351
- const subResult = await processCollection(store, sub, flatSchema, contentRoot, config);
2697
+ const subResult = await processCollection(store, sub, flatSchema, contentRoot, config, idIndex, entryLinkUrl);
2352
2698
  entries.push(...subResult.entries);
2353
2699
  for (const [filePath, content] of subResult.files) {
2354
2700
  files.set(filePath, content);
@@ -2372,7 +2718,7 @@ async function processCollection(store, collection, flatSchema, contentRoot, con
2372
2718
  };
2373
2719
  return { entries, files, manifestCollection };
2374
2720
  }
2375
- async function processRootEntries(store, rootCollection, contentRoot, config) {
2721
+ async function processRootEntries(store, rootCollection, contentRoot, config, idIndex, entryLinkUrl) {
2376
2722
  const files = /* @__PURE__ */ new Map();
2377
2723
  const entries = [];
2378
2724
  const manifestEntries = [];
@@ -2392,6 +2738,9 @@ async function processRootEntries(store, rootCollection, contentRoot, config) {
2392
2738
  resolveReferences: false
2393
2739
  });
2394
2740
  const aiEntry = docToAIEntry(doc, listEntry.slug, entryTypeName, entryTypeConfig, "");
2741
+ if (aiEntry.body && idIndex) {
2742
+ aiEntry.body = resolveEntryLinksInText(aiEntry.body, idIndex, contentRoot, entryLinkUrl);
2743
+ }
2395
2744
  if (config?.exclude?.where?.(aiEntry))
2396
2745
  continue;
2397
2746
  entries.push(aiEntry);
@@ -2782,97 +3131,6 @@ import fs9 from "node:fs/promises";
2782
3131
  import path10 from "node:path";
2783
3132
  import { simpleGit as simpleGit2 } from "simple-git";
2784
3133
 
2785
- // dist/utils/debug.js
2786
- var LOG_LEVELS = {
2787
- DEBUG: 0,
2788
- INFO: 1,
2789
- WARN: 2,
2790
- ERROR: 3
2791
- };
2792
- var DebugLogger = class {
2793
- constructor(options = {}) {
2794
- this.timers = /* @__PURE__ */ new Map();
2795
- this.options = options;
2796
- }
2797
- shouldLog(level) {
2798
- const enabled = this.options.enabled ?? process.env.CANOPYCMS_DEBUG === "true";
2799
- if (!enabled)
2800
- return false;
2801
- const minLevel = this.options.minLevel ?? "DEBUG";
2802
- return LOG_LEVELS[level] >= LOG_LEVELS[minLevel];
2803
- }
2804
- formatMessage(level, category, message) {
2805
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2806
- const prefix = this.options.prefix ?? "CanopyCMS";
2807
- return `[${timestamp}] [${prefix}:${category}] [${level}] ${message}`;
2808
- }
2809
- debug(category, message, data) {
2810
- if (this.shouldLog("DEBUG")) {
2811
- console.log(this.formatMessage("DEBUG", category, message), data ?? "");
2812
- }
2813
- }
2814
- info(category, message, data) {
2815
- if (this.shouldLog("INFO")) {
2816
- console.log(this.formatMessage("INFO", category, message), data ?? "");
2817
- }
2818
- }
2819
- warn(category, message, data) {
2820
- if (this.shouldLog("WARN")) {
2821
- console.warn(this.formatMessage("WARN", category, message), data ?? "");
2822
- }
2823
- }
2824
- error(category, message, data) {
2825
- const msg = this.formatMessage("ERROR", category, message);
2826
- if (this.shouldLog("ERROR")) {
2827
- console.error(msg, data ?? "");
2828
- }
2829
- const throwOnError = this.options.throwOnError ?? false;
2830
- if (throwOnError) {
2831
- const errorMsg = data ? `${message}: ${JSON.stringify(data)}` : message;
2832
- throw new Error(errorMsg);
2833
- }
2834
- }
2835
- /**
2836
- * Start timing an operation
2837
- */
2838
- time(label) {
2839
- this.timers.set(label, Date.now());
2840
- }
2841
- /**
2842
- * End timing an operation and log the duration
2843
- */
2844
- timeEnd(category, label) {
2845
- const start = this.timers.get(label);
2846
- if (start === void 0) {
2847
- this.warn(category, `Timer '${label}' does not exist`);
2848
- return;
2849
- }
2850
- const duration = Date.now() - start;
2851
- this.timers.delete(label);
2852
- this.debug(category, `${label} completed`, { durationMs: duration });
2853
- return duration;
2854
- }
2855
- /**
2856
- * Wrap an async function with automatic timing
2857
- */
2858
- async timed(category, label, fn) {
2859
- this.time(label);
2860
- try {
2861
- return await fn();
2862
- } finally {
2863
- this.timeEnd(category, label);
2864
- }
2865
- }
2866
- };
2867
- function createDebugLogger(options) {
2868
- return new DebugLogger(options);
2869
- }
2870
- var testLogger = createDebugLogger({
2871
- enabled: process.env.E2E_DEBUG === "true",
2872
- prefix: "E2E",
2873
- throwOnError: false
2874
- });
2875
-
2876
3134
  // dist/utils/git.js
2877
3135
  import { simpleGit } from "simple-git";
2878
3136
  async function detectHeadBranch(repoRoot, fallback = "main") {
@@ -2886,7 +3144,7 @@ async function detectHeadBranch(repoRoot, fallback = "main") {
2886
3144
  }
2887
3145
 
2888
3146
  // dist/git-manager.js
2889
- var log = createDebugLogger({ prefix: "GitManager" });
3147
+ var log2 = createDebugLogger({ prefix: "GitManager" });
2890
3148
  var remoteInitLocks = /* @__PURE__ */ new Map();
2891
3149
  var GitManager = class _GitManager {
2892
3150
  constructor(options, gitOptions) {
@@ -2897,14 +3155,14 @@ var GitManager = class _GitManager {
2897
3155
  this.git.env("GIT_CEILING_DIRECTORIES", path10.dirname(this.repoPath));
2898
3156
  }
2899
3157
  static async cloneRepo(remoteUrl, targetPath, baseBranch = "main") {
2900
- log.debug("git", "Cloning repository", {
3158
+ log2.debug("git", "Cloning repository", {
2901
3159
  remoteUrl,
2902
3160
  targetPath,
2903
3161
  baseBranch
2904
3162
  });
2905
3163
  const git = simpleGit2();
2906
3164
  await git.clone(remoteUrl, targetPath, ["--branch", baseBranch, "--single-branch"]);
2907
- log.debug("git", "Clone complete");
3165
+ log2.debug("git", "Clone complete");
2908
3166
  }
2909
3167
  /**
2910
3168
  * Initializes a local bare git repository to simulate a remote for dev mode.
@@ -2920,32 +3178,32 @@ var GitManager = class _GitManager {
2920
3178
  static async ensureLocalSimulatedRemote(options) {
2921
3179
  const existingLock = remoteInitLocks.get(options.remotePath);
2922
3180
  if (existingLock) {
2923
- log.debug("git", "Waiting for existing remote initialization", {
3181
+ log2.debug("git", "Waiting for existing remote initialization", {
2924
3182
  remotePath: options.remotePath
2925
3183
  });
2926
3184
  await existingLock;
2927
3185
  try {
2928
3186
  const stat = await fs9.stat(options.remotePath);
2929
3187
  if (stat.isDirectory()) {
2930
- log.debug("git", "Remote exists after waiting for lock");
3188
+ log2.debug("git", "Remote exists after waiting for lock");
2931
3189
  return;
2932
3190
  }
2933
3191
  } catch (err) {
2934
3192
  if (!isNotFoundError(err))
2935
3193
  throw err;
2936
- log.debug("git", "Remote does not exist after lock, will retry initialization");
3194
+ log2.debug("git", "Remote does not exist after lock, will retry initialization");
2937
3195
  }
2938
3196
  }
2939
- const lockPromise = log.timed("git", "ensureLocalSimulatedRemote", async () => {
3197
+ const lockPromise = log2.timed("git", "ensureLocalSimulatedRemote", async () => {
2940
3198
  try {
2941
- log.debug("git", "Initializing local simulated remote", {
3199
+ log2.debug("git", "Initializing local simulated remote", {
2942
3200
  remotePath: options.remotePath,
2943
3201
  baseBranch: options.baseBranch
2944
3202
  });
2945
3203
  try {
2946
3204
  const stat = await fs9.stat(options.remotePath);
2947
3205
  if (stat.isDirectory()) {
2948
- log.debug("git", "Remote already exists, skipping");
3206
+ log2.debug("git", "Remote already exists, skipping");
2949
3207
  return;
2950
3208
  }
2951
3209
  } catch (err) {
@@ -2968,8 +3226,8 @@ var GitManager = class _GitManager {
2968
3226
  }
2969
3227
  let hasCommits = false;
2970
3228
  try {
2971
- const log3 = await sourceGit.log(["-1"]);
2972
- hasCommits = log3.total > 0;
3229
+ const log4 = await sourceGit.log(["-1"]);
3230
+ hasCommits = log4.total > 0;
2973
3231
  } catch {
2974
3232
  hasCommits = false;
2975
3233
  }
@@ -2980,7 +3238,7 @@ var GitManager = class _GitManager {
2980
3238
  if (!branches.all.includes(options.baseBranch)) {
2981
3239
  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.`);
2982
3240
  }
2983
- log.debug("git", "Creating bare remote repository");
3241
+ log2.debug("git", "Creating bare remote repository");
2984
3242
  await fs9.mkdir(path10.dirname(options.remotePath), { recursive: true });
2985
3243
  await simpleGit2().raw([
2986
3244
  "init",
@@ -3020,7 +3278,7 @@ var GitManager = class _GitManager {
3020
3278
  } catch {
3021
3279
  }
3022
3280
  }
3023
- log.debug("git", "Remote initialization complete");
3281
+ log2.debug("git", "Remote initialization complete");
3024
3282
  } finally {
3025
3283
  remoteInitLocks.delete(options.remotePath);
3026
3284
  }
@@ -3089,7 +3347,7 @@ var GitManager = class _GitManager {
3089
3347
  try {
3090
3348
  const stat = await fs9.stat(config.autoDetectRemotePath);
3091
3349
  if (stat.isDirectory()) {
3092
- log.debug("git", "Auto-detected local remote", {
3350
+ log2.debug("git", "Auto-detected local remote", {
3093
3351
  path: config.autoDetectRemotePath
3094
3352
  });
3095
3353
  return config.autoDetectRemotePath;
@@ -3142,7 +3400,7 @@ var GitManager = class _GitManager {
3142
3400
  try {
3143
3401
  const stat = await fs9.stat(gitPath);
3144
3402
  if (stat.isDirectory()) {
3145
- log.debug("git", "Removing corrupt .git directory", {
3403
+ log2.debug("git", "Removing corrupt .git directory", {
3146
3404
  workspacePath: options.workspacePath
3147
3405
  });
3148
3406
  await fs9.rm(gitPath, { recursive: true });
@@ -3180,7 +3438,7 @@ var GitManager = class _GitManager {
3180
3438
  await git.git.addConfig("canopycms.managed", "true");
3181
3439
  await git.git.addConfig("user.name", options.gitBotAuthorName);
3182
3440
  await git.git.addConfig("user.email", options.gitBotAuthorEmail);
3183
- log.debug("git", "Marked workspace as CanopyCMS-managed", {
3441
+ log2.debug("git", "Marked workspace as CanopyCMS-managed", {
3184
3442
  workspacePath: options.workspacePath
3185
3443
  });
3186
3444
  if (!justCloned) {
@@ -3343,13 +3601,13 @@ var GitManager = class _GitManager {
3343
3601
  }
3344
3602
  const lines = content.split("\n");
3345
3603
  if (lines.some((line) => line.trim() === pattern)) {
3346
- log.debug("git", "Pattern already in .git/info/exclude", { pattern });
3604
+ log2.debug("git", "Pattern already in .git/info/exclude", { pattern });
3347
3605
  return;
3348
3606
  }
3349
3607
  const needsLeadingNewline = content.length > 0 && !content.endsWith("\n");
3350
3608
  const newContent = content + (needsLeadingNewline ? "\n" : "") + pattern + "\n";
3351
3609
  await fs9.writeFile(excludePath, newContent, "utf-8");
3352
- log.debug("git", "Added pattern to .git/info/exclude", { pattern });
3610
+ log2.debug("git", "Added pattern to .git/info/exclude", { pattern });
3353
3611
  }
3354
3612
  /**
3355
3613
  * Create an orphan branch for settings (permissions/groups).
@@ -3363,10 +3621,10 @@ var GitManager = class _GitManager {
3363
3621
  * @param initialFiles - Files to commit to the new branch (e.g., { 'permissions.json': '{}', 'groups.json': '{}' })
3364
3622
  */
3365
3623
  async createOrphanSettingsBranch(branchName, initialFiles) {
3366
- log.debug("git", "Creating orphan settings branch", { branchName });
3624
+ log2.debug("git", "Creating orphan settings branch", { branchName });
3367
3625
  const branches = await this.git.branch();
3368
3626
  if (branches.all.includes(branchName)) {
3369
- log.debug("git", "Orphan branch already exists", { branchName });
3627
+ log2.debug("git", "Orphan branch already exists", { branchName });
3370
3628
  await this.git.checkout(branchName);
3371
3629
  return;
3372
3630
  }
@@ -3382,19 +3640,19 @@ var GitManager = class _GitManager {
3382
3640
  await this.git.add(filePath);
3383
3641
  }
3384
3642
  await this.git.commit("Initialize settings branch", ["--allow-empty"]);
3385
- log.debug("git", "Orphan settings branch created", { branchName });
3643
+ log2.debug("git", "Orphan settings branch created", { branchName });
3386
3644
  }
3387
3645
  };
3388
3646
 
3389
3647
  // dist/branch-workspace.js
3390
- var log2 = createDebugLogger({ prefix: "BranchWorkspace" });
3648
+ var log3 = createDebugLogger({ prefix: "BranchWorkspace" });
3391
3649
  var workspaceInitLocks = /* @__PURE__ */ new Map();
3392
3650
  var BranchWorkspaceManager = class {
3393
3651
  constructor(config) {
3394
3652
  this.config = config;
3395
3653
  }
3396
3654
  async ensureGitWorkspace(options) {
3397
- return log2.timed("workspace", "ensureGitWorkspace", async () => {
3655
+ return log3.timed("workspace", "ensureGitWorkspace", async () => {
3398
3656
  const existingLock = workspaceInitLocks.get(options.branchRoot);
3399
3657
  if (existingLock) {
3400
3658
  await existingLock;
@@ -3402,7 +3660,7 @@ var BranchWorkspaceManager = class {
3402
3660
  }
3403
3661
  const lockPromise = (async () => {
3404
3662
  try {
3405
- log2.debug("workspace", "Ensuring git workspace", {
3663
+ log3.debug("workspace", "Ensuring git workspace", {
3406
3664
  branchName: options.branchName,
3407
3665
  mode: options.mode
3408
3666
  });
@@ -3531,7 +3789,8 @@ async function generateAIContentFiles(options) {
3531
3789
  store,
3532
3790
  flatSchema,
3533
3791
  contentRoot: contentRootName,
3534
- config: aiConfig
3792
+ config: aiConfig,
3793
+ entryLinkUrl: config.entryLinkUrl
3535
3794
  });
3536
3795
  const absoluteOutputDir = path11.resolve(outputDir) + path11.sep;
3537
3796
  let fileCount = 0;