canopycms 0.0.26 → 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 (80) 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/api/content.d.ts +6 -0
  9. package/dist/api/content.d.ts.map +1 -1
  10. package/dist/api/content.js +18 -1
  11. package/dist/api/content.js.map +1 -1
  12. package/dist/api/schema.d.ts +20 -20
  13. package/dist/build/generate-ai-content.d.ts.map +1 -1
  14. package/dist/build/generate-ai-content.js +1 -0
  15. package/dist/build/generate-ai-content.js.map +1 -1
  16. package/dist/cli/generate-ai-content.js +262 -123
  17. package/dist/cli/init.js +2 -1
  18. package/dist/client.d.ts +2 -0
  19. package/dist/client.d.ts.map +1 -1
  20. package/dist/client.js +1 -0
  21. package/dist/client.js.map +1 -1
  22. package/dist/config/helpers.d.ts.map +1 -1
  23. package/dist/config/helpers.js +2 -1
  24. package/dist/config/helpers.js.map +1 -1
  25. package/dist/config/schemas/collection.d.ts +6 -6
  26. package/dist/config/schemas/config.d.ts +10 -6
  27. package/dist/config/schemas/config.d.ts.map +1 -1
  28. package/dist/config/schemas/config.js +1 -0
  29. package/dist/config/schemas/config.js.map +1 -1
  30. package/dist/config/types.d.ts +6 -1
  31. package/dist/config/types.d.ts.map +1 -1
  32. package/dist/config/types.js.map +1 -1
  33. package/dist/content-reader.d.ts +2 -0
  34. package/dist/content-reader.d.ts.map +1 -1
  35. package/dist/content-reader.js +13 -4
  36. package/dist/content-reader.js.map +1 -1
  37. package/dist/editor/CanopyEditor.d.ts.map +1 -1
  38. package/dist/editor/CanopyEditor.js +1 -1
  39. package/dist/editor/CanopyEditor.js.map +1 -1
  40. package/dist/editor/Editor.d.ts +1 -0
  41. package/dist/editor/Editor.d.ts.map +1 -1
  42. package/dist/editor/Editor.js +32 -9
  43. package/dist/editor/Editor.js.map +1 -1
  44. package/dist/editor/fields/MarkdownField.d.ts.map +1 -1
  45. package/dist/editor/fields/MarkdownField.js +8 -2
  46. package/dist/editor/fields/MarkdownField.js.map +1 -1
  47. package/dist/editor/fields/entry-link/EntryLinkContext.d.ts +20 -0
  48. package/dist/editor/fields/entry-link/EntryLinkContext.d.ts.map +1 -0
  49. package/dist/editor/fields/entry-link/EntryLinkContext.js +12 -0
  50. package/dist/editor/fields/entry-link/EntryLinkContext.js.map +1 -0
  51. package/dist/editor/fields/entry-link/InsertEntryLink.d.ts +16 -0
  52. package/dist/editor/fields/entry-link/InsertEntryLink.d.ts.map +1 -0
  53. package/dist/editor/fields/entry-link/InsertEntryLink.js +62 -0
  54. package/dist/editor/fields/entry-link/InsertEntryLink.js.map +1 -0
  55. package/dist/editor/fields/entry-link/index.d.ts +3 -0
  56. package/dist/editor/fields/entry-link/index.d.ts.map +1 -0
  57. package/dist/editor/fields/entry-link/index.js +3 -0
  58. package/dist/editor/fields/entry-link/index.js.map +1 -0
  59. package/dist/editor/hooks/useEntryLinkResolution.d.ts +26 -0
  60. package/dist/editor/hooks/useEntryLinkResolution.d.ts.map +1 -0
  61. package/dist/editor/hooks/useEntryLinkResolution.js +96 -0
  62. package/dist/editor/hooks/useEntryLinkResolution.js.map +1 -0
  63. package/dist/entry-link-resolver.d.ts +67 -0
  64. package/dist/entry-link-resolver.d.ts.map +1 -0
  65. package/dist/entry-link-resolver.js +226 -0
  66. package/dist/entry-link-resolver.js.map +1 -0
  67. package/dist/schema/schema-store.d.ts +10 -10
  68. package/dist/server.d.ts +3 -0
  69. package/dist/server.d.ts.map +1 -1
  70. package/dist/server.js +2 -0
  71. package/dist/server.js.map +1 -1
  72. package/dist/utils/entry-url.d.ts +21 -0
  73. package/dist/utils/entry-url.d.ts.map +1 -0
  74. package/dist/utils/entry-url.js +41 -0
  75. package/dist/utils/entry-url.js.map +1 -0
  76. package/dist/validation/entry-link-validator.d.ts +27 -0
  77. package/dist/validation/entry-link-validator.d.ts.map +1 -0
  78. package/dist/validation/entry-link-validator.js +49 -0
  79. package/dist/validation/entry-link-validator.js.map +1 -0
  80. package/package.json +1 -1
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Entry link resolution for body content.
3
+ *
4
+ * Resolves `entry:CONTENT_ID` patterns in markdown/MDX body text to actual URL paths.
5
+ * This extends the reference-by-ID pattern (already used for structured reference fields)
6
+ * to inline links in content bodies.
7
+ *
8
+ * Syntax:
9
+ * [Link text](entry:vh2WdhwAFiSL)
10
+ * [Link text](entry:vh2WdhwAFiSL#section-heading)
11
+ *
12
+ * Resolution skips fenced code blocks and inline code spans to avoid
13
+ * corrupting code examples.
14
+ */
15
+ import { createDebugLogger } from './utils/debug.js';
16
+ import { computeEntryUrl } from './utils/entry-url.js';
17
+ const log = createDebugLogger({ prefix: 'EntryLinks' });
18
+ /**
19
+ * Base58 alphabet pattern (matches content IDs).
20
+ * Excludes ambiguous characters: 0, O, I, l
21
+ */
22
+ /** Base58 alphabet character class (excludes ambiguous: 0, O, I, l). */
23
+ export const BASE58_CHAR = '[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]';
24
+ /**
25
+ * Pattern matching `entry:CONTENT_ID` with optional anchor fragment.
26
+ * Uses the `g` flag — safe to share when used only with `.replace()`.
27
+ */
28
+ export const ENTRY_LINK_PATTERN = new RegExp(`entry:(${BASE58_CHAR}{12})(#[^\\s)>"']*)?`, 'g');
29
+ /**
30
+ * Compute a URL path from an entry's location in the content tree.
31
+ * Delegates to the shared `computeEntryUrl` utility.
32
+ */
33
+ export function resolveEntryUrl(location, contentRoot) {
34
+ return computeEntryUrl(location.collection ?? '', location.slug ?? '', contentRoot);
35
+ }
36
+ /**
37
+ * Replace `entry:CONTENT_ID` patterns in text with resolved URL paths.
38
+ *
39
+ * Skips code blocks (fenced ``` and inline `code`) to avoid corrupting
40
+ * code examples that mention the entry: syntax.
41
+ *
42
+ * Missing IDs are replaced with "#" (dead link) and logged as warnings.
43
+ * Anchor fragments are preserved: entry:ID#heading => /path#heading
44
+ */
45
+ export function resolveEntryLinksInText(text, idIndex, contentRoot, customResolver) {
46
+ // Split text into protected regions (code blocks/spans) and resolvable regions
47
+ const parts = splitByCodeRegions(text);
48
+ return parts
49
+ .map((part) => {
50
+ if (part.isCode)
51
+ return part.text;
52
+ return part.text.replace(ENTRY_LINK_PATTERN, (_match, id, anchor) => {
53
+ const location = idIndex.findById(id);
54
+ if (!location || location.type !== 'entry' || !location.collection || !location.slug) {
55
+ log.warn('resolve', `Entry link target not found: entry:${id}`);
56
+ return anchor ?? '#';
57
+ }
58
+ let url;
59
+ if (customResolver) {
60
+ url = customResolver({
61
+ collection: location.collection,
62
+ slug: location.slug,
63
+ id,
64
+ });
65
+ }
66
+ else {
67
+ url = resolveEntryUrl(location, contentRoot);
68
+ }
69
+ return `${url}${anchor ?? ''}`;
70
+ });
71
+ })
72
+ .join('');
73
+ }
74
+ /** Quick-check pattern for early bail-out (no `g` flag, safe for `.test()`). */
75
+ export const ENTRY_LINK_QUICK_CHECK = /entry:[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{12}/;
76
+ /**
77
+ * Recursively resolve entry:ID patterns in all string values of a data object.
78
+ *
79
+ * This handles nested objects, arrays, and mixed structures — important for
80
+ * JSON entries where markdown fields live inside nested objects (e.g., hero.body).
81
+ *
82
+ * Returns the same object reference if nothing changed (structural sharing).
83
+ */
84
+ export function resolveEntryLinksInData(data, idIndex, contentRoot, customResolver) {
85
+ if (typeof data === 'string') {
86
+ if (!ENTRY_LINK_QUICK_CHECK.test(data))
87
+ return data;
88
+ return resolveEntryLinksInText(data, idIndex, contentRoot, customResolver);
89
+ }
90
+ if (Array.isArray(data)) {
91
+ let changed = false;
92
+ const result = data.map((item) => {
93
+ const resolved = resolveEntryLinksInData(item, idIndex, contentRoot, customResolver);
94
+ if (resolved !== item)
95
+ changed = true;
96
+ return resolved;
97
+ });
98
+ return changed ? result : data;
99
+ }
100
+ if (data != null && typeof data === 'object') {
101
+ let changed = false;
102
+ const result = {};
103
+ for (const [key, value] of Object.entries(data)) {
104
+ const resolved = resolveEntryLinksInData(value, idIndex, contentRoot, customResolver);
105
+ result[key] = resolved;
106
+ if (resolved !== value)
107
+ changed = true;
108
+ }
109
+ return changed ? result : data;
110
+ }
111
+ return data;
112
+ }
113
+ /**
114
+ * Extract all entry link IDs from text (for validation, not resolution).
115
+ * Returns IDs found in entry:ID patterns, skipping code blocks.
116
+ */
117
+ export function extractEntryLinkIds(text) {
118
+ const parts = splitByCodeRegions(text);
119
+ const results = [];
120
+ for (const part of parts) {
121
+ if (part.isCode)
122
+ continue;
123
+ const regex = new RegExp(ENTRY_LINK_PATTERN.source, 'g');
124
+ let match;
125
+ while ((match = regex.exec(part.text)) !== null) {
126
+ results.push({
127
+ id: match[1],
128
+ anchor: match[2] || undefined,
129
+ });
130
+ }
131
+ }
132
+ return results;
133
+ }
134
+ /**
135
+ * Split text into alternating code/non-code regions.
136
+ *
137
+ * Handles:
138
+ * - Fenced code blocks (``` or ~~~), including with language tags
139
+ * - Inline code spans (`code` and ``code``)
140
+ *
141
+ * This ensures entry:ID inside code is never resolved.
142
+ */
143
+ function splitByCodeRegions(text) {
144
+ const parts = [];
145
+ let current = '';
146
+ let i = 0;
147
+ while (i < text.length) {
148
+ // Check for fenced code block (``` or ~~~)
149
+ if ((text[i] === '`' || text[i] === '~') &&
150
+ i + 2 < text.length &&
151
+ text[i + 1] === text[i] &&
152
+ text[i + 2] === text[i]) {
153
+ const fence = text[i];
154
+ // Count fence length (could be ``` or ```` etc.)
155
+ let fenceLen = 0;
156
+ while (i + fenceLen < text.length && text[i + fenceLen] === fence)
157
+ fenceLen++;
158
+ // Find end of opening fence line
159
+ const lineEnd = text.indexOf('\n', i + fenceLen);
160
+ if (lineEnd === -1) {
161
+ // No newline — rest of text is code block
162
+ if (current)
163
+ parts.push({ text: current, isCode: false });
164
+ parts.push({ text: text.slice(i), isCode: true });
165
+ return parts;
166
+ }
167
+ // Find closing fence
168
+ const closingPattern = fence.repeat(fenceLen);
169
+ let closeStart = lineEnd + 1;
170
+ let found = false;
171
+ while (closeStart < text.length) {
172
+ const nextNewline = text.indexOf('\n', closeStart);
173
+ const lineContent = nextNewline === -1 ? text.slice(closeStart) : text.slice(closeStart, nextNewline);
174
+ if (lineContent.trim().startsWith(closingPattern)) {
175
+ const endPos = nextNewline === -1 ? text.length : nextNewline + 1;
176
+ if (current)
177
+ parts.push({ text: current, isCode: false });
178
+ current = '';
179
+ parts.push({ text: text.slice(i, endPos), isCode: true });
180
+ i = endPos;
181
+ found = true;
182
+ break;
183
+ }
184
+ if (nextNewline === -1)
185
+ break;
186
+ closeStart = nextNewline + 1;
187
+ }
188
+ if (!found) {
189
+ // Unclosed code block — treat rest as code
190
+ if (current)
191
+ parts.push({ text: current, isCode: false });
192
+ parts.push({ text: text.slice(i), isCode: true });
193
+ return parts;
194
+ }
195
+ continue;
196
+ }
197
+ // Check for inline code span (` or ``)
198
+ if (text[i] === '`') {
199
+ // Count opening backticks
200
+ let ticks = 0;
201
+ while (i + ticks < text.length && text[i + ticks] === '`')
202
+ ticks++;
203
+ // Find matching closing backticks
204
+ const closer = '`'.repeat(ticks);
205
+ const closeIdx = text.indexOf(closer, i + ticks);
206
+ if (closeIdx !== -1) {
207
+ if (current)
208
+ parts.push({ text: current, isCode: false });
209
+ current = '';
210
+ parts.push({ text: text.slice(i, closeIdx + ticks), isCode: true });
211
+ i = closeIdx + ticks;
212
+ continue;
213
+ }
214
+ // No closing backticks — treat as regular text
215
+ current += text.slice(i, i + ticks);
216
+ i += ticks;
217
+ continue;
218
+ }
219
+ current += text[i];
220
+ i++;
221
+ }
222
+ if (current)
223
+ parts.push({ text: current, isCode: false });
224
+ return parts;
225
+ }
226
+ //# sourceMappingURL=entry-link-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry-link-resolver.js","sourceRoot":"","sources":["../src/entry-link-resolver.ts"],"names":[],"mappings":"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"}
@@ -57,15 +57,15 @@ declare const entryTypeInputSchema: z.ZodObject<{
57
57
  name: string;
58
58
  format: "mdx" | "md" | "json";
59
59
  schema: string;
60
- label?: string | undefined;
61
60
  default?: boolean | undefined;
61
+ label?: string | undefined;
62
62
  maxItems?: number | undefined;
63
63
  }, {
64
64
  name: string;
65
65
  format: "mdx" | "md" | "json";
66
66
  schema: string;
67
- label?: string | undefined;
68
67
  default?: boolean | undefined;
68
+ label?: string | undefined;
69
69
  maxItems?: number | undefined;
70
70
  }>;
71
71
  declare const createCollectionInputSchema: z.ZodObject<{
@@ -83,15 +83,15 @@ declare const createCollectionInputSchema: z.ZodObject<{
83
83
  name: string;
84
84
  format: "mdx" | "md" | "json";
85
85
  schema: string;
86
- label?: string | undefined;
87
86
  default?: boolean | undefined;
87
+ label?: string | undefined;
88
88
  maxItems?: number | undefined;
89
89
  }, {
90
90
  name: string;
91
91
  format: "mdx" | "md" | "json";
92
92
  schema: string;
93
- label?: string | undefined;
94
93
  default?: boolean | undefined;
94
+ label?: string | undefined;
95
95
  maxItems?: number | undefined;
96
96
  }>, "many">;
97
97
  }, "strip", z.ZodTypeAny, {
@@ -99,8 +99,8 @@ declare const createCollectionInputSchema: z.ZodObject<{
99
99
  name: string;
100
100
  format: "mdx" | "md" | "json";
101
101
  schema: string;
102
- label?: string | undefined;
103
102
  default?: boolean | undefined;
103
+ label?: string | undefined;
104
104
  maxItems?: number | undefined;
105
105
  }[];
106
106
  name: string;
@@ -111,8 +111,8 @@ declare const createCollectionInputSchema: z.ZodObject<{
111
111
  name: string;
112
112
  format: "mdx" | "md" | "json";
113
113
  schema: string;
114
- label?: string | undefined;
115
114
  default?: boolean | undefined;
115
+ label?: string | undefined;
116
116
  maxItems?: number | undefined;
117
117
  }[];
118
118
  name: string;
@@ -126,14 +126,14 @@ declare const updateCollectionInputSchema: z.ZodObject<{
126
126
  order: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
127
127
  }, "strip", z.ZodTypeAny, {
128
128
  name?: string | undefined;
129
+ slug?: string | undefined;
129
130
  label?: string | undefined;
130
131
  order?: string[] | undefined;
131
- slug?: string | undefined;
132
132
  }, {
133
133
  name?: string | undefined;
134
+ slug?: string | undefined;
134
135
  label?: string | undefined;
135
136
  order?: string[] | undefined;
136
- slug?: string | undefined;
137
137
  }>;
138
138
  declare const updateEntryTypeInputSchema: z.ZodObject<{
139
139
  label: z.ZodOptional<z.ZodString>;
@@ -142,16 +142,16 @@ declare const updateEntryTypeInputSchema: z.ZodObject<{
142
142
  default: z.ZodOptional<z.ZodBoolean>;
143
143
  maxItems: z.ZodOptional<z.ZodNumber>;
144
144
  }, "strip", z.ZodTypeAny, {
145
+ default?: boolean | undefined;
145
146
  label?: string | undefined;
146
147
  format?: "mdx" | "md" | "json" | undefined;
147
148
  schema?: string | undefined;
148
- default?: boolean | undefined;
149
149
  maxItems?: number | undefined;
150
150
  }, {
151
+ default?: boolean | undefined;
151
152
  label?: string | undefined;
152
153
  format?: "mdx" | "md" | "json" | undefined;
153
154
  schema?: string | undefined;
154
- default?: boolean | undefined;
155
155
  maxItems?: number | undefined;
156
156
  }>;
157
157
  export declare class SchemaOps {
package/dist/server.d.ts CHANGED
@@ -14,4 +14,7 @@ export { buildContentTree } from './content-tree';
14
14
  export type { ContentTreeNode, BuildContentTreeOptions, ContentTreeExtractMeta, } from './content-tree';
15
15
  export { listEntries } from './content-listing';
16
16
  export type { ListEntriesItem, ListEntriesOptions } from './content-listing';
17
+ export { resolveEntryUrl, resolveEntryLinksInText, resolveEntryLinksInData, extractEntryLinkIds, } from './entry-link-resolver';
18
+ export type { EntryLinkUrlResolver } from './entry-link-resolver';
19
+ export { computeEntryUrl } from './utils/entry-url';
17
20
  //# sourceMappingURL=server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAA;AAChC,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA;AACzB,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACpD,cAAc,wBAAwB,CAAA;AACtC,cAAc,oBAAoB,CAAA;AAClC,cAAc,iBAAiB,CAAA;AAC/B,OAAO,EACL,uBAAuB,EACvB,2BAA2B,EAC3B,wBAAwB,EACxB,aAAa,GACd,MAAM,UAAU,CAAA;AACjB,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAClE,OAAO,EAAE,yBAAyB,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAA;AAChG,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,MAAM,CAAA;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACjD,YAAY,EACV,eAAe,EACf,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,YAAY,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAA;AAChC,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA;AACzB,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACpD,cAAc,wBAAwB,CAAA;AACtC,cAAc,oBAAoB,CAAA;AAClC,cAAc,iBAAiB,CAAA;AAC/B,OAAO,EACL,uBAAuB,EACvB,2BAA2B,EAC3B,wBAAwB,EACxB,aAAa,GACd,MAAM,UAAU,CAAA;AACjB,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAClE,OAAO,EAAE,yBAAyB,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAA;AAChG,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,MAAM,CAAA;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACjD,YAAY,EACV,eAAe,EACf,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,YAAY,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAC5E,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA"}
package/dist/server.js CHANGED
@@ -11,4 +11,6 @@ export { createEntrySchemaRegistry, validateEntrySchemaRegistry } from './entry-
11
11
  export { generateId, isValidId } from './id.js';
12
12
  export { buildContentTree } from './content-tree.js';
13
13
  export { listEntries } from './content-listing.js';
14
+ export { resolveEntryUrl, resolveEntryLinksInText, resolveEntryLinksInData, extractEntryLinkIds, } from './entry-link-resolver.js';
15
+ export { computeEntryUrl } from './utils/entry-url.js';
14
16
  //# sourceMappingURL=server.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAA;AAChC,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA;AACzB,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACpD,cAAc,wBAAwB,CAAA;AACtC,cAAc,oBAAoB,CAAA;AAClC,cAAc,iBAAiB,CAAA;AAC/B,OAAO,EACL,uBAAuB,EACvB,2BAA2B,EAC3B,wBAAwB,EACxB,aAAa,GACd,MAAM,UAAU,CAAA;AAEjB,OAAO,EAAE,yBAAyB,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAA;AAChG,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,MAAM,CAAA;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAMjD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAA;AAChC,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA;AACzB,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACpD,cAAc,wBAAwB,CAAA;AACtC,cAAc,oBAAoB,CAAA;AAClC,cAAc,iBAAiB,CAAA;AAC/B,OAAO,EACL,uBAAuB,EACvB,2BAA2B,EAC3B,wBAAwB,EACxB,aAAa,GACd,MAAM,UAAU,CAAA;AAEjB,OAAO,EAAE,yBAAyB,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAA;AAChG,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,MAAM,CAAA;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAMjD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAE/C,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,uBAAuB,CAAA;AAE9B,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Shared entry URL computation — used by both server and client code.
3
+ *
4
+ * This module has NO server-only dependencies (no node:fs, etc.)
5
+ * so it can be safely imported into browser bundles.
6
+ */
7
+ /**
8
+ * Compute a URL path from an entry's collection path and slug.
9
+ *
10
+ * Logic:
11
+ * - Strip the contentRoot prefix (e.g., "content/") from the collection path
12
+ * - Append the slug (unless it's "index", which collapses to the parent path)
13
+ * - Always returns a path starting with "/"
14
+ *
15
+ * Examples:
16
+ * ("content/posts", "hello-world", "content") => "/posts/hello-world"
17
+ * ("content/docs/api", "index", "content") => "/docs/api"
18
+ * ("content", "index", "content") => "/"
19
+ */
20
+ export declare function computeEntryUrl(collection: string, slug: string, contentRoot: string): string;
21
+ //# sourceMappingURL=entry-url.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry-url.d.ts","sourceRoot":"","sources":["../../src/utils/entry-url.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAsB7F"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Shared entry URL computation — used by both server and client code.
3
+ *
4
+ * This module has NO server-only dependencies (no node:fs, etc.)
5
+ * so it can be safely imported into browser bundles.
6
+ */
7
+ import { trimSlashes } from '../paths/normalize.js';
8
+ /**
9
+ * Compute a URL path from an entry's collection path and slug.
10
+ *
11
+ * Logic:
12
+ * - Strip the contentRoot prefix (e.g., "content/") from the collection path
13
+ * - Append the slug (unless it's "index", which collapses to the parent path)
14
+ * - Always returns a path starting with "/"
15
+ *
16
+ * Examples:
17
+ * ("content/posts", "hello-world", "content") => "/posts/hello-world"
18
+ * ("content/docs/api", "index", "content") => "/docs/api"
19
+ * ("content", "index", "content") => "/"
20
+ */
21
+ export function computeEntryUrl(collection, slug, contentRoot) {
22
+ const root = trimSlashes(contentRoot);
23
+ // Strip contentRoot prefix
24
+ let stripped = collection;
25
+ if (root && collection.startsWith(`${root}/`)) {
26
+ stripped = collection.slice(root.length + 1);
27
+ }
28
+ else if (collection === root) {
29
+ stripped = '';
30
+ }
31
+ // Build URL segments
32
+ const segments = stripped.split('/').filter(Boolean);
33
+ // Append slug unless it's "index" (index entries collapse to parent)
34
+ if (slug && slug !== 'index') {
35
+ segments.push(slug);
36
+ }
37
+ const path = segments.length > 0 ? `/${segments.join('/')}` : '/';
38
+ // Lowercase to match content-listing.ts and content-tree.ts URL conventions
39
+ return path.toLowerCase();
40
+ }
41
+ //# sourceMappingURL=entry-url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry-url.js","sourceRoot":"","sources":["../../src/utils/entry-url.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAEhD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,eAAe,CAAC,UAAkB,EAAE,IAAY,EAAE,WAAmB;IACnF,MAAM,IAAI,GAAG,WAAW,CAAC,WAAW,CAAC,CAAA;IAErC,2BAA2B;IAC3B,IAAI,QAAQ,GAAG,UAAU,CAAA;IACzB,IAAI,IAAI,IAAI,UAAU,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,CAAC,EAAE,CAAC;QAC9C,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IAC9C,CAAC;SAAM,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;QAC/B,QAAQ,GAAG,EAAE,CAAA;IACf,CAAC;IAED,qBAAqB;IACrB,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IAEpD,qEAAqE;IACrE,IAAI,IAAI,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;QAC7B,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACrB,CAAC;IAED,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAA;IACjE,4EAA4E;IAC5E,OAAO,IAAI,CAAC,WAAW,EAAE,CAAA;AAC3B,CAAC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * EntryLinkValidator validates that entry:ID patterns in body/markdown fields
3
+ * reference existing entries.
4
+ *
5
+ * Returns warnings (not errors) — saves are never blocked by broken entry links.
6
+ * This parallels ReferenceValidator but operates on inline links in text content
7
+ * rather than structured reference fields.
8
+ */
9
+ import type { ContentIdIndex } from '../content-id-index';
10
+ import type { FieldConfig } from '../config';
11
+ export interface EntryLinkWarning {
12
+ field: string;
13
+ fieldPath: string;
14
+ id: string;
15
+ message: string;
16
+ }
17
+ export interface EntryLinkValidationResult {
18
+ warnings: EntryLinkWarning[];
19
+ }
20
+ /**
21
+ * Validate entry links in body/markdown/mdx fields of the provided data.
22
+ *
23
+ * Scans all markdown, mdx, and rich-text fields for entry:ID patterns
24
+ * and checks that each referenced ID exists in the content index.
25
+ */
26
+ export declare function validateEntryLinks(data: Record<string, unknown>, schema: readonly FieldConfig[], idIndex: ContentIdIndex, bodyContent?: string): EntryLinkValidationResult;
27
+ //# sourceMappingURL=entry-link-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry-link-validator.d.ts","sourceRoot":"","sources":["../../src/validation/entry-link-validator.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAI5C,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,gBAAgB,EAAE,CAAA;CAC7B;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,EAAE,SAAS,WAAW,EAAE,EAC9B,OAAO,EAAE,cAAc,EACvB,WAAW,CAAC,EAAE,MAAM,GACnB,yBAAyB,CAoB3B"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * EntryLinkValidator validates that entry:ID patterns in body/markdown fields
3
+ * reference existing entries.
4
+ *
5
+ * Returns warnings (not errors) — saves are never blocked by broken entry links.
6
+ * This parallels ReferenceValidator but operates on inline links in text content
7
+ * rather than structured reference fields.
8
+ */
9
+ import { extractEntryLinkIds } from '../entry-link-resolver.js';
10
+ import { findFieldsByType } from './field-traversal.js';
11
+ /**
12
+ * Validate entry links in body/markdown/mdx fields of the provided data.
13
+ *
14
+ * Scans all markdown, mdx, and rich-text fields for entry:ID patterns
15
+ * and checks that each referenced ID exists in the content index.
16
+ */
17
+ export function validateEntryLinks(data, schema, idIndex, bodyContent) {
18
+ const warnings = [];
19
+ // Check body content (for md/mdx entries, body is separate from data)
20
+ if (bodyContent) {
21
+ checkText(bodyContent, 'body', 'body', idIndex, warnings);
22
+ }
23
+ // Check markdown/mdx/rich-text fields in structured data
24
+ const markdownTypes = ['markdown', 'mdx', 'rich-text'];
25
+ for (const fieldType of markdownTypes) {
26
+ const contexts = findFieldsByType(schema, data, fieldType);
27
+ for (const ctx of contexts) {
28
+ if (typeof ctx.value === 'string' && ctx.value) {
29
+ checkText(ctx.value, ctx.field.name, ctx.path, idIndex, warnings);
30
+ }
31
+ }
32
+ }
33
+ return { warnings };
34
+ }
35
+ function checkText(text, fieldName, fieldPath, idIndex, warnings) {
36
+ const links = extractEntryLinkIds(text);
37
+ for (const link of links) {
38
+ const location = idIndex.findById(link.id);
39
+ if (!location || location.type !== 'entry') {
40
+ warnings.push({
41
+ field: fieldName,
42
+ fieldPath,
43
+ id: link.id,
44
+ message: `Entry link target not found: entry:${link.id}`,
45
+ });
46
+ }
47
+ }
48
+ }
49
+ //# sourceMappingURL=entry-link-validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry-link-validator.js","sourceRoot":"","sources":["../../src/validation/entry-link-validator.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAapD;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAChC,IAA6B,EAC7B,MAA8B,EAC9B,OAAuB,EACvB,WAAoB;IAEpB,MAAM,QAAQ,GAAuB,EAAE,CAAA;IAEvC,sEAAsE;IACtE,IAAI,WAAW,EAAE,CAAC;QAChB,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAA;IAC3D,CAAC;IAED,yDAAyD;IACzD,MAAM,aAAa,GAAG,CAAC,UAAU,EAAE,KAAK,EAAE,WAAW,CAAU,CAAA;IAC/D,KAAK,MAAM,SAAS,IAAI,aAAa,EAAE,CAAC;QACtC,MAAM,QAAQ,GAAG,gBAAgB,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,CAAC,CAAA;QAC1D,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;YAC3B,IAAI,OAAO,GAAG,CAAC,KAAK,KAAK,QAAQ,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;gBAC/C,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAA;YACnE,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,CAAA;AACrB,CAAC;AAED,SAAS,SAAS,CAChB,IAAY,EACZ,SAAiB,EACjB,SAAiB,EACjB,OAAuB,EACvB,QAA4B;IAE5B,MAAM,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAA;IAEvC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QAC1C,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC3C,QAAQ,CAAC,IAAI,CAAC;gBACZ,KAAK,EAAE,SAAS;gBAChB,SAAS;gBACT,EAAE,EAAE,IAAI,CAAC,EAAE;gBACX,OAAO,EAAE,sCAAsC,IAAI,CAAC,EAAE,EAAE;aACzD,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "//": "@codemirror/language, @lezer/highlight: workaround — @mdxeditor/editor uses cm6-theme-basic-light which peer-requires these but mdxeditor doesn't declare them as dependencies",
3
3
  "name": "canopycms",
4
- "version": "0.0.26",
4
+ "version": "0.0.27",
5
5
  "description": "CanopyCMS core package: schema-driven content, branch-aware editing, and editor UI for Next.js.",
6
6
  "license": "MIT",
7
7
  "repository": {