canopycms 0.0.41 → 0.0.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/generate-ai-content.js +2 -9
- package/dist/cli/init.js +2 -9
- package/dist/cli/template-files/schemas.ts.template +20 -5
- package/dist/config/flatten.d.ts +1 -6
- package/dist/config/flatten.d.ts.map +1 -1
- package/dist/config/flatten.js +1 -23
- package/dist/config/flatten.js.map +1 -1
- package/dist/config/helpers.d.ts +10 -5
- package/dist/config/helpers.d.ts.map +1 -1
- package/dist/config/helpers.js +10 -5
- package/dist/config/helpers.js.map +1 -1
- package/dist/config/index.d.ts +2 -2
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +2 -2
- package/dist/config/index.js.map +1 -1
- package/dist/config/schemas/collection.d.ts +0 -53
- package/dist/config/schemas/collection.d.ts.map +1 -1
- package/dist/config/schemas/collection.js +0 -6
- package/dist/config/schemas/collection.js.map +1 -1
- package/dist/config/schemas/config.d.ts +1 -80
- package/dist/config/schemas/config.d.ts.map +1 -1
- package/dist/config/schemas/config.js +6 -5
- package/dist/config/schemas/config.js.map +1 -1
- package/dist/config/validation.d.ts +13 -6
- package/dist/config/validation.d.ts.map +1 -1
- package/dist/config/validation.js +65 -127
- package/dist/config/validation.js.map +1 -1
- package/dist/config-test.d.ts +1 -1
- package/dist/config-test.d.ts.map +1 -1
- package/dist/config-test.js.map +1 -1
- package/dist/content-tree.d.ts +85 -7
- package/dist/content-tree.d.ts.map +1 -1
- package/dist/content-tree.js +34 -9
- package/dist/content-tree.js.map +1 -1
- package/dist/context.d.ts +9 -3
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js.map +1 -1
- package/dist/entry-schema-registry.d.ts +54 -10
- package/dist/entry-schema-registry.d.ts.map +1 -1
- package/dist/entry-schema-registry.js +62 -10
- package/dist/entry-schema-registry.js.map +1 -1
- package/dist/entry-schema.d.ts +34 -0
- package/dist/entry-schema.d.ts.map +1 -1
- package/dist/entry-schema.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/server.d.ts +53 -3
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +50 -2
- package/dist/server.js.map +1 -1
- package/dist/worker/cms-worker.d.ts.map +1 -1
- package/dist/worker/cms-worker.js +5 -0
- package/dist/worker/cms-worker.js.map +1 -1
- package/package.json +6 -6
|
@@ -1,33 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Field-shape validation utilities for entry schemas.
|
|
3
|
+
*
|
|
4
|
+
* These helpers validate individual `EntrySchema` field arrays — they're invoked
|
|
5
|
+
* from `createEntrySchemaRegistry` so the canonical schema-authoring path runs
|
|
6
|
+
* the same checks. Exported so the test suite can exercise them directly.
|
|
3
7
|
*/
|
|
4
8
|
import type { CanopyConfig } from './types';
|
|
5
9
|
/**
|
|
6
10
|
* Recursively check that all select fields have options defined.
|
|
7
11
|
* Throws an error if a select field is missing options.
|
|
8
12
|
*/
|
|
9
|
-
export declare const ensureSelectFieldsHaveOptions: (
|
|
13
|
+
export declare const ensureSelectFieldsHaveOptions: (fields: unknown) => void;
|
|
10
14
|
/**
|
|
11
15
|
* Recursively check that all reference fields have at least one of `collections` or `entryTypes`.
|
|
12
16
|
* Throws an error if a reference field has neither.
|
|
13
17
|
*/
|
|
14
|
-
export declare const ensureReferenceFieldsHaveScope: (
|
|
18
|
+
export declare const ensureReferenceFieldsHaveScope: (fields: unknown) => void;
|
|
15
19
|
/**
|
|
16
20
|
* Validate that inline groups don't cause field name collisions within the same scope.
|
|
17
21
|
* Because inline groups flatten their children into the parent scope, a field name used
|
|
18
22
|
* in a group that also appears as a sibling field (or in another group) will silently
|
|
19
23
|
* overwrite data on read/write.
|
|
24
|
+
*
|
|
25
|
+
* Pass a label (typically the entry-type name from the registry) for clearer error messages.
|
|
20
26
|
*/
|
|
21
|
-
export declare const ensureNoFlattenedFieldNameCollisions: (
|
|
27
|
+
export declare const ensureNoFlattenedFieldNameCollisions: (fields: unknown, scopeLabel?: string) => void;
|
|
22
28
|
/**
|
|
23
29
|
* Validate that inline groups (type: 'group') only appear at the top level of entry
|
|
24
30
|
* schemas, not inside object or block fields. Groups inside complex fields would produce
|
|
25
31
|
* correct TypeScript types but broken editor rendering.
|
|
26
32
|
*/
|
|
27
|
-
export declare const ensureNoGroupsInsideComplexFields: (
|
|
33
|
+
export declare const ensureNoGroupsInsideComplexFields: (fields: unknown) => void;
|
|
28
34
|
/**
|
|
29
35
|
* Validate and normalize a CanopyConfig object.
|
|
30
|
-
* Performs Zod validation
|
|
36
|
+
* Performs Zod validation and normalizes paths. Field-shape validation for
|
|
37
|
+
* entry schemas runs at `createEntrySchemaRegistry` time.
|
|
31
38
|
*
|
|
32
39
|
* @param config - Raw configuration input
|
|
33
40
|
* @returns Validated and normalized CanopyConfig
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/config/validation.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/config/validation.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE3C;;;GAGG;AACH,eAAO,MAAM,6BAA6B,GAAI,QAAQ,OAAO,KAAG,IAoB/D,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,8BAA8B,GAAI,QAAQ,OAAO,KAAG,IA0BhE,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,oCAAoC,GAC/C,QAAQ,OAAO,EACf,mBAA2B,KAC1B,IA0DF,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,iCAAiC,GAAI,QAAQ,OAAO,KAAG,IA4BnE,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,GAAI,QAAQ,OAAO,KAAG,YAQtD,CAAA"}
|
|
@@ -1,111 +1,81 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Field-shape validation utilities for entry schemas.
|
|
3
|
+
*
|
|
4
|
+
* These helpers validate individual `EntrySchema` field arrays — they're invoked
|
|
5
|
+
* from `createEntrySchemaRegistry` so the canonical schema-authoring path runs
|
|
6
|
+
* the same checks. Exported so the test suite can exercise them directly.
|
|
3
7
|
*/
|
|
4
8
|
import { CanopyConfigSchema } from './schemas/config.js';
|
|
5
|
-
import { normalizePathValue
|
|
9
|
+
import { normalizePathValue } from './flatten.js';
|
|
6
10
|
/**
|
|
7
11
|
* Recursively check that all select fields have options defined.
|
|
8
12
|
* Throws an error if a select field is missing options.
|
|
9
13
|
*/
|
|
10
|
-
export const ensureSelectFieldsHaveOptions = (
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
throw new Error(`Select field "${fieldName}" requires options`);
|
|
19
|
-
}
|
|
20
|
-
if (f?.type === 'group') {
|
|
21
|
-
checkFields(f.fields);
|
|
22
|
-
}
|
|
23
|
-
if (f?.type === 'object') {
|
|
24
|
-
checkFields(f.fields);
|
|
25
|
-
}
|
|
26
|
-
if (f?.type === 'block' && Array.isArray(f.templates)) {
|
|
27
|
-
for (const template of f.templates) {
|
|
28
|
-
checkFields(template.fields);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
14
|
+
export const ensureSelectFieldsHaveOptions = (fields) => {
|
|
15
|
+
if (!Array.isArray(fields))
|
|
16
|
+
return;
|
|
17
|
+
for (const field of fields) {
|
|
18
|
+
const f = field;
|
|
19
|
+
if (f?.type === 'select' && (!Array.isArray(f.options) || f.options.length === 0)) {
|
|
20
|
+
const fieldName = f?.name ?? 'unknown';
|
|
21
|
+
throw new Error(`Select field "${fieldName}" requires options`);
|
|
31
22
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (Array.isArray(root.entries)) {
|
|
38
|
-
for (const entryType of root.entries) {
|
|
39
|
-
checkFields(entryType?.schema);
|
|
40
|
-
}
|
|
23
|
+
if (f?.type === 'group') {
|
|
24
|
+
ensureSelectFieldsHaveOptions(f.fields);
|
|
25
|
+
}
|
|
26
|
+
if (f?.type === 'object') {
|
|
27
|
+
ensureSelectFieldsHaveOptions(f.fields);
|
|
41
28
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
walkSchema(collection);
|
|
29
|
+
if (f?.type === 'block' && Array.isArray(f.templates)) {
|
|
30
|
+
for (const template of f.templates) {
|
|
31
|
+
ensureSelectFieldsHaveOptions(template.fields);
|
|
46
32
|
}
|
|
47
33
|
}
|
|
48
|
-
}
|
|
49
|
-
walkSchema(config?.schema);
|
|
34
|
+
}
|
|
50
35
|
};
|
|
51
36
|
/**
|
|
52
37
|
* Recursively check that all reference fields have at least one of `collections` or `entryTypes`.
|
|
53
38
|
* Throws an error if a reference field has neither.
|
|
54
39
|
*/
|
|
55
|
-
export const ensureReferenceFieldsHaveScope = (
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
throw new Error(`Reference field "${fieldName}" requires at least one of "collections" or "entryTypes"`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
if (f?.type === 'group') {
|
|
70
|
-
checkFields(f.fields);
|
|
71
|
-
}
|
|
72
|
-
if (f?.type === 'object') {
|
|
73
|
-
checkFields(f.fields);
|
|
74
|
-
}
|
|
75
|
-
if (f?.type === 'block' && Array.isArray(f.templates)) {
|
|
76
|
-
for (const template of f.templates) {
|
|
77
|
-
checkFields(template.fields);
|
|
78
|
-
}
|
|
40
|
+
export const ensureReferenceFieldsHaveScope = (fields) => {
|
|
41
|
+
if (!Array.isArray(fields))
|
|
42
|
+
return;
|
|
43
|
+
for (const field of fields) {
|
|
44
|
+
const f = field;
|
|
45
|
+
if (f?.type === 'reference') {
|
|
46
|
+
const hasCollections = Array.isArray(f.collections) && f.collections.length > 0;
|
|
47
|
+
const hasEntryTypes = Array.isArray(f.entryTypes) && f.entryTypes.length > 0;
|
|
48
|
+
if (!hasCollections && !hasEntryTypes) {
|
|
49
|
+
const fieldName = f?.name ?? 'unknown';
|
|
50
|
+
throw new Error(`Reference field "${fieldName}" requires at least one of "collections" or "entryTypes"`);
|
|
79
51
|
}
|
|
80
52
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (!root)
|
|
84
|
-
return;
|
|
85
|
-
if (Array.isArray(root.entries)) {
|
|
86
|
-
for (const entryType of root.entries) {
|
|
87
|
-
checkFields(entryType?.schema);
|
|
88
|
-
}
|
|
53
|
+
if (f?.type === 'group') {
|
|
54
|
+
ensureReferenceFieldsHaveScope(f.fields);
|
|
89
55
|
}
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
|
|
56
|
+
if (f?.type === 'object') {
|
|
57
|
+
ensureReferenceFieldsHaveScope(f.fields);
|
|
58
|
+
}
|
|
59
|
+
if (f?.type === 'block' && Array.isArray(f.templates)) {
|
|
60
|
+
for (const template of f.templates) {
|
|
61
|
+
ensureReferenceFieldsHaveScope(template.fields);
|
|
93
62
|
}
|
|
94
63
|
}
|
|
95
|
-
}
|
|
96
|
-
walkSchema(config?.schema);
|
|
64
|
+
}
|
|
97
65
|
};
|
|
98
66
|
/**
|
|
99
67
|
* Validate that inline groups don't cause field name collisions within the same scope.
|
|
100
68
|
* Because inline groups flatten their children into the parent scope, a field name used
|
|
101
69
|
* in a group that also appears as a sibling field (or in another group) will silently
|
|
102
70
|
* overwrite data on read/write.
|
|
71
|
+
*
|
|
72
|
+
* Pass a label (typically the entry-type name from the registry) for clearer error messages.
|
|
103
73
|
*/
|
|
104
|
-
export const ensureNoFlattenedFieldNameCollisions = (
|
|
74
|
+
export const ensureNoFlattenedFieldNameCollisions = (fields, scopeLabel = 'entry schema') => {
|
|
105
75
|
// Collect all effective field names at a scope level (groups are transparent)
|
|
106
|
-
const collectNamesAtScope = (
|
|
76
|
+
const collectNamesAtScope = (scopeFields) => {
|
|
107
77
|
const names = [];
|
|
108
|
-
for (const field of
|
|
78
|
+
for (const field of scopeFields) {
|
|
109
79
|
const f = field;
|
|
110
80
|
if (f?.type === 'group') {
|
|
111
81
|
names.push(...collectNamesAtScope(f.fields ?? []));
|
|
@@ -117,9 +87,9 @@ export const ensureNoFlattenedFieldNameCollisions = (config) => {
|
|
|
117
87
|
return names;
|
|
118
88
|
};
|
|
119
89
|
// Collect all object/block fields at a scope level (including those inside groups)
|
|
120
|
-
const collectComplexFields = (
|
|
90
|
+
const collectComplexFields = (scopeFields) => {
|
|
121
91
|
const result = [];
|
|
122
|
-
for (const field of
|
|
92
|
+
for (const field of scopeFields) {
|
|
123
93
|
const f = field;
|
|
124
94
|
if (f?.type === 'group') {
|
|
125
95
|
result.push(...collectComplexFields(f.fields ?? []));
|
|
@@ -130,57 +100,43 @@ export const ensureNoFlattenedFieldNameCollisions = (config) => {
|
|
|
130
100
|
}
|
|
131
101
|
return result;
|
|
132
102
|
};
|
|
133
|
-
const checkScope = (
|
|
134
|
-
if (!Array.isArray(
|
|
103
|
+
const checkScope = (scopeFields, label) => {
|
|
104
|
+
if (!Array.isArray(scopeFields))
|
|
135
105
|
return;
|
|
136
106
|
// Check for collisions at this scope (groups flattened in)
|
|
137
|
-
const names = collectNamesAtScope(
|
|
107
|
+
const names = collectNamesAtScope(scopeFields);
|
|
138
108
|
const seen = new Set();
|
|
139
109
|
for (const name of names) {
|
|
140
110
|
if (seen.has(name)) {
|
|
141
|
-
throw new Error(`Field name collision in ${
|
|
111
|
+
throw new Error(`Field name collision in ${label}: field "${name}" appears more than once. ` +
|
|
142
112
|
`Note: inline groups flatten their fields into the parent scope.`);
|
|
143
113
|
}
|
|
144
114
|
seen.add(name);
|
|
145
115
|
}
|
|
146
116
|
// Recurse into nested scopes (object fields and block templates have their own scope)
|
|
147
|
-
for (const f of collectComplexFields(
|
|
117
|
+
for (const f of collectComplexFields(scopeFields)) {
|
|
148
118
|
if (f.type === 'object') {
|
|
149
|
-
checkScope(f.fields, `${
|
|
119
|
+
checkScope(f.fields, `${label} > object "${f.name}"`);
|
|
150
120
|
}
|
|
151
121
|
else if (f.type === 'block' && Array.isArray(f.templates)) {
|
|
152
122
|
for (const template of f.templates) {
|
|
153
|
-
checkScope(template.fields, `${
|
|
123
|
+
checkScope(template.fields, `${label} > block "${f.name}" template "${template.name}"`);
|
|
154
124
|
}
|
|
155
125
|
}
|
|
156
126
|
}
|
|
157
127
|
};
|
|
158
|
-
|
|
159
|
-
if (!root)
|
|
160
|
-
return;
|
|
161
|
-
if (Array.isArray(root.entries)) {
|
|
162
|
-
for (const entryType of root.entries) {
|
|
163
|
-
checkScope(entryType?.schema, `entry type "${entryType.name}"`);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
if (Array.isArray(root.collections)) {
|
|
167
|
-
for (const collection of root.collections) {
|
|
168
|
-
walkSchema(collection);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
-
walkSchema(config?.schema);
|
|
128
|
+
checkScope(Array.isArray(fields) ? fields : undefined, scopeLabel);
|
|
173
129
|
};
|
|
174
130
|
/**
|
|
175
131
|
* Validate that inline groups (type: 'group') only appear at the top level of entry
|
|
176
132
|
* schemas, not inside object or block fields. Groups inside complex fields would produce
|
|
177
133
|
* correct TypeScript types but broken editor rendering.
|
|
178
134
|
*/
|
|
179
|
-
export const ensureNoGroupsInsideComplexFields = (
|
|
180
|
-
const checkFields = (
|
|
181
|
-
if (!Array.isArray(
|
|
135
|
+
export const ensureNoGroupsInsideComplexFields = (fields) => {
|
|
136
|
+
const checkFields = (scopeFields, parentType) => {
|
|
137
|
+
if (!Array.isArray(scopeFields))
|
|
182
138
|
return;
|
|
183
|
-
for (const field of
|
|
139
|
+
for (const field of scopeFields) {
|
|
184
140
|
const f = field;
|
|
185
141
|
if (f?.type === 'group') {
|
|
186
142
|
if (parentType) {
|
|
@@ -201,40 +157,22 @@ export const ensureNoGroupsInsideComplexFields = (config) => {
|
|
|
201
157
|
}
|
|
202
158
|
}
|
|
203
159
|
};
|
|
204
|
-
|
|
205
|
-
if (!root)
|
|
206
|
-
return;
|
|
207
|
-
if (Array.isArray(root.entries)) {
|
|
208
|
-
for (const entryType of root.entries) {
|
|
209
|
-
checkFields(entryType?.schema);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
if (Array.isArray(root.collections)) {
|
|
213
|
-
for (const collection of root.collections) {
|
|
214
|
-
walkSchema(collection);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
};
|
|
218
|
-
walkSchema(config?.schema);
|
|
160
|
+
checkFields(Array.isArray(fields) ? fields : undefined);
|
|
219
161
|
};
|
|
220
162
|
/**
|
|
221
163
|
* Validate and normalize a CanopyConfig object.
|
|
222
|
-
* Performs Zod validation
|
|
164
|
+
* Performs Zod validation and normalizes paths. Field-shape validation for
|
|
165
|
+
* entry schemas runs at `createEntrySchemaRegistry` time.
|
|
223
166
|
*
|
|
224
167
|
* @param config - Raw configuration input
|
|
225
168
|
* @returns Validated and normalized CanopyConfig
|
|
226
169
|
* @throws Error if validation fails
|
|
227
170
|
*/
|
|
228
171
|
export const validateCanopyConfig = (config) => {
|
|
229
|
-
ensureSelectFieldsHaveOptions(config);
|
|
230
|
-
ensureReferenceFieldsHaveScope(config);
|
|
231
|
-
ensureNoGroupsInsideComplexFields(config);
|
|
232
|
-
ensureNoFlattenedFieldNameCollisions(config);
|
|
233
172
|
const parsed = CanopyConfigSchema.parse(config);
|
|
234
173
|
const normalized = {
|
|
235
174
|
...parsed,
|
|
236
175
|
contentRoot: normalizePathValue(parsed.contentRoot ?? 'content'),
|
|
237
|
-
schema: parsed.schema ? normalizeSchemaPathsRoot(parsed.schema) : undefined,
|
|
238
176
|
};
|
|
239
177
|
return normalized;
|
|
240
178
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/config/validation.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/config/validation.ts"],"names":[],"mappings":"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"}
|
package/dist/config-test.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CanopyConfig, CanopyConfigInput, RootCollectionConfig } from './config';
|
|
2
2
|
import { type CanopyServices, type CreateCanopyServicesOptions } from './services';
|
|
3
|
-
type TestConfigInput = Omit<CanopyConfigInput, 'gitBotAuthorName' | 'gitBotAuthorEmail'
|
|
3
|
+
type TestConfigInput = Omit<CanopyConfigInput, 'gitBotAuthorName' | 'gitBotAuthorEmail'> & {
|
|
4
4
|
schema: RootCollectionConfig;
|
|
5
5
|
} & Partial<Pick<CanopyConfigInput, 'gitBotAuthorName' | 'gitBotAuthorEmail'>>;
|
|
6
6
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-test.d.ts","sourceRoot":"","sources":["../src/config-test.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAErF,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,2BAA2B,EACjC,MAAM,YAAY,CAAA;AAOnB,KAAK,eAAe,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"config-test.d.ts","sourceRoot":"","sources":["../src/config-test.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAErF,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,2BAA2B,EACjC,MAAM,YAAY,CAAA;AAOnB,KAAK,eAAe,GAAG,IAAI,CAAC,iBAAiB,EAAE,kBAAkB,GAAG,mBAAmB,CAAC,GAAG;IACzF,MAAM,EAAE,oBAAoB,CAAA;CAC7B,GAAG,OAAO,CAAC,IAAI,CAAC,iBAAiB,EAAE,kBAAkB,GAAG,mBAAmB,CAAC,CAAC,CAAA;AAE9E;;;GAGG;AACH,eAAO,MAAM,sBAAsB,GACjC,QAAQ,eAAe,EACvB,YAAY,OAAO,CAAC,iBAAiB,CAAC,KACrC,YAQF,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,eAAe,EACvB,UAAU,2BAA2B,KACpC,OAAO,CAAC,cAAc,CAkBxB,CAAA"}
|
package/dist/config-test.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-test.js","sourceRoot":"","sources":["../src/config-test.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAC5D,OAAO,EACL,wBAAwB,GAGzB,MAAM,YAAY,CAAA;AAEnB,MAAM,eAAe,GAAG;IACtB,gBAAgB,EAAE,oBAAoB;IACtC,iBAAiB,EAAE,4BAA4B;CAChD,CAAA;
|
|
1
|
+
{"version":3,"file":"config-test.js","sourceRoot":"","sources":["../src/config-test.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAC5D,OAAO,EACL,wBAAwB,GAGzB,MAAM,YAAY,CAAA;AAEnB,MAAM,eAAe,GAAG;IACtB,gBAAgB,EAAE,oBAAoB;IACtC,iBAAiB,EAAE,4BAA4B;CAChD,CAAA;AAMD;;;GAGG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CACpC,MAAuB,EACvB,SAAsC,EACxB,EAAE;IAChB,0EAA0E;IAC1E,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,mBAAmB,EAAE,GAAG,MAAM,CAAA;IAC1D,OAAO,kBAAkB,CAAC;QACxB,GAAG,eAAe;QAClB,GAAG,mBAAmB;QACtB,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC;KACrB,CAAC,CAAC,MAAM,CAAA;AACX,CAAC,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,EACrC,MAAuB,EACvB,OAAqC,EACZ,EAAE;IAC3B,MAAM,YAAY,GAAG,sBAAsB,CAAC,MAAM,CAAC,CAAA;IACnD,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,WAAW,CAAC,CAAA;IAEzE,+DAA+D;IAC/D,MAAM,qBAAqB,GAAG;QAC5B,SAAS,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;YACtB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,UAAU;SACX,CAAC;QACF,UAAU,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC;KAC3B,CAAA;IAED,OAAO,wBAAwB,CAAC,YAAY,EAAE;QAC5C,GAAG,OAAO;QACV,iBAAiB,EACf,qBAAoF;KACvF,CAAC,CAAA;AACJ,CAAC,CAAA"}
|
package/dist/content-tree.d.ts
CHANGED
|
@@ -9,14 +9,82 @@
|
|
|
9
9
|
* adopter via the `extract` callback.
|
|
10
10
|
*/
|
|
11
11
|
import type { FlatSchemaItem, ContentFormat } from './config';
|
|
12
|
+
/**
|
|
13
|
+
* Adopter-supplied registry mapping entry type names (as they appear in
|
|
14
|
+
* filenames: `partner.index.yaml` → `'partner'`) to their data shapes.
|
|
15
|
+
*
|
|
16
|
+
* Pair with `TypeFromEntrySchema<typeof yourSchema>` to derive shapes from
|
|
17
|
+
* schemas you've already defined — no redeclaration needed.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { defineEntrySchema, type TypeFromEntrySchema } from 'canopycms'
|
|
22
|
+
*
|
|
23
|
+
* const partnerSchema = defineEntrySchema([
|
|
24
|
+
* { name: 'name', type: 'string', isTitle: true },
|
|
25
|
+
* { name: 'tagline', type: 'string' },
|
|
26
|
+
* ])
|
|
27
|
+
* const docSchema = defineEntrySchema([
|
|
28
|
+
* { name: 'title', type: 'string' },
|
|
29
|
+
* ])
|
|
30
|
+
*
|
|
31
|
+
* interface MyEntries {
|
|
32
|
+
* partner: TypeFromEntrySchema<typeof partnerSchema>
|
|
33
|
+
* doc: TypeFromEntrySchema<typeof docSchema>
|
|
34
|
+
* }
|
|
35
|
+
*
|
|
36
|
+
* const canopy = await getCanopyForBuild()
|
|
37
|
+
* await canopy.buildContentTree<NavFields, MyEntries>({
|
|
38
|
+
* extract: (data, meta) => {
|
|
39
|
+
* if (meta.kind === 'collection' && meta.indexEntry?.entryType === 'partner') {
|
|
40
|
+
* // meta.indexEntry.data is narrowed to PartnerContent
|
|
41
|
+
* return { name: meta.indexEntry.data.name }
|
|
42
|
+
* }
|
|
43
|
+
* return { name: '' }
|
|
44
|
+
* },
|
|
45
|
+
* })
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export type EntryTypeMap = Record<string, object>;
|
|
49
|
+
/**
|
|
50
|
+
* Default TEntryTypes when an adopter hasn't supplied one.
|
|
51
|
+
* The index signature preserves the loose shape — `meta.indexEntry.data`
|
|
52
|
+
* stays `Record<string, unknown>`, useful for unstructured access without
|
|
53
|
+
* opting in to the discriminated-union pattern. Exported so callers wrapping
|
|
54
|
+
* `buildContentTree` (e.g. CanopyBuildContext) share the same default.
|
|
55
|
+
*/
|
|
56
|
+
export type DefaultEntryTypes = {
|
|
57
|
+
[entryType: string]: Record<string, unknown>;
|
|
58
|
+
};
|
|
12
59
|
/** Metadata passed to the extract callback. */
|
|
13
|
-
export interface ContentTreeExtractMeta {
|
|
60
|
+
export interface ContentTreeExtractMeta<TEntryTypes = DefaultEntryTypes> {
|
|
14
61
|
kind: 'collection' | 'entry';
|
|
15
62
|
logicalPath: LogicalPath;
|
|
16
|
-
/**
|
|
17
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Entry type name — present when kind is 'entry'.
|
|
65
|
+
* Narrows to a literal union when TEntryTypes is supplied.
|
|
66
|
+
*/
|
|
67
|
+
entryType?: keyof TEntryTypes & string;
|
|
18
68
|
/** Content format — present when kind is 'entry'. */
|
|
19
69
|
format?: ContentFormat;
|
|
70
|
+
/**
|
|
71
|
+
* The entry with slug 'index' inside a collection, when present.
|
|
72
|
+
* Represents the collection's "identity" under the directory-as-page pattern
|
|
73
|
+
* (e.g., a partner's metadata for /data-catalog/<partner>/, a section landing
|
|
74
|
+
* for /docs/<section>/). Only populated when kind === 'collection' AND the
|
|
75
|
+
* collection contains an entry with slug 'index'. Undefined for collections
|
|
76
|
+
* at the maxDepth cap (entries aren't loaded there).
|
|
77
|
+
*
|
|
78
|
+
* When TEntryTypes is supplied, this becomes a discriminated union: narrow
|
|
79
|
+
* on `indexEntry.entryType` and `data` is typed accordingly.
|
|
80
|
+
*/
|
|
81
|
+
indexEntry?: {
|
|
82
|
+
[K in keyof TEntryTypes & string]: {
|
|
83
|
+
entryType: K;
|
|
84
|
+
format: ContentFormat;
|
|
85
|
+
data: TEntryTypes[K];
|
|
86
|
+
};
|
|
87
|
+
}[keyof TEntryTypes & string];
|
|
20
88
|
}
|
|
21
89
|
import type { LogicalPath, ContentId, Slug } from './paths/types';
|
|
22
90
|
export interface ContentTreeNode<T = unknown> {
|
|
@@ -46,18 +114,28 @@ export interface ContentTreeNode<T = unknown> {
|
|
|
46
114
|
/** Children (entries + subcollections interleaved by order array) */
|
|
47
115
|
children?: ContentTreeNode<T>[];
|
|
48
116
|
}
|
|
49
|
-
export interface BuildContentTreeOptions<T = unknown> {
|
|
117
|
+
export interface BuildContentTreeOptions<T = unknown, TEntryTypes = DefaultEntryTypes> {
|
|
50
118
|
/** Starting collection path. Defaults to content root. */
|
|
51
119
|
rootPath?: string;
|
|
52
120
|
/**
|
|
53
121
|
* Extract typed custom fields from each node's raw data.
|
|
54
122
|
* For entries: data is frontmatter + body (md/mdx) or parsed JSON.
|
|
55
123
|
* For collections: data is `{ name, label }` from the schema.
|
|
124
|
+
*
|
|
125
|
+
* Supply TEntryTypes to get narrowed access to `meta.indexEntry.data`
|
|
126
|
+
* via discriminated-union narrowing on `meta.indexEntry.entryType`.
|
|
127
|
+
*
|
|
128
|
+
* Note: extract should be a pure mapping. It may be invoked on entry nodes
|
|
129
|
+
* that `filter` later removes (the tree-walk extracts then filters), so any
|
|
130
|
+
* side effects (logging, counters, populating an external index) will see
|
|
131
|
+
* those rejected nodes too.
|
|
56
132
|
*/
|
|
57
|
-
extract?: (data: Record<string, unknown>, meta: ContentTreeExtractMeta) => T;
|
|
133
|
+
extract?: (data: Record<string, unknown>, meta: ContentTreeExtractMeta<TEntryTypes>) => T;
|
|
58
134
|
/**
|
|
59
135
|
* Filter: return false to exclude a node and its descendants.
|
|
60
|
-
* Runs after extract, so `fields` is available.
|
|
136
|
+
* Runs after extract, so `fields` is available. Rejecting a collection
|
|
137
|
+
* short-circuits descendant traversal (no recursion into child collections
|
|
138
|
+
* or entry reads beneath the rejected node).
|
|
61
139
|
*/
|
|
62
140
|
filter?: (node: ContentTreeNode<T>) => boolean;
|
|
63
141
|
/** Custom URL path builder. Default: strips content root prefix, collapses index entries to parent path, lowercases. */
|
|
@@ -79,5 +157,5 @@ export interface BuildContentTreeOptions<T = unknown> {
|
|
|
79
157
|
* @param contentRootName - The content root name (e.g. "content")
|
|
80
158
|
* @param options - Tree-building options
|
|
81
159
|
*/
|
|
82
|
-
export declare function buildContentTree<T = unknown>(branchRoot: string, flatSchema: FlatSchemaItem[], contentRootName: string, options?: BuildContentTreeOptions<T>): Promise<ContentTreeNode<T>[]>;
|
|
160
|
+
export declare function buildContentTree<T = unknown, TEntryTypes = DefaultEntryTypes>(branchRoot: string, flatSchema: FlatSchemaItem[], contentRootName: string, options?: BuildContentTreeOptions<T, TEntryTypes>): Promise<ContentTreeNode<T>[]>;
|
|
83
161
|
//# sourceMappingURL=content-tree.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-tree.d.ts","sourceRoot":"","sources":["../src/content-tree.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAE7D,+CAA+C;AAC/C,MAAM,WAAW,sBAAsB;
|
|
1
|
+
{"version":3,"file":"content-tree.d.ts","sourceRoot":"","sources":["../src/content-tree.ts"],"names":[],"mappings":"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"}
|
package/dist/content-tree.js
CHANGED
|
@@ -67,7 +67,6 @@ export async function buildContentTree(branchRoot, flatSchema, contentRootName,
|
|
|
67
67
|
if (!rootCollection)
|
|
68
68
|
return [];
|
|
69
69
|
const buildNode = async (collection, depth) => {
|
|
70
|
-
// Build collection node (before children, so filter can reject early)
|
|
71
70
|
const collectionData = {
|
|
72
71
|
name: collection.name,
|
|
73
72
|
label: collection.label,
|
|
@@ -82,23 +81,47 @@ export async function buildContentTree(branchRoot, flatSchema, contentRootName,
|
|
|
82
81
|
label: collection.label,
|
|
83
82
|
},
|
|
84
83
|
};
|
|
84
|
+
// maxDepth branch: don't load entries (no children would be exposed anyway).
|
|
85
|
+
// Consequence: extract sees no indexEntry on depth-capped collections.
|
|
86
|
+
if (maxDepth !== undefined && depth >= maxDepth) {
|
|
87
|
+
if (extract) {
|
|
88
|
+
node.fields = extract(collectionData, {
|
|
89
|
+
kind: 'collection',
|
|
90
|
+
logicalPath: collection.logicalPath,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
if (filter && !filter(node))
|
|
94
|
+
return null;
|
|
95
|
+
return node;
|
|
96
|
+
}
|
|
97
|
+
// Load this collection's entries first (needed for indexEntry detection).
|
|
98
|
+
// We deliberately do NOT parallelize the child-collection recursion here:
|
|
99
|
+
// if `filter` rejects this collection, returning early short-circuits all
|
|
100
|
+
// descendant I/O — preserving the pre-existing "filter prunes whole subtree"
|
|
101
|
+
// optimization that the directory-as-page reorder would otherwise lose.
|
|
102
|
+
const entries = await listCollectionEntries(branchRoot, collection);
|
|
103
|
+
// Surface the 'index' entry (when present) to extract via meta.
|
|
104
|
+
// 'index' is the same magic slug Canopy uses in defaultBuildPath to collapse
|
|
105
|
+
// /foo/index URLs to /foo/, keeping conventions consistent. If a collection
|
|
106
|
+
// contains multiple slug==='index' entries (only possible via hand-edited or
|
|
107
|
+
// merged content — the write path forbids it), the first by filename order
|
|
108
|
+
// wins.
|
|
109
|
+
// The cast bridges runtime string keys to the parametric discriminated union;
|
|
110
|
+
// the adopter narrows on `indexEntry.entryType` to get the typed `data`.
|
|
111
|
+
const idx = entries.find((e) => e.slug === 'index');
|
|
112
|
+
const indexEntry = (idx ? { entryType: idx.entryType, format: idx.format, data: idx.data } : undefined);
|
|
85
113
|
if (extract) {
|
|
86
114
|
node.fields = extract(collectionData, {
|
|
87
115
|
kind: 'collection',
|
|
88
116
|
logicalPath: collection.logicalPath,
|
|
117
|
+
indexEntry,
|
|
89
118
|
});
|
|
90
119
|
}
|
|
91
120
|
if (filter && !filter(node))
|
|
92
121
|
return null;
|
|
93
|
-
//
|
|
94
|
-
if (maxDepth !== undefined && depth >= maxDepth)
|
|
95
|
-
return node;
|
|
96
|
-
// Gather child collections and entries in parallel
|
|
122
|
+
// Now recurse into child collections (after filter has had a chance to prune)
|
|
97
123
|
const childCollections = childrenByParent.get(collection.logicalPath) ?? [];
|
|
98
|
-
const
|
|
99
|
-
Promise.all(childCollections.map((child) => buildNode(child, depth + 1))),
|
|
100
|
-
listCollectionEntries(branchRoot, collection),
|
|
101
|
-
]);
|
|
124
|
+
const childCollectionNodes = await Promise.all(childCollections.map((child) => buildNode(child, depth + 1)));
|
|
102
125
|
// Build entry nodes
|
|
103
126
|
const entryNodes = [];
|
|
104
127
|
for (const entry of entries) {
|
|
@@ -146,6 +169,8 @@ function buildEntryNode(entry, buildPath, extract) {
|
|
|
146
169
|
},
|
|
147
170
|
};
|
|
148
171
|
if (extract) {
|
|
172
|
+
// Runtime gives us the raw string entryType; cast to the parametric key
|
|
173
|
+
// type so callers who supply TEntryTypes get the discriminated-union narrowing.
|
|
149
174
|
node.fields = extract(entry.data, {
|
|
150
175
|
kind: 'entry',
|
|
151
176
|
logicalPath: entry.logicalPath,
|