multisite-cms-mcp 1.2.4 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +5 -6
- package/dist/tools/get-conversion-guide.d.ts.map +1 -1
- package/dist/tools/get-conversion-guide.js +134 -295
- package/dist/tools/get-example.d.ts.map +1 -1
- package/dist/tools/get-example.js +166 -409
- package/dist/tools/get-schema.d.ts +1 -1
- package/dist/tools/get-schema.d.ts.map +1 -1
- package/dist/tools/get-schema.js +130 -450
- package/dist/tools/get-tenant-schema.d.ts +9 -3
- package/dist/tools/get-tenant-schema.d.ts.map +1 -1
- package/dist/tools/get-tenant-schema.js +16 -4
- package/dist/tools/sync-schema.d.ts +1 -1
- package/dist/tools/sync-schema.d.ts.map +1 -1
- package/dist/tools/sync-schema.js +51 -97
- package/dist/tools/validate-manifest.d.ts.map +1 -1
- package/dist/tools/validate-manifest.js +26 -83
- package/dist/tools/validate-package.d.ts.map +1 -1
- package/dist/tools/validate-package.js +46 -41
- package/dist/tools/validate-template.d.ts +2 -2
- package/dist/tools/validate-template.d.ts.map +1 -1
- package/dist/tools/validate-template.js +67 -201
- package/package.json +1 -1
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Fetches the complete schema for a specific project including:
|
|
3
|
-
* -
|
|
4
|
-
* -
|
|
5
|
-
* -
|
|
3
|
+
* - All custom collections with their fields
|
|
4
|
+
* - Field tokens, types, and descriptions for AI context
|
|
5
|
+
* - Template patterns and example usage
|
|
6
|
+
*
|
|
7
|
+
* Each field includes:
|
|
8
|
+
* - Field Name (display name)
|
|
9
|
+
* - Token (the exact syntax to use in templates)
|
|
10
|
+
* - Type (text, richText, image, etc.)
|
|
11
|
+
* - Description (help text for context)
|
|
6
12
|
*
|
|
7
13
|
* Uses stored credentials or triggers device flow for authentication.
|
|
8
14
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-tenant-schema.d.ts","sourceRoot":"","sources":["../../src/tools/get-tenant-schema.ts"],"names":[],"mappings":"AA2EA
|
|
1
|
+
{"version":3,"file":"get-tenant-schema.d.ts","sourceRoot":"","sources":["../../src/tools/get-tenant-schema.ts"],"names":[],"mappings":"AA2EA;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAmFxE"}
|
|
@@ -38,9 +38,15 @@ async function resolveProjectId(projectIdentifier) {
|
|
|
38
38
|
}
|
|
39
39
|
/**
|
|
40
40
|
* Fetches the complete schema for a specific project including:
|
|
41
|
-
* -
|
|
42
|
-
* -
|
|
43
|
-
* -
|
|
41
|
+
* - All custom collections with their fields
|
|
42
|
+
* - Field tokens, types, and descriptions for AI context
|
|
43
|
+
* - Template patterns and example usage
|
|
44
|
+
*
|
|
45
|
+
* Each field includes:
|
|
46
|
+
* - Field Name (display name)
|
|
47
|
+
* - Token (the exact syntax to use in templates)
|
|
48
|
+
* - Type (text, richText, image, etc.)
|
|
49
|
+
* - Description (help text for context)
|
|
44
50
|
*
|
|
45
51
|
* Uses stored credentials or triggers device flow for authentication.
|
|
46
52
|
*
|
|
@@ -137,7 +143,13 @@ The API returned a successful response but the prompt data was missing.
|
|
|
137
143
|
|
|
138
144
|
**Project ID:** \`${tenantId}\`
|
|
139
145
|
|
|
140
|
-
|
|
146
|
+
This schema is specific to this project and includes all collections with their fields.
|
|
147
|
+
|
|
148
|
+
**Field Table Format:**
|
|
149
|
+
- **Field Name** - Display name for the field
|
|
150
|
+
- **Token** - The exact token to use in templates (use triple braces for richText)
|
|
151
|
+
- **Type** - Field type (text, richText, image, url, boolean, number, date, select, relation)
|
|
152
|
+
- **Description** - Help text explaining what the field is for
|
|
141
153
|
|
|
142
154
|
---
|
|
143
155
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-schema.d.ts","sourceRoot":"","sources":["../../src/tools/sync-schema.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAYH,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,UAAU,kBAAkB;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,aAAa,EAAE,CAAC;CAC1B;AAED,UAAU,WAAW;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"sync-schema.d.ts","sourceRoot":"","sources":["../../src/tools/sync-schema.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAYH,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,UAAU,kBAAkB;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,aAAa,EAAE,CAAC;CAC1B;AAED,UAAU,WAAW;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,aAAa,EAAE,CAAC;CACzB;AAED,UAAU,eAAe;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,kBAAkB,EAAE,CAAC;IACnC,WAAW,CAAC,EAAE,WAAW,EAAE,CAAC;CAC7B;AAgJD;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAmSxE"}
|
|
@@ -11,7 +11,6 @@ const api_client_1 = require("../lib/api-client");
|
|
|
11
11
|
const device_flow_1 = require("../lib/device-flow");
|
|
12
12
|
const get_field_types_1 = require("./get-field-types");
|
|
13
13
|
// ============ Constants ============
|
|
14
|
-
const BUILTIN_COLLECTION_TYPES = ['blogs', 'authors', 'team', 'downloads'];
|
|
15
14
|
const VALID_FIELD_TYPES = get_field_types_1.AVAILABLE_FIELD_TYPES.map(ft => ft.value);
|
|
16
15
|
const AUTH_REQUIRED_MESSAGE = `# Authentication Required
|
|
17
16
|
|
|
@@ -95,26 +94,14 @@ async function resolveProjectId(projectIdentifier) {
|
|
|
95
94
|
};
|
|
96
95
|
}
|
|
97
96
|
/**
|
|
98
|
-
* Fetch existing
|
|
97
|
+
* Fetch existing collections for a project
|
|
99
98
|
*/
|
|
100
|
-
async function
|
|
101
|
-
// Fetch custom collections
|
|
99
|
+
async function fetchExistingCollections(tenantId) {
|
|
102
100
|
const collectionsRes = await (0, api_client_1.apiRequest)('/api/collections', { tenantId });
|
|
103
101
|
if ((0, api_client_1.isApiError)(collectionsRes)) {
|
|
104
102
|
return { error: `Failed to fetch collections: ${collectionsRes.error}` };
|
|
105
103
|
}
|
|
106
|
-
|
|
107
|
-
const builtinRes = await (0, api_client_1.apiRequest)('/api/cms/builtin-collections', { tenantId });
|
|
108
|
-
const builtinFields = {};
|
|
109
|
-
if (!(0, api_client_1.isApiError)(builtinRes)) {
|
|
110
|
-
for (const bc of builtinRes.data) {
|
|
111
|
-
builtinFields[bc.type] = bc.fields.map(f => ({ slug: f.slug, name: f.name, type: f.type }));
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return {
|
|
115
|
-
collections: collectionsRes.data,
|
|
116
|
-
builtinFields,
|
|
117
|
-
};
|
|
104
|
+
return collectionsRes.data;
|
|
118
105
|
}
|
|
119
106
|
// ============ Main Function ============
|
|
120
107
|
/**
|
|
@@ -172,8 +159,9 @@ Use \`get_field_types\` to see available field types.
|
|
|
172
159
|
allValidationErrors.push(`fieldsToAdd entry missing collectionSlug`);
|
|
173
160
|
continue;
|
|
174
161
|
}
|
|
175
|
-
|
|
176
|
-
|
|
162
|
+
// Note: isBuiltin is deprecated and ignored - all collections are now custom
|
|
163
|
+
if (group.isBuiltin) {
|
|
164
|
+
allValidationErrors.push(`isBuiltin is no longer supported. All collections are custom collections. Use the collection slug directly.`);
|
|
177
165
|
}
|
|
178
166
|
if (!group.fields || group.fields.length === 0) {
|
|
179
167
|
allValidationErrors.push(`fieldsToAdd for "${group.collectionSlug}" has no fields`);
|
|
@@ -204,27 +192,26 @@ ${resolved.error}
|
|
|
204
192
|
`;
|
|
205
193
|
}
|
|
206
194
|
const { tenantId } = resolved;
|
|
207
|
-
// Fetch existing
|
|
208
|
-
const
|
|
209
|
-
if ('error' in
|
|
195
|
+
// Fetch existing collections
|
|
196
|
+
const existingResult = await fetchExistingCollections(tenantId);
|
|
197
|
+
if ('error' in existingResult) {
|
|
210
198
|
// Check if auth error
|
|
211
|
-
if (
|
|
199
|
+
if (existingResult.error.includes('401') || existingResult.error.includes('auth')) {
|
|
212
200
|
const authResult = await (0, device_flow_1.ensureAuthenticated)();
|
|
213
201
|
if (!authResult.authenticated) {
|
|
214
202
|
return AUTH_REQUIRED_MESSAGE;
|
|
215
203
|
}
|
|
216
204
|
// Retry
|
|
217
|
-
const retry = await
|
|
205
|
+
const retry = await fetchExistingCollections(tenantId);
|
|
218
206
|
if ('error' in retry) {
|
|
219
207
|
return `# Error\n\n${retry.error}`;
|
|
220
208
|
}
|
|
221
|
-
Object.assign(existingSchema, retry);
|
|
222
209
|
}
|
|
223
210
|
else {
|
|
224
|
-
return `# Error\n\n${
|
|
211
|
+
return `# Error\n\n${existingResult.error}`;
|
|
225
212
|
}
|
|
226
213
|
}
|
|
227
|
-
const
|
|
214
|
+
const existingCollections = Array.isArray(existingResult) ? existingResult : [];
|
|
228
215
|
// Track results
|
|
229
216
|
const results = [];
|
|
230
217
|
const created = { collections: 0, fields: 0 };
|
|
@@ -235,14 +222,14 @@ ${resolved.error}
|
|
|
235
222
|
// Check if collection already exists
|
|
236
223
|
const existing = existingCollections.find(c => c.slug.toLowerCase() === col.slug.toLowerCase());
|
|
237
224
|
if (existing) {
|
|
238
|
-
results.push(
|
|
225
|
+
results.push(`Skipped collection "${col.slug}" (already exists)`);
|
|
239
226
|
skipped.collections++;
|
|
240
227
|
// But still try to add any new fields
|
|
241
228
|
if (col.fields && col.fields.length > 0) {
|
|
242
229
|
for (const field of col.fields) {
|
|
243
230
|
const fieldExists = existing.fields.some(f => f.slug.toLowerCase() === field.slug.toLowerCase());
|
|
244
231
|
if (fieldExists) {
|
|
245
|
-
results.push(`
|
|
232
|
+
results.push(` Skipped field "${field.slug}" (already exists)`);
|
|
246
233
|
skipped.fields++;
|
|
247
234
|
}
|
|
248
235
|
else {
|
|
@@ -261,10 +248,10 @@ ${resolved.error}
|
|
|
261
248
|
},
|
|
262
249
|
});
|
|
263
250
|
if ((0, api_client_1.isApiError)(fieldRes)) {
|
|
264
|
-
results.push(`
|
|
251
|
+
results.push(` Failed to add field "${field.slug}": ${fieldRes.error}`);
|
|
265
252
|
}
|
|
266
253
|
else {
|
|
267
|
-
results.push(`
|
|
254
|
+
results.push(` Added field "${field.slug}" (${field.type})`);
|
|
268
255
|
created.fields++;
|
|
269
256
|
}
|
|
270
257
|
}
|
|
@@ -285,10 +272,10 @@ ${resolved.error}
|
|
|
285
272
|
},
|
|
286
273
|
});
|
|
287
274
|
if ((0, api_client_1.isApiError)(createRes)) {
|
|
288
|
-
results.push(
|
|
275
|
+
results.push(`Failed to create collection "${col.slug}": ${createRes.error}`);
|
|
289
276
|
continue;
|
|
290
277
|
}
|
|
291
|
-
results.push(
|
|
278
|
+
results.push(`Created collection "${col.name}" (${col.slug})`);
|
|
292
279
|
created.collections++;
|
|
293
280
|
// Add fields to the new collection
|
|
294
281
|
if (col.fields && col.fields.length > 0) {
|
|
@@ -308,10 +295,10 @@ ${resolved.error}
|
|
|
308
295
|
},
|
|
309
296
|
});
|
|
310
297
|
if ((0, api_client_1.isApiError)(fieldRes)) {
|
|
311
|
-
results.push(`
|
|
298
|
+
results.push(` Failed to add field "${field.slug}": ${fieldRes.error}`);
|
|
312
299
|
}
|
|
313
300
|
else {
|
|
314
|
-
results.push(`
|
|
301
|
+
results.push(` Added field "${field.slug}" (${field.type})`);
|
|
315
302
|
created.fields++;
|
|
316
303
|
}
|
|
317
304
|
}
|
|
@@ -321,72 +308,39 @@ ${resolved.error}
|
|
|
321
308
|
// Add fields to existing collections
|
|
322
309
|
if (fieldsToAdd && fieldsToAdd.length > 0) {
|
|
323
310
|
for (const group of fieldsToAdd) {
|
|
324
|
-
results.push(`\
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
if (fieldExists) {
|
|
331
|
-
results.push(` ⏭️ Skipped field "${field.slug}" (already exists)`);
|
|
332
|
-
skipped.fields++;
|
|
333
|
-
continue;
|
|
334
|
-
}
|
|
335
|
-
const fieldRes = await (0, api_client_1.apiRequest)(`/api/cms/builtin-collections/${group.collectionSlug}/fields`, {
|
|
336
|
-
tenantId,
|
|
337
|
-
method: 'POST',
|
|
338
|
-
body: {
|
|
339
|
-
slug: field.slug,
|
|
340
|
-
name: field.name,
|
|
341
|
-
type: field.type,
|
|
342
|
-
description: field.description,
|
|
343
|
-
isRequired: field.isRequired,
|
|
344
|
-
options: field.options,
|
|
345
|
-
},
|
|
346
|
-
});
|
|
347
|
-
if ((0, api_client_1.isApiError)(fieldRes)) {
|
|
348
|
-
results.push(` ❌ Failed to add field "${field.slug}": ${fieldRes.error}`);
|
|
349
|
-
}
|
|
350
|
-
else {
|
|
351
|
-
results.push(` ✅ Added field "${field.slug}" (${field.type})`);
|
|
352
|
-
created.fields++;
|
|
353
|
-
}
|
|
354
|
-
}
|
|
311
|
+
results.push(`\nCollection ${group.collectionSlug}:`);
|
|
312
|
+
// All collections are custom collections now
|
|
313
|
+
const customCollection = existingCollections.find(c => c.slug.toLowerCase() === group.collectionSlug.toLowerCase());
|
|
314
|
+
if (!customCollection) {
|
|
315
|
+
results.push(` Collection "${group.collectionSlug}" not found. Create it first with the collections parameter.`);
|
|
316
|
+
continue;
|
|
355
317
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
318
|
+
for (const field of group.fields) {
|
|
319
|
+
const fieldExists = customCollection.fields.some(f => f.slug.toLowerCase() === field.slug.toLowerCase());
|
|
320
|
+
if (fieldExists) {
|
|
321
|
+
results.push(` Skipped field "${field.slug}" (already exists)`);
|
|
322
|
+
skipped.fields++;
|
|
361
323
|
continue;
|
|
362
324
|
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
});
|
|
383
|
-
if ((0, api_client_1.isApiError)(fieldRes)) {
|
|
384
|
-
results.push(` ❌ Failed to add field "${field.slug}": ${fieldRes.error}`);
|
|
385
|
-
}
|
|
386
|
-
else {
|
|
387
|
-
results.push(` ✅ Added field "${field.slug}" (${field.type})`);
|
|
388
|
-
created.fields++;
|
|
389
|
-
}
|
|
325
|
+
const fieldRes = await (0, api_client_1.apiRequest)(`/api/collections/${customCollection.id}/fields`, {
|
|
326
|
+
tenantId,
|
|
327
|
+
method: 'POST',
|
|
328
|
+
body: {
|
|
329
|
+
slug: field.slug,
|
|
330
|
+
name: field.name,
|
|
331
|
+
type: field.type,
|
|
332
|
+
description: field.description,
|
|
333
|
+
isRequired: field.isRequired,
|
|
334
|
+
options: field.options,
|
|
335
|
+
referenceCollection: field.referenceCollection,
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
if ((0, api_client_1.isApiError)(fieldRes)) {
|
|
339
|
+
results.push(` Failed to add field "${field.slug}": ${fieldRes.error}`);
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
results.push(` Added field "${field.slug}" (${field.type})`);
|
|
343
|
+
created.fields++;
|
|
390
344
|
}
|
|
391
345
|
}
|
|
392
346
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-manifest.d.ts","sourceRoot":"","sources":["../../src/tools/validate-manifest.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"validate-manifest.d.ts","sourceRoot":"","sources":["../../src/tools/validate-manifest.ts"],"names":[],"mappings":"AAmBA;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAkO5E"}
|
|
@@ -8,30 +8,9 @@ const pageSchema = zod_1.z.object({
|
|
|
8
8
|
title: zod_1.z.string(),
|
|
9
9
|
editable: zod_1.z.boolean().optional(),
|
|
10
10
|
});
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
blogIndexPath: zod_1.z.string().startsWith('/').optional(),
|
|
15
|
-
blogPost: zod_1.z.string().optional(),
|
|
16
|
-
blogPostPath: zod_1.z.string().startsWith('/').optional(),
|
|
17
|
-
team: zod_1.z.string().optional(),
|
|
18
|
-
teamPath: zod_1.z.string().startsWith('/').optional(),
|
|
19
|
-
downloads: zod_1.z.string().optional(),
|
|
20
|
-
downloadsPath: zod_1.z.string().startsWith('/').optional(),
|
|
21
|
-
authorsIndex: zod_1.z.string().optional(),
|
|
22
|
-
authorDetail: zod_1.z.string().optional(),
|
|
23
|
-
authorsIndexPath: zod_1.z.string().startsWith('/').optional(),
|
|
24
|
-
// Legacy format - deprecated
|
|
25
|
-
collectionPaths: zod_1.z.record(zod_1.z.string().startsWith('/')).optional(),
|
|
26
|
-
collectionTemplates: zod_1.z.record(zod_1.z.object({
|
|
27
|
-
path: zod_1.z.string().startsWith('/').optional(),
|
|
28
|
-
indexTemplate: zod_1.z.string().optional(),
|
|
29
|
-
detailTemplate: zod_1.z.string().optional(),
|
|
30
|
-
})).optional(),
|
|
31
|
-
});
|
|
32
|
-
// Custom collection templates use flat format: {slug}Index, {slug}Detail, {slug}IndexPath, {slug}DetailPath
|
|
33
|
-
// This schema allows any additional string keys for custom collections
|
|
34
|
-
const cmsTemplatesSchema = builtInTemplatesSchema.catchall(zod_1.z.string()).optional();
|
|
11
|
+
// CMS templates use flat format: {slug}Index, {slug}Detail, {slug}IndexPath, {slug}DetailPath
|
|
12
|
+
// This schema allows any string keys for collection templates
|
|
13
|
+
const cmsTemplatesSchema = zod_1.z.record(zod_1.z.string()).optional();
|
|
35
14
|
const manifestSchema = zod_1.z.object({
|
|
36
15
|
pages: zod_1.z.array(pageSchema),
|
|
37
16
|
cmsTemplates: cmsTemplatesSchema,
|
|
@@ -56,7 +35,7 @@ async function validateManifest(manifestJson) {
|
|
|
56
35
|
// Check for missing closing brace
|
|
57
36
|
if (!trimmed.endsWith('}')) {
|
|
58
37
|
diagnostics = `
|
|
59
|
-
|
|
38
|
+
Detected Issue: File does not end with a closing brace '}'
|
|
60
39
|
The manifest.json appears to be truncated or missing the final '}'.
|
|
61
40
|
|
|
62
41
|
Fix: Add a closing '}' at the end of the file.`;
|
|
@@ -64,7 +43,7 @@ async function validateManifest(manifestJson) {
|
|
|
64
43
|
// Check for missing opening brace
|
|
65
44
|
else if (!trimmed.startsWith('{')) {
|
|
66
45
|
diagnostics = `
|
|
67
|
-
|
|
46
|
+
Detected Issue: File does not start with an opening brace '{'
|
|
68
47
|
The manifest.json must be a JSON object starting with '{'.`;
|
|
69
48
|
}
|
|
70
49
|
// Check bracket balance
|
|
@@ -75,20 +54,20 @@ async function validateManifest(manifestJson) {
|
|
|
75
54
|
const closeBrackets = (manifestJson.match(/]/g) || []).length;
|
|
76
55
|
if (openBraces !== closeBraces) {
|
|
77
56
|
diagnostics = `
|
|
78
|
-
|
|
57
|
+
Detected Issue: Mismatched braces
|
|
79
58
|
Found ${openBraces} opening '{' but ${closeBraces} closing '}'
|
|
80
59
|
${openBraces > closeBraces ? `Missing ${openBraces - closeBraces} closing brace(s) '}'` : `Extra ${closeBraces - openBraces} closing brace(s) '}'`}`;
|
|
81
60
|
}
|
|
82
61
|
else if (openBrackets !== closeBrackets) {
|
|
83
62
|
diagnostics = `
|
|
84
|
-
|
|
63
|
+
Detected Issue: Mismatched brackets
|
|
85
64
|
Found ${openBrackets} opening '[' but ${closeBrackets} closing ']'
|
|
86
65
|
${openBrackets > closeBrackets ? `Missing ${openBrackets - closeBrackets} closing bracket(s) ']'` : `Extra ${closeBrackets - openBrackets} closing bracket(s) ']'`}`;
|
|
87
66
|
}
|
|
88
67
|
else {
|
|
89
68
|
// Generic guidance
|
|
90
69
|
diagnostics = `
|
|
91
|
-
|
|
70
|
+
Common JSON issues to check:
|
|
92
71
|
- Missing or extra commas between items
|
|
93
72
|
- Trailing comma after last item in arrays/objects
|
|
94
73
|
- Unquoted property names (must use "key" not key)
|
|
@@ -96,12 +75,12 @@ async function validateManifest(manifestJson) {
|
|
|
96
75
|
- Unescaped special characters in strings`;
|
|
97
76
|
}
|
|
98
77
|
}
|
|
99
|
-
return
|
|
78
|
+
return `INVALID JSON
|
|
100
79
|
|
|
101
80
|
Error: ${errorMessage}
|
|
102
81
|
${diagnostics}
|
|
103
82
|
|
|
104
|
-
|
|
83
|
+
Tip: Use a JSON validator (like jsonlint.com) to find the exact error location.`;
|
|
105
84
|
}
|
|
106
85
|
// Validate against schema
|
|
107
86
|
const result = manifestSchema.safeParse(manifest);
|
|
@@ -141,48 +120,12 @@ ${diagnostics}
|
|
|
141
120
|
const templates = m.cmsTemplates;
|
|
142
121
|
const customCollections = new Set();
|
|
143
122
|
if (templates) {
|
|
144
|
-
//
|
|
145
|
-
const templateFields = ['blogIndex', 'blogPost', 'team', 'downloads', 'authorsIndex', 'authorDetail'];
|
|
146
|
-
for (const field of templateFields) {
|
|
147
|
-
const value = templates[field];
|
|
148
|
-
if (typeof value === 'string') {
|
|
149
|
-
if (!value.startsWith('templates/')) {
|
|
150
|
-
warnings.push(`- cmsTemplates.${field}: should be in templates/ folder (got: ${value})`);
|
|
151
|
-
}
|
|
152
|
-
if (!value.endsWith('.html')) {
|
|
153
|
-
warnings.push(`- cmsTemplates.${field}: should end with .html (got: ${value})`);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
// Check path consistency
|
|
158
|
-
if (templates.blogIndex && !templates.blogIndexPath) {
|
|
159
|
-
warnings.push('- blogIndex template defined but no blogIndexPath - will default to /blog');
|
|
160
|
-
}
|
|
161
|
-
if (templates.blogPost && !templates.blogPostPath) {
|
|
162
|
-
warnings.push('- blogPost template defined but no blogPostPath - will default to /blog');
|
|
163
|
-
}
|
|
164
|
-
if (templates.blogIndexPath !== templates.blogPostPath && templates.blogIndexPath && templates.blogPostPath) {
|
|
165
|
-
warnings.push(`- blogIndexPath (${templates.blogIndexPath}) differs from blogPostPath (${templates.blogPostPath}) - usually these should match`);
|
|
166
|
-
}
|
|
167
|
-
// Check for common author path mistake
|
|
168
|
-
if (templates.authorsPath && !templates.authorsIndexPath) {
|
|
169
|
-
warnings.push('- Found "authorsPath" but expected "authorsIndexPath" - rename to authorsIndexPath');
|
|
170
|
-
}
|
|
171
|
-
// Check author template consistency
|
|
172
|
-
if (templates.authorsIndex && !templates.authorsIndexPath) {
|
|
173
|
-
warnings.push('- authorsIndex template defined but no authorsIndexPath - will default to /authors');
|
|
174
|
-
}
|
|
175
|
-
if (templates.authorDetail && !templates.authorsIndex) {
|
|
176
|
-
warnings.push('- authorDetail template defined but no authorsIndex - authors need both templates');
|
|
177
|
-
}
|
|
178
|
-
// Detect and validate custom collection templates (flat format)
|
|
123
|
+
// Detect and validate collection templates (flat format)
|
|
179
124
|
// Pattern: {slug}Index, {slug}Detail, {slug}IndexPath, {slug}DetailPath
|
|
180
125
|
const allKeys = Object.keys(templates);
|
|
181
126
|
for (const key of allKeys) {
|
|
182
|
-
// Skip
|
|
183
|
-
if (['
|
|
184
|
-
'downloads', 'downloadsPath', 'authorsIndex', 'authorDetail', 'authorsIndexPath',
|
|
185
|
-
'collectionPaths', 'collectionTemplates'].includes(key)) {
|
|
127
|
+
// Skip deprecated keys
|
|
128
|
+
if (['collectionPaths', 'collectionTemplates'].includes(key)) {
|
|
186
129
|
continue;
|
|
187
130
|
}
|
|
188
131
|
// Extract collection slug from key pattern
|
|
@@ -203,7 +146,7 @@ ${diagnostics}
|
|
|
203
146
|
customCollections.add(slug);
|
|
204
147
|
}
|
|
205
148
|
}
|
|
206
|
-
// Validate each
|
|
149
|
+
// Validate each collection
|
|
207
150
|
for (const slug of customCollections) {
|
|
208
151
|
const indexTemplate = templates[`${slug}Index`];
|
|
209
152
|
const detailTemplate = templates[`${slug}Detail`];
|
|
@@ -229,16 +172,16 @@ ${diagnostics}
|
|
|
229
172
|
}
|
|
230
173
|
// Check consistency - if you have one, you should have the others
|
|
231
174
|
if (indexTemplate && !indexPath) {
|
|
232
|
-
warnings.push(`-
|
|
175
|
+
warnings.push(`- Collection "${slug}": has ${slug}Index but no ${slug}IndexPath`);
|
|
233
176
|
}
|
|
234
177
|
if (detailTemplate && !detailPath) {
|
|
235
|
-
warnings.push(`-
|
|
178
|
+
warnings.push(`- Collection "${slug}": has ${slug}Detail but no ${slug}DetailPath`);
|
|
236
179
|
}
|
|
237
180
|
if (indexPath && !indexTemplate) {
|
|
238
|
-
warnings.push(`-
|
|
181
|
+
warnings.push(`- Collection "${slug}": has ${slug}IndexPath but no ${slug}Index template`);
|
|
239
182
|
}
|
|
240
183
|
if (detailPath && !detailTemplate) {
|
|
241
|
-
warnings.push(`-
|
|
184
|
+
warnings.push(`- Collection "${slug}": has ${slug}DetailPath but no ${slug}Detail template`);
|
|
242
185
|
}
|
|
243
186
|
// Note: indexPath and detailPath can be the same (e.g., both "/videos")
|
|
244
187
|
// The system distinguishes by whether a slug segment exists after the path
|
|
@@ -250,32 +193,32 @@ ${diagnostics}
|
|
|
250
193
|
}
|
|
251
194
|
// Build result
|
|
252
195
|
let output = '';
|
|
253
|
-
// Build
|
|
254
|
-
const
|
|
255
|
-
? `\n-
|
|
196
|
+
// Build collections summary
|
|
197
|
+
const collectionsSummary = customCollections.size > 0
|
|
198
|
+
? `\n- Collections: ${Array.from(customCollections).join(', ')}`
|
|
256
199
|
: '';
|
|
257
200
|
if (errors.length === 0 && warnings.length === 0) {
|
|
258
|
-
output =
|
|
201
|
+
output = `MANIFEST VALID
|
|
259
202
|
|
|
260
203
|
The manifest.json structure is correct.
|
|
261
204
|
|
|
262
205
|
Summary:
|
|
263
206
|
- ${m.pages?.length || 0} static page(s) defined
|
|
264
|
-
- CMS templates: ${templates ? 'configured in manifest' : 'not configured (can be set via Settings → CMS Templates after upload)'}${
|
|
207
|
+
- CMS templates: ${templates ? 'configured in manifest' : 'not configured (can be set via Settings → CMS Templates after upload)'}${collectionsSummary}
|
|
265
208
|
- Head HTML: ${m.defaultHeadHtml ? 'configured' : 'not configured'}`;
|
|
266
209
|
}
|
|
267
210
|
else if (errors.length === 0) {
|
|
268
|
-
output =
|
|
211
|
+
output = `MANIFEST VALID WITH WARNINGS
|
|
269
212
|
|
|
270
213
|
The manifest structure is valid but has potential issues:
|
|
271
214
|
|
|
272
215
|
Warnings:
|
|
273
216
|
${warnings.join('\n')}
|
|
274
217
|
|
|
275
|
-
|
|
218
|
+
Tip: CMS templates can also be configured after upload via Dashboard → Settings → CMS Templates`;
|
|
276
219
|
}
|
|
277
220
|
else {
|
|
278
|
-
output =
|
|
221
|
+
output = `MANIFEST INVALID
|
|
279
222
|
|
|
280
223
|
Errors (must fix):
|
|
281
224
|
${errors.join('\n')}`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-package.d.ts","sourceRoot":"","sources":["../../src/tools/validate-package.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,EAAE,EAClB,eAAe,EAAE,MAAM,EACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACxC,OAAO,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"validate-package.d.ts","sourceRoot":"","sources":["../../src/tools/validate-package.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,EAAE,EAClB,eAAe,EAAE,MAAM,EACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACxC,OAAO,CAAC,MAAM,CAAC,CAwMjB"}
|