fastmode-mcp 1.0.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.
Files changed (67) hide show
  1. package/README.md +561 -0
  2. package/bin/run.js +50 -0
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +802 -0
  6. package/dist/lib/api-client.d.ts +81 -0
  7. package/dist/lib/api-client.d.ts.map +1 -0
  8. package/dist/lib/api-client.js +237 -0
  9. package/dist/lib/auth-state.d.ts +13 -0
  10. package/dist/lib/auth-state.d.ts.map +1 -0
  11. package/dist/lib/auth-state.js +24 -0
  12. package/dist/lib/context-fetcher.d.ts +67 -0
  13. package/dist/lib/context-fetcher.d.ts.map +1 -0
  14. package/dist/lib/context-fetcher.js +190 -0
  15. package/dist/lib/credentials.d.ts +52 -0
  16. package/dist/lib/credentials.d.ts.map +1 -0
  17. package/dist/lib/credentials.js +196 -0
  18. package/dist/lib/device-flow.d.ts +14 -0
  19. package/dist/lib/device-flow.d.ts.map +1 -0
  20. package/dist/lib/device-flow.js +244 -0
  21. package/dist/tools/cms-items.d.ts +56 -0
  22. package/dist/tools/cms-items.d.ts.map +1 -0
  23. package/dist/tools/cms-items.js +376 -0
  24. package/dist/tools/create-site.d.ts +9 -0
  25. package/dist/tools/create-site.d.ts.map +1 -0
  26. package/dist/tools/create-site.js +202 -0
  27. package/dist/tools/deploy-package.d.ts +9 -0
  28. package/dist/tools/deploy-package.d.ts.map +1 -0
  29. package/dist/tools/deploy-package.js +434 -0
  30. package/dist/tools/generate-samples.d.ts +19 -0
  31. package/dist/tools/generate-samples.d.ts.map +1 -0
  32. package/dist/tools/generate-samples.js +272 -0
  33. package/dist/tools/get-conversion-guide.d.ts +7 -0
  34. package/dist/tools/get-conversion-guide.d.ts.map +1 -0
  35. package/dist/tools/get-conversion-guide.js +1323 -0
  36. package/dist/tools/get-example.d.ts +7 -0
  37. package/dist/tools/get-example.d.ts.map +1 -0
  38. package/dist/tools/get-example.js +1568 -0
  39. package/dist/tools/get-field-types.d.ts +30 -0
  40. package/dist/tools/get-field-types.d.ts.map +1 -0
  41. package/dist/tools/get-field-types.js +154 -0
  42. package/dist/tools/get-schema.d.ts +5 -0
  43. package/dist/tools/get-schema.d.ts.map +1 -0
  44. package/dist/tools/get-schema.js +320 -0
  45. package/dist/tools/get-started.d.ts +21 -0
  46. package/dist/tools/get-started.d.ts.map +1 -0
  47. package/dist/tools/get-started.js +624 -0
  48. package/dist/tools/get-tenant-schema.d.ts +18 -0
  49. package/dist/tools/get-tenant-schema.d.ts.map +1 -0
  50. package/dist/tools/get-tenant-schema.js +158 -0
  51. package/dist/tools/list-projects.d.ts +5 -0
  52. package/dist/tools/list-projects.d.ts.map +1 -0
  53. package/dist/tools/list-projects.js +101 -0
  54. package/dist/tools/sync-schema.d.ts +41 -0
  55. package/dist/tools/sync-schema.d.ts.map +1 -0
  56. package/dist/tools/sync-schema.js +483 -0
  57. package/dist/tools/validate-manifest.d.ts +5 -0
  58. package/dist/tools/validate-manifest.d.ts.map +1 -0
  59. package/dist/tools/validate-manifest.js +311 -0
  60. package/dist/tools/validate-package.d.ts +5 -0
  61. package/dist/tools/validate-package.d.ts.map +1 -0
  62. package/dist/tools/validate-package.js +337 -0
  63. package/dist/tools/validate-template.d.ts +12 -0
  64. package/dist/tools/validate-template.d.ts.map +1 -0
  65. package/dist/tools/validate-template.js +790 -0
  66. package/package.json +54 -0
  67. package/scripts/postinstall.js +129 -0
@@ -0,0 +1,483 @@
1
+ "use strict";
2
+ /**
3
+ * Sync Schema Tool
4
+ *
5
+ * Creates collections and/or fields in Fast Mode.
6
+ * Requires authentication. Will skip duplicates.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.syncSchema = syncSchema;
10
+ const api_client_1 = require("../lib/api-client");
11
+ const device_flow_1 = require("../lib/device-flow");
12
+ const get_field_types_1 = require("./get-field-types");
13
+ // ============ Constants ============
14
+ const VALID_FIELD_TYPES = get_field_types_1.AVAILABLE_FIELD_TYPES.map(ft => ft.value);
15
+ const AUTH_REQUIRED_MESSAGE = `# Authentication Required
16
+
17
+ This tool requires authentication to create collections and fields.
18
+
19
+ **To authenticate:**
20
+ 1. Set the FASTMODE_AUTH_TOKEN environment variable, OR
21
+ 2. Run this tool again and follow the browser-based login flow
22
+
23
+ Use \`list_projects\` to verify your authentication status.
24
+ `;
25
+ // ============ Helper Functions ============
26
+ /**
27
+ * Normalize select/multiSelect options to array format before sending to API.
28
+ * Handles comma-separated strings and converts to JSON array.
29
+ */
30
+ function normalizeOptionsForApi(options, fieldType) {
31
+ if (!options)
32
+ return undefined;
33
+ // Only process select/multiSelect fields
34
+ if (fieldType !== 'select' && fieldType !== 'multiSelect') {
35
+ return options;
36
+ }
37
+ // If it already looks like a JSON array, validate and return
38
+ if (options.startsWith('[')) {
39
+ try {
40
+ const parsed = JSON.parse(options);
41
+ if (Array.isArray(parsed))
42
+ return options;
43
+ }
44
+ catch {
45
+ // Invalid JSON, fall through to comma-separated handling
46
+ }
47
+ }
48
+ // Convert comma-separated string to JSON array
49
+ const arr = options.split(',').map(s => s.trim()).filter(Boolean);
50
+ return JSON.stringify(arr);
51
+ }
52
+ /**
53
+ * Validate a field type against available types
54
+ */
55
+ function validateFieldType(type) {
56
+ if (!type) {
57
+ return { valid: false, error: 'Field type is required' };
58
+ }
59
+ if (!VALID_FIELD_TYPES.includes(type)) {
60
+ return {
61
+ valid: false,
62
+ error: `Invalid field type "${type}". Valid types: ${VALID_FIELD_TYPES.join(', ')}`
63
+ };
64
+ }
65
+ return { valid: true };
66
+ }
67
+ /**
68
+ * Validate all fields in input
69
+ */
70
+ function validateFields(fields) {
71
+ const errors = [];
72
+ for (const field of fields) {
73
+ if (!field.slug) {
74
+ errors.push(`Field missing slug`);
75
+ continue;
76
+ }
77
+ if (!field.name) {
78
+ errors.push(`Field "${field.slug}" missing name`);
79
+ }
80
+ if (!field.type) {
81
+ errors.push(`Field "${field.slug}" missing type. Use get_field_types to see available types.`);
82
+ continue;
83
+ }
84
+ const typeValidation = validateFieldType(field.type);
85
+ if (!typeValidation.valid) {
86
+ errors.push(`Field "${field.slug}": ${typeValidation.error}`);
87
+ }
88
+ // Check for required options/referenceCollection
89
+ if ((field.type === 'select' || field.type === 'multiSelect') && !field.options) {
90
+ errors.push(`Field "${field.slug}" (${field.type}) requires "options" parameter with comma-separated values`);
91
+ }
92
+ if (field.type === 'relation' && !field.referenceCollection) {
93
+ errors.push(`Field "${field.slug}" (relation) requires "referenceCollection" parameter`);
94
+ }
95
+ }
96
+ return { valid: errors.length === 0, errors };
97
+ }
98
+ /**
99
+ * Resolve project identifier to tenant ID
100
+ */
101
+ async function resolveProjectId(projectIdentifier) {
102
+ const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
103
+ if (uuidPattern.test(projectIdentifier)) {
104
+ return { tenantId: projectIdentifier };
105
+ }
106
+ const response = await (0, api_client_1.apiRequest)('/api/tenants');
107
+ if ((0, api_client_1.isApiError)(response)) {
108
+ return { error: `Failed to look up project: ${response.error}` };
109
+ }
110
+ const match = response.data.find(p => p.name.toLowerCase() === projectIdentifier.toLowerCase());
111
+ if (match) {
112
+ return { tenantId: match.id };
113
+ }
114
+ const partialMatch = response.data.find(p => p.name.toLowerCase().includes(projectIdentifier.toLowerCase()));
115
+ if (partialMatch) {
116
+ return { tenantId: partialMatch.id };
117
+ }
118
+ return {
119
+ error: `Project "${projectIdentifier}" not found. Use list_projects to see available projects.`
120
+ };
121
+ }
122
+ /**
123
+ * Fetch existing collections for a project
124
+ */
125
+ async function fetchExistingCollections(tenantId) {
126
+ const collectionsRes = await (0, api_client_1.apiRequest)('/api/collections', { tenantId });
127
+ if ((0, api_client_1.isApiError)(collectionsRes)) {
128
+ return { error: `Failed to fetch collections: ${collectionsRes.error}` };
129
+ }
130
+ return collectionsRes.data;
131
+ }
132
+ // ============ Main Function ============
133
+ /**
134
+ * Sync schema - create collections and/or fields
135
+ *
136
+ * @param input - The sync schema input with projectId, collections, and/or fieldsToAdd
137
+ */
138
+ async function syncSchema(input) {
139
+ // Check authentication
140
+ if (await (0, api_client_1.needsAuthentication)()) {
141
+ const authResult = await (0, device_flow_1.ensureAuthenticated)();
142
+ if (!authResult.authenticated) {
143
+ return AUTH_REQUIRED_MESSAGE;
144
+ }
145
+ }
146
+ const { projectId, collections, fieldsToAdd } = input;
147
+ // Validate input
148
+ if (!projectId) {
149
+ return `# Error: Missing projectId
150
+
151
+ Please provide a projectId. Use \`list_projects\` to see your available projects.
152
+ `;
153
+ }
154
+ if ((!collections || collections.length === 0) && (!fieldsToAdd || fieldsToAdd.length === 0)) {
155
+ return `# Error: Nothing to sync
156
+
157
+ Please provide either:
158
+ - \`collections\`: Array of new collections to create
159
+ - \`fieldsToAdd\`: Array of fields to add to existing collections
160
+
161
+ Use \`get_field_types\` to see available field types.
162
+ `;
163
+ }
164
+ // Validate all field types before making any API calls
165
+ const allValidationErrors = [];
166
+ const fieldTypeTips = [];
167
+ // Field type hints based on common naming patterns
168
+ const fieldTypeHints = {
169
+ 'video': { suggestedType: 'videoEmbed', tip: 'videoEmbed provides responsive iframe helpers for YouTube/Vimeo' },
170
+ 'youtube': { suggestedType: 'videoEmbed', tip: 'videoEmbed handles YouTube embeds with correct settings' },
171
+ 'vimeo': { suggestedType: 'videoEmbed', tip: 'videoEmbed handles Vimeo embeds properly' },
172
+ 'loom': { suggestedType: 'videoEmbed', tip: 'videoEmbed supports Loom video URLs' },
173
+ 'wistia': { suggestedType: 'videoEmbed', tip: 'videoEmbed supports Wistia video URLs' },
174
+ 'author': { suggestedType: 'relation', tip: 'relation links to an authors collection' },
175
+ 'category': { suggestedType: 'relation', tip: 'relation links to a categories collection' },
176
+ 'categories': { suggestedType: 'relation', tip: 'relation links to a categories collection' },
177
+ 'tag': { suggestedType: 'relation', tip: 'relation links to a tags collection' },
178
+ 'tags': { suggestedType: 'relation', tip: 'relation links to a tags collection' },
179
+ 'parent': { suggestedType: 'relation', tip: 'relation links to a parent collection' },
180
+ 'related': { suggestedType: 'relation', tip: 'relation links to related items' },
181
+ };
182
+ // Helper to check field type hints
183
+ const checkFieldTypeHint = (field) => {
184
+ const hint = fieldTypeHints[field.slug.toLowerCase()];
185
+ if (hint && field.type !== hint.suggestedType) {
186
+ // Only add tip if type is a "close but not optimal" choice
187
+ if ((field.type === 'url' || field.type === 'text') && hint.suggestedType === 'videoEmbed') {
188
+ fieldTypeTips.push(`💡 "${field.slug}": Consider using "${hint.suggestedType}" type - ${hint.tip}`);
189
+ }
190
+ else if (field.type === 'text' && hint.suggestedType === 'relation') {
191
+ fieldTypeTips.push(`💡 "${field.slug}": Consider using "${hint.suggestedType}" type - ${hint.tip}`);
192
+ }
193
+ }
194
+ };
195
+ if (collections) {
196
+ for (const col of collections) {
197
+ if (!col.slug)
198
+ allValidationErrors.push(`Collection missing slug`);
199
+ if (!col.name)
200
+ allValidationErrors.push(`Collection "${col.slug || 'unknown'}" missing name`);
201
+ if (!col.nameSingular)
202
+ allValidationErrors.push(`Collection "${col.slug || 'unknown'}" missing nameSingular`);
203
+ if (col.fields && col.fields.length > 0) {
204
+ const fieldValidation = validateFields(col.fields);
205
+ if (!fieldValidation.valid) {
206
+ allValidationErrors.push(...fieldValidation.errors.map(e => `Collection "${col.slug}": ${e}`));
207
+ }
208
+ // Check for field type hints
209
+ for (const field of col.fields) {
210
+ checkFieldTypeHint(field);
211
+ }
212
+ }
213
+ }
214
+ }
215
+ if (fieldsToAdd) {
216
+ for (const group of fieldsToAdd) {
217
+ if (!group.collectionSlug) {
218
+ allValidationErrors.push(`fieldsToAdd entry missing collectionSlug`);
219
+ continue;
220
+ }
221
+ // Note: isBuiltin is deprecated and ignored - all collections are now custom
222
+ if (group.isBuiltin) {
223
+ allValidationErrors.push(`isBuiltin is no longer supported. All collections are custom collections. Use the collection slug directly.`);
224
+ }
225
+ if (!group.fields || group.fields.length === 0) {
226
+ allValidationErrors.push(`fieldsToAdd for "${group.collectionSlug}" has no fields`);
227
+ continue;
228
+ }
229
+ const fieldValidation = validateFields(group.fields);
230
+ if (!fieldValidation.valid) {
231
+ allValidationErrors.push(...fieldValidation.errors.map(e => `${group.collectionSlug}: ${e}`));
232
+ }
233
+ // Check for field type hints
234
+ for (const field of group.fields) {
235
+ checkFieldTypeHint(field);
236
+ }
237
+ }
238
+ }
239
+ if (allValidationErrors.length > 0) {
240
+ return `# Validation Errors
241
+
242
+ Please fix the following errors before syncing:
243
+
244
+ ${allValidationErrors.map(e => `- ${e}`).join('\n')}
245
+
246
+ **Tip:** Use \`get_field_types\` to see available field types and their requirements.
247
+ `;
248
+ }
249
+ // Resolve project ID
250
+ const resolved = await resolveProjectId(projectId);
251
+ if ('error' in resolved) {
252
+ return `# Project Not Found
253
+
254
+ ${resolved.error}
255
+ `;
256
+ }
257
+ const { tenantId } = resolved;
258
+ // Fetch existing collections
259
+ const existingResult = await fetchExistingCollections(tenantId);
260
+ if ('error' in existingResult) {
261
+ // Check if auth error
262
+ if (existingResult.error.includes('401') || existingResult.error.includes('auth')) {
263
+ const authResult = await (0, device_flow_1.ensureAuthenticated)();
264
+ if (!authResult.authenticated) {
265
+ return AUTH_REQUIRED_MESSAGE;
266
+ }
267
+ // Retry
268
+ const retry = await fetchExistingCollections(tenantId);
269
+ if ('error' in retry) {
270
+ return `# Error\n\n${retry.error}`;
271
+ }
272
+ }
273
+ else {
274
+ return `# Error\n\n${existingResult.error}`;
275
+ }
276
+ }
277
+ let existingCollections = Array.isArray(existingResult) ? existingResult : [];
278
+ // Track results
279
+ const collectionResults = [];
280
+ const fieldResults = [];
281
+ const created = { collections: 0, fields: 0 };
282
+ const skipped = { collections: 0, fields: 0 };
283
+ const failed = { collections: 0, fields: 0 };
284
+ // Build a map of collection slug -> ID (for both existing and newly created)
285
+ const collectionIdMap = new Map();
286
+ const collectionFieldsMap = new Map();
287
+ // Initialize with existing collections
288
+ for (const col of existingCollections) {
289
+ collectionIdMap.set(col.slug.toLowerCase(), col.id);
290
+ collectionFieldsMap.set(col.slug.toLowerCase(), col.fields);
291
+ }
292
+ // ============ PHASE 1: Create/Resolve ALL Collections ============
293
+ if (collections && collections.length > 0) {
294
+ collectionResults.push('### Phase 1: Collections\n');
295
+ for (const col of collections) {
296
+ const slugLower = col.slug.toLowerCase();
297
+ // Check if collection already exists
298
+ if (collectionIdMap.has(slugLower)) {
299
+ collectionResults.push(`| ${col.slug} | Skipped | Already exists |`);
300
+ skipped.collections++;
301
+ continue;
302
+ }
303
+ // Create new collection (WITHOUT fields - those come in Phase 2)
304
+ let createRes = await (0, api_client_1.apiRequest)('/api/collections', {
305
+ tenantId,
306
+ method: 'POST',
307
+ body: {
308
+ slug: col.slug,
309
+ name: col.name,
310
+ nameSingular: col.nameSingular,
311
+ description: col.description,
312
+ hasSlug: col.hasSlug ?? true,
313
+ },
314
+ });
315
+ // Retry once if failed
316
+ if ((0, api_client_1.isApiError)(createRes)) {
317
+ await new Promise(resolve => setTimeout(resolve, 500));
318
+ createRes = await (0, api_client_1.apiRequest)('/api/collections', {
319
+ tenantId,
320
+ method: 'POST',
321
+ body: {
322
+ slug: col.slug,
323
+ name: col.name,
324
+ nameSingular: col.nameSingular,
325
+ description: col.description,
326
+ hasSlug: col.hasSlug ?? true,
327
+ },
328
+ });
329
+ }
330
+ if ((0, api_client_1.isApiError)(createRes)) {
331
+ collectionResults.push(`| ${col.slug} | FAILED | ${createRes.error} |`);
332
+ failed.collections++;
333
+ continue;
334
+ }
335
+ // Add to our maps
336
+ collectionIdMap.set(slugLower, createRes.data.id);
337
+ collectionFieldsMap.set(slugLower, []); // New collection has no fields yet
338
+ collectionResults.push(`| ${col.slug} | Created | ${col.name} |`);
339
+ created.collections++;
340
+ }
341
+ }
342
+ const fieldJobs = [];
343
+ // Fields from new collections
344
+ if (collections) {
345
+ for (const col of collections) {
346
+ if (col.fields && col.fields.length > 0) {
347
+ for (const field of col.fields) {
348
+ fieldJobs.push({ collectionSlug: col.slug, field });
349
+ }
350
+ }
351
+ }
352
+ }
353
+ // Fields from fieldsToAdd
354
+ if (fieldsToAdd) {
355
+ for (const group of fieldsToAdd) {
356
+ for (const field of group.fields) {
357
+ fieldJobs.push({ collectionSlug: group.collectionSlug, field });
358
+ }
359
+ }
360
+ }
361
+ if (fieldJobs.length > 0) {
362
+ fieldResults.push('### Phase 2: Fields\n');
363
+ fieldResults.push('| Collection | Field | Type | Status |');
364
+ fieldResults.push('|------------|-------|------|--------|');
365
+ for (const job of fieldJobs) {
366
+ const slugLower = job.collectionSlug.toLowerCase();
367
+ const collectionId = collectionIdMap.get(slugLower);
368
+ if (!collectionId) {
369
+ fieldResults.push(`| ${job.collectionSlug} | ${job.field.slug} | ${job.field.type} | FAILED: Collection not found |`);
370
+ failed.fields++;
371
+ continue;
372
+ }
373
+ // Check if field already exists
374
+ const existingFields = collectionFieldsMap.get(slugLower) || [];
375
+ const fieldExists = existingFields.some(f => f.slug.toLowerCase() === job.field.slug.toLowerCase());
376
+ if (fieldExists) {
377
+ fieldResults.push(`| ${job.collectionSlug} | ${job.field.slug} | ${job.field.type} | Skipped (exists) |`);
378
+ skipped.fields++;
379
+ continue;
380
+ }
381
+ // Normalize options for select/multiSelect fields before sending
382
+ const normalizedOptions = normalizeOptionsForApi(job.field.options, job.field.type);
383
+ // Create the field with retry logic
384
+ let fieldRes = await (0, api_client_1.apiRequest)(`/api/collections/${collectionId}/fields`, {
385
+ tenantId,
386
+ method: 'POST',
387
+ body: {
388
+ slug: job.field.slug,
389
+ name: job.field.name,
390
+ type: job.field.type,
391
+ description: job.field.description,
392
+ isRequired: job.field.isRequired,
393
+ options: normalizedOptions,
394
+ referenceCollection: job.field.referenceCollection,
395
+ },
396
+ });
397
+ // Retry once if failed (network issues, temporary errors)
398
+ if ((0, api_client_1.isApiError)(fieldRes)) {
399
+ // Wait a moment and retry
400
+ await new Promise(resolve => setTimeout(resolve, 500));
401
+ fieldRes = await (0, api_client_1.apiRequest)(`/api/collections/${collectionId}/fields`, {
402
+ tenantId,
403
+ method: 'POST',
404
+ body: {
405
+ slug: job.field.slug,
406
+ name: job.field.name,
407
+ type: job.field.type,
408
+ description: job.field.description,
409
+ isRequired: job.field.isRequired,
410
+ options: normalizedOptions,
411
+ referenceCollection: job.field.referenceCollection,
412
+ },
413
+ });
414
+ }
415
+ if ((0, api_client_1.isApiError)(fieldRes)) {
416
+ fieldResults.push(`| ${job.collectionSlug} | ${job.field.slug} | ${job.field.type} | FAILED: ${fieldRes.error} |`);
417
+ failed.fields++;
418
+ }
419
+ else {
420
+ fieldResults.push(`| ${job.collectionSlug} | ${job.field.slug} | ${job.field.type} | Created |`);
421
+ created.fields++;
422
+ // Update the fields map so subsequent checks know this field exists
423
+ const fields = collectionFieldsMap.get(slugLower) || [];
424
+ fields.push({ slug: job.field.slug });
425
+ collectionFieldsMap.set(slugLower, fields);
426
+ }
427
+ }
428
+ }
429
+ // ============ Build Summary ============
430
+ const hasFailures = failed.collections > 0 || failed.fields > 0;
431
+ let output = `# Schema Sync ${hasFailures ? 'Completed with Errors' : 'Complete'}
432
+
433
+ **Project ID:** \`${tenantId}\`
434
+
435
+ ## Summary
436
+
437
+ | Metric | Created | Skipped | Failed |
438
+ |--------|---------|---------|--------|
439
+ | Collections | ${created.collections} | ${skipped.collections} | ${failed.collections} |
440
+ | Fields | ${created.fields} | ${skipped.fields} | ${failed.fields} |
441
+
442
+ `;
443
+ if (collectionResults.length > 1) {
444
+ output += `## Collections
445
+
446
+ | Slug | Status | Details |
447
+ |------|--------|---------|
448
+ ${collectionResults.slice(1).join('\n')}
449
+
450
+ `;
451
+ }
452
+ if (fieldResults.length > 0) {
453
+ output += `## Fields
454
+
455
+ ${fieldResults.join('\n')}
456
+
457
+ `;
458
+ }
459
+ if (hasFailures) {
460
+ output += `---
461
+
462
+ ## ACTION REQUIRED
463
+
464
+ Some items failed to create. Please review the errors above and:
465
+ 1. Fix any issues with field types or parameters
466
+ 2. Run sync_schema again - it will skip already-created items and retry failed ones
467
+ `;
468
+ }
469
+ // Add field type tips if any were collected
470
+ if (fieldTypeTips.length > 0) {
471
+ output += `---
472
+
473
+ ## Tips
474
+
475
+ The following suggestions may help improve your schema:
476
+
477
+ ${fieldTypeTips.join('\n')}
478
+
479
+ These are suggestions only - your current field types will still work.
480
+ `;
481
+ }
482
+ return output;
483
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Validates a manifest.json file and returns errors or success message
3
+ */
4
+ export declare function validateManifest(manifestJson: string): Promise<string>;
5
+ //# sourceMappingURL=validate-manifest.d.ts.map
@@ -0,0 +1 @@
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,CAoT5E"}