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,311 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateManifest = validateManifest;
4
+ const zod_1 = require("zod");
5
+ const pageSchema = zod_1.z.object({
6
+ path: zod_1.z.string().startsWith('/'),
7
+ file: zod_1.z.string(),
8
+ title: zod_1.z.string(),
9
+ editable: zod_1.z.boolean().optional(),
10
+ });
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();
14
+ const manifestSchema = zod_1.z.object({
15
+ pages: zod_1.z.array(pageSchema),
16
+ cmsTemplates: cmsTemplatesSchema,
17
+ defaultHeadHtml: zod_1.z.string().optional(),
18
+ });
19
+ /**
20
+ * Validates a manifest.json file and returns errors or success message
21
+ */
22
+ async function validateManifest(manifestJson) {
23
+ const errors = [];
24
+ const warnings = [];
25
+ // Try to parse JSON
26
+ let manifest;
27
+ try {
28
+ manifest = JSON.parse(manifestJson);
29
+ }
30
+ catch (e) {
31
+ const errorMessage = e instanceof Error ? e.message : 'Failed to parse JSON';
32
+ // Provide more specific diagnostics for common JSON errors
33
+ let diagnostics = '';
34
+ const trimmed = manifestJson.trim();
35
+ // Check for missing closing brace
36
+ if (!trimmed.endsWith('}')) {
37
+ diagnostics = `
38
+ Detected Issue: File does not end with a closing brace '}'
39
+ The manifest.json appears to be truncated or missing the final '}'.
40
+
41
+ Fix: Add a closing '}' at the end of the file.`;
42
+ }
43
+ // Check for missing opening brace
44
+ else if (!trimmed.startsWith('{')) {
45
+ diagnostics = `
46
+ Detected Issue: File does not start with an opening brace '{'
47
+ The manifest.json must be a JSON object starting with '{'.`;
48
+ }
49
+ // Check bracket balance
50
+ else {
51
+ const openBraces = (manifestJson.match(/{/g) || []).length;
52
+ const closeBraces = (manifestJson.match(/}/g) || []).length;
53
+ const openBrackets = (manifestJson.match(/\[/g) || []).length;
54
+ const closeBrackets = (manifestJson.match(/]/g) || []).length;
55
+ if (openBraces !== closeBraces) {
56
+ diagnostics = `
57
+ Detected Issue: Mismatched braces
58
+ Found ${openBraces} opening '{' but ${closeBraces} closing '}'
59
+ ${openBraces > closeBraces ? `Missing ${openBraces - closeBraces} closing brace(s) '}'` : `Extra ${closeBraces - openBraces} closing brace(s) '}'`}`;
60
+ }
61
+ else if (openBrackets !== closeBrackets) {
62
+ diagnostics = `
63
+ Detected Issue: Mismatched brackets
64
+ Found ${openBrackets} opening '[' but ${closeBrackets} closing ']'
65
+ ${openBrackets > closeBrackets ? `Missing ${openBrackets - closeBrackets} closing bracket(s) ']'` : `Extra ${closeBrackets - openBrackets} closing bracket(s) ']'`}`;
66
+ }
67
+ else {
68
+ // Generic guidance
69
+ diagnostics = `
70
+ Common JSON issues to check:
71
+ - Missing or extra commas between items
72
+ - Trailing comma after last item in arrays/objects
73
+ - Unquoted property names (must use "key" not key)
74
+ - Single quotes instead of double quotes
75
+ - Unescaped special characters in strings`;
76
+ }
77
+ }
78
+ return `INVALID JSON
79
+
80
+ Error: ${errorMessage}
81
+ ${diagnostics}
82
+
83
+ Tip: Use a JSON validator (like jsonlint.com) to find the exact error location.`;
84
+ }
85
+ // Validate against schema
86
+ const result = manifestSchema.safeParse(manifest);
87
+ if (!result.success) {
88
+ for (const issue of result.error.issues) {
89
+ errors.push(`- ${issue.path.join('.')}: ${issue.message}`);
90
+ }
91
+ }
92
+ // Additional validation
93
+ const m = manifest;
94
+ // CRITICAL: Detect wrong "collections:" format (common AI mistake)
95
+ // AIs often use the wrong nested format instead of the flat cmsTemplates format
96
+ if (m.collections && !m.cmsTemplates) {
97
+ errors.push(`WRONG FORMAT: Use "cmsTemplates", not "collections"
98
+
99
+ This is a common mistake. The correct format is:
100
+
101
+ WRONG (what you have):
102
+ "collections": {
103
+ "posts": { "indexPath": "/blog", "indexFile": "...", "detailPath": "...", "detailFile": "..." }
104
+ }
105
+
106
+ CORRECT (what you need):
107
+ "cmsTemplates": {
108
+ "postsIndex": "templates/posts_index.html",
109
+ "postsDetail": "templates/posts_detail.html",
110
+ "postsIndexPath": "/blog",
111
+ "postsDetailPath": "/blog"
112
+ }
113
+
114
+ Note the flat structure with {slug}Index, {slug}Detail, {slug}IndexPath, {slug}DetailPath keys.`);
115
+ }
116
+ // Detect if cmsTemplates uses wrong nested format
117
+ if (m.cmsTemplates && typeof m.cmsTemplates === 'object') {
118
+ const templates = m.cmsTemplates;
119
+ for (const [key, value] of Object.entries(templates)) {
120
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
121
+ const nested = value;
122
+ if (nested.indexPath || nested.detailPath || nested.indexFile || nested.detailFile) {
123
+ errors.push(`WRONG FORMAT in cmsTemplates.${key}: Using nested object format
124
+
125
+ WRONG (what you have):
126
+ "cmsTemplates": {
127
+ "${key}": { "indexPath": "...", "indexFile": "...", "detailPath": "...", "detailFile": "..." }
128
+ }
129
+
130
+ CORRECT (what you need):
131
+ "cmsTemplates": {
132
+ "${key}Index": "templates/${key}_index.html",
133
+ "${key}Detail": "templates/${key}_detail.html",
134
+ "${key}IndexPath": "${nested.indexPath || '/' + key}",
135
+ "${key}DetailPath": "${nested.detailPath || '/' + key}"
136
+ }
137
+
138
+ Use the FLAT format with separate keys for each property.`);
139
+ break; // Only show this error once
140
+ }
141
+ }
142
+ }
143
+ }
144
+ // Check pages array
145
+ if (!Array.isArray(m.pages)) {
146
+ errors.push('- pages: Must be an array');
147
+ }
148
+ else if (m.pages.length === 0) {
149
+ errors.push(`NO PAGES DEFINED: The pages array is empty.
150
+
151
+ You must define at least one page, typically the homepage:
152
+
153
+ "pages": [
154
+ { "path": "/", "file": "pages/index.html", "title": "Home" }
155
+ ]
156
+
157
+ A site without pages will have no accessible content.`);
158
+ }
159
+ else {
160
+ // Check for homepage
161
+ const hasHomepage = m.pages.some((p) => p.path === '/');
162
+ if (!hasHomepage) {
163
+ errors.push(`NO HOMEPAGE: Missing path "/" in pages array.
164
+
165
+ Every site needs a homepage. Add a page with path "/":
166
+
167
+ "pages": [
168
+ { "path": "/", "file": "pages/index.html", "title": "Home" },
169
+ ...your other pages...
170
+ ]
171
+
172
+ Without a homepage, visitors will see a 404 error.`);
173
+ }
174
+ // Check file paths
175
+ for (const page of m.pages) {
176
+ if (typeof page.file === 'string') {
177
+ if (!page.file.startsWith('pages/')) {
178
+ warnings.push(`- Page "${page.path}": file should be in pages/ folder (got: ${page.file})`);
179
+ }
180
+ if (!page.file.endsWith('.html')) {
181
+ warnings.push(`- Page "${page.path}": file should end with .html (got: ${page.file})`);
182
+ }
183
+ }
184
+ }
185
+ }
186
+ // Check cmsTemplates - enforce strict pattern-based validation
187
+ const templates = m.cmsTemplates;
188
+ const customCollections = new Set();
189
+ if (templates) {
190
+ // All keys must match the unified format: {slug}Index, {slug}Detail, {slug}IndexPath, {slug}DetailPath
191
+ const VALID_KEY_PATTERN = /^[a-z][a-zA-Z0-9_]*(Index|Detail|IndexPath|DetailPath)$/;
192
+ const KEY_PARTS_PATTERN = /^([a-z][a-zA-Z0-9_]*)(Index|Detail|IndexPath|DetailPath)$/;
193
+ for (const key of Object.keys(templates)) {
194
+ if (!VALID_KEY_PATTERN.test(key)) {
195
+ // Provide helpful error message with suggestion
196
+ let suggestion = '';
197
+ if (key.toLowerCase().includes('post')) {
198
+ suggestion = ` Did you mean "${key.replace(/[Pp]ost/g, 'Detail')}"?`;
199
+ }
200
+ else if (!key.includes('Index') && !key.includes('Detail')) {
201
+ suggestion = ` Did you mean "${key}Index" or "${key}Detail"?`;
202
+ }
203
+ errors.push(`- Invalid cmsTemplates key "${key}". ` +
204
+ `All keys must use format: {slug}Index, {slug}Detail, {slug}IndexPath, or {slug}DetailPath.${suggestion}`);
205
+ continue;
206
+ }
207
+ // Extract collection slug from valid keys
208
+ const match = key.match(KEY_PARTS_PATTERN);
209
+ if (match) {
210
+ customCollections.add(match[1]);
211
+ }
212
+ }
213
+ // Validate each detected collection
214
+ for (const slug of customCollections) {
215
+ const indexTemplate = templates[`${slug}Index`];
216
+ const detailTemplate = templates[`${slug}Detail`];
217
+ const indexPath = templates[`${slug}IndexPath`];
218
+ const detailPath = templates[`${slug}DetailPath`];
219
+ // Check template file paths
220
+ if (typeof indexTemplate === 'string') {
221
+ if (!indexTemplate.endsWith('.html')) {
222
+ warnings.push(`- cmsTemplates.${slug}Index: should end with .html (got: ${indexTemplate})`);
223
+ }
224
+ }
225
+ if (typeof detailTemplate === 'string') {
226
+ if (!detailTemplate.endsWith('.html')) {
227
+ warnings.push(`- cmsTemplates.${slug}Detail: should end with .html (got: ${detailTemplate})`);
228
+ }
229
+ }
230
+ // Check path format
231
+ if (typeof indexPath === 'string' && !indexPath.startsWith('/')) {
232
+ errors.push(`- cmsTemplates.${slug}IndexPath: must start with "/" (got: ${indexPath})`);
233
+ }
234
+ if (typeof detailPath === 'string' && !detailPath.startsWith('/')) {
235
+ errors.push(`- cmsTemplates.${slug}DetailPath: must start with "/" (got: ${detailPath})`);
236
+ }
237
+ // Check consistency - incomplete CMS config will cause broken routes
238
+ if (indexTemplate && !detailTemplate) {
239
+ errors.push(`INCOMPLETE CMS CONFIG: Collection "${slug}" has ${slug}Index but no ${slug}Detail.
240
+
241
+ Both templates are required for a collection to work:
242
+ "cmsTemplates": {
243
+ "${slug}Index": "templates/${slug}_index.html",
244
+ "${slug}Detail": "templates/${slug}_detail.html", // <- MISSING
245
+ "${slug}IndexPath": "${indexPath || '/' + slug}",
246
+ "${slug}DetailPath": "${detailPath || '/' + slug}"
247
+ }
248
+
249
+ Without both templates, clicking on list items will lead to 404 errors.`);
250
+ }
251
+ if (detailTemplate && !indexTemplate) {
252
+ errors.push(`INCOMPLETE CMS CONFIG: Collection "${slug}" has ${slug}Detail but no ${slug}Index.
253
+
254
+ Both templates are required for a collection to work:
255
+ "cmsTemplates": {
256
+ "${slug}Index": "templates/${slug}_index.html", // <- MISSING
257
+ "${slug}Detail": "templates/${slug}_detail.html",
258
+ "${slug}IndexPath": "${indexPath || '/' + slug}",
259
+ "${slug}DetailPath": "${detailPath || '/' + slug}"
260
+ }
261
+
262
+ Without an index template, there's no way to list and navigate to items.`);
263
+ }
264
+ if (indexTemplate && !indexPath) {
265
+ warnings.push(`- Collection "${slug}": has ${slug}Index but no ${slug}IndexPath - using default path`);
266
+ }
267
+ if (detailTemplate && !detailPath) {
268
+ warnings.push(`- Collection "${slug}": has ${slug}Detail but no ${slug}DetailPath - using default path`);
269
+ }
270
+ }
271
+ }
272
+ // Build result
273
+ let output = '';
274
+ // Build collections summary
275
+ const collectionsSummary = customCollections.size > 0
276
+ ? `\n- Collections: ${Array.from(customCollections).join(', ')}`
277
+ : '';
278
+ if (errors.length === 0 && warnings.length === 0) {
279
+ output = `MANIFEST VALID
280
+
281
+ The manifest.json structure is correct.
282
+
283
+ Summary:
284
+ - ${m.pages?.length || 0} static page(s) defined
285
+ - CMS templates: ${templates ? 'configured in manifest' : 'not configured (can be set via Settings → CMS Templates after upload)'}${collectionsSummary}
286
+ - Head HTML: ${m.defaultHeadHtml ? 'configured' : 'not configured'}`;
287
+ }
288
+ else if (errors.length === 0) {
289
+ output = `MANIFEST VALID WITH WARNINGS
290
+
291
+ The manifest structure is valid but has potential issues:
292
+
293
+ Warnings:
294
+ ${warnings.join('\n')}
295
+
296
+ Tip: CMS templates can also be configured after upload via Dashboard → Settings → CMS Templates`;
297
+ }
298
+ else {
299
+ output = `MANIFEST INVALID
300
+
301
+ Errors (must fix):
302
+ ${errors.join('\n')}`;
303
+ if (warnings.length > 0) {
304
+ output += `
305
+
306
+ Warnings:
307
+ ${warnings.join('\n')}`;
308
+ }
309
+ }
310
+ return output;
311
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Validates the complete structure of a CMS website package
3
+ */
4
+ export declare function validatePackage(fileList: string[], manifestContent: string, templateContents?: Record<string, string>): Promise<string>;
5
+ //# sourceMappingURL=validate-package.d.ts.map
@@ -0,0 +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,CA4VjB"}
@@ -0,0 +1,337 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validatePackage = validatePackage;
4
+ const validate_manifest_1 = require("./validate-manifest");
5
+ const validate_template_1 = require("./validate-template");
6
+ /**
7
+ * Validates the complete structure of a CMS website package
8
+ */
9
+ async function validatePackage(fileList, manifestContent, templateContents) {
10
+ const errors = [];
11
+ const warnings = [];
12
+ const validations = [];
13
+ // 1. Check required structure
14
+ const hasManifest = fileList.some(f => f === 'manifest.json' || f.endsWith('/manifest.json'));
15
+ const hasPagesFolder = fileList.some(f => f.includes('pages/'));
16
+ const hasPublicFolder = fileList.some(f => f.includes('public/'));
17
+ if (!hasManifest) {
18
+ errors.push('- Missing manifest.json at package root');
19
+ }
20
+ else {
21
+ validations.push('manifest.json found');
22
+ }
23
+ if (!hasPagesFolder) {
24
+ warnings.push('- No pages/ folder found - static pages should be in pages/');
25
+ }
26
+ else {
27
+ validations.push('pages/ folder found');
28
+ }
29
+ if (!hasPublicFolder) {
30
+ warnings.push('- No public/ folder found - assets (CSS, JS, images) should be in public/');
31
+ }
32
+ else {
33
+ validations.push('public/ folder found');
34
+ }
35
+ // 1b. Check for WRONG folder structures (common AI mistakes)
36
+ const hasAssetsFolder = fileList.some(f => f.includes('assets/') && !f.includes('public/assets/'));
37
+ const hasCollectionsFolder = fileList.some(f => f.includes('collections/') && f.endsWith('.html'));
38
+ const hasTemplatesFolder = fileList.some(f => f.includes('templates/'));
39
+ // Error: Using /assets/ instead of /public/
40
+ if (hasAssetsFolder && !hasPublicFolder) {
41
+ errors.push(`WRONG FOLDER: Using "assets/" instead of "public/"
42
+
43
+ Fast Mode serves static assets from the /public/ folder, NOT /assets/.
44
+
45
+ WRONG structure:
46
+ assets/
47
+ ├── css/style.css ← Won't be served!
48
+ └── js/main.js ← Won't be served!
49
+
50
+ CORRECT structure:
51
+ public/
52
+ ├── css/style.css ← Served at /css/style.css
53
+ └── js/main.js ← Served at /js/main.js
54
+
55
+ Move all files from assets/ to public/`);
56
+ }
57
+ else if (hasAssetsFolder) {
58
+ warnings.push(`- Found assets/ folder - assets should be in public/, not assets/`);
59
+ }
60
+ // Error: Using /collections/ instead of /templates/
61
+ if (hasCollectionsFolder) {
62
+ errors.push(`WRONG FOLDER: Using "collections/" instead of "templates/"
63
+
64
+ CMS templates should be in the templates/ folder, NOT collections/.
65
+
66
+ WRONG structure:
67
+ collections/
68
+ ├── posts/
69
+ │ ├── index.html ← Wrong location!
70
+ │ └── detail.html ← Wrong location!
71
+
72
+ CORRECT structure:
73
+ templates/
74
+ ├── posts_index.html
75
+ └── posts_detail.html
76
+
77
+ Note: Template files use {collection}_index.html and {collection}_detail.html naming.`);
78
+ }
79
+ // Warning: No templates folder but has cmsTemplates in manifest (potential issue)
80
+ if (!hasTemplatesFolder && hasPagesFolder) {
81
+ // This is just a hint - might be fine if no CMS content
82
+ // We'll check more specifically later when validating manifest
83
+ }
84
+ // 2. Validate manifest
85
+ const manifestResult = await (0, validate_manifest_1.validateManifest)(manifestContent);
86
+ if (manifestResult.includes('INVALID')) {
87
+ errors.push('- Manifest validation failed (see details below)');
88
+ }
89
+ else if (manifestResult.includes('WARNINGS')) {
90
+ warnings.push('- Manifest has warnings (see details below)');
91
+ }
92
+ else {
93
+ validations.push('manifest.json is valid');
94
+ }
95
+ // 3. Parse manifest for template checks
96
+ let manifest = null;
97
+ try {
98
+ manifest = JSON.parse(manifestContent);
99
+ }
100
+ catch {
101
+ // Already caught in manifest validation
102
+ }
103
+ // 4. Check that referenced files exist
104
+ if (manifest?.pages && Array.isArray(manifest.pages)) {
105
+ for (const page of manifest.pages) {
106
+ const p = page;
107
+ if (typeof p.file === 'string') {
108
+ const fileExists = fileList.some(f => f === p.file || f.endsWith('/' + p.file));
109
+ if (!fileExists) {
110
+ errors.push(`- Page file not found: ${p.file} (referenced by path ${p.path})`);
111
+ }
112
+ }
113
+ }
114
+ }
115
+ // 5. Check CMS template files exist (detect custom collections from manifest)
116
+ if (manifest?.cmsTemplates) {
117
+ const templates = manifest.cmsTemplates;
118
+ const checkedTemplates = new Set();
119
+ for (const [key, value] of Object.entries(templates)) {
120
+ // Skip path keys and non-string values
121
+ if (key.endsWith('Path') || typeof value !== 'string' || !value.endsWith('.html')) {
122
+ continue;
123
+ }
124
+ // Avoid duplicate checks
125
+ if (checkedTemplates.has(value))
126
+ continue;
127
+ checkedTemplates.add(value);
128
+ const fileExists = fileList.some(f => f === value || f.endsWith('/' + value));
129
+ if (!fileExists) {
130
+ errors.push(`- Template file not found: ${value} (cmsTemplates.${key})`);
131
+ }
132
+ else {
133
+ validations.push(`${key} template exists`);
134
+ }
135
+ }
136
+ }
137
+ // 6. Validate template contents if provided
138
+ const templateValidations = [];
139
+ if (templateContents) {
140
+ for (const [path, content] of Object.entries(templateContents)) {
141
+ // Determine template type based on path
142
+ let templateType = 'static_page';
143
+ // Check if it's an index template
144
+ if (path.includes('_index') || path.includes('-index')) {
145
+ templateType = 'custom_index';
146
+ }
147
+ // Check if it's a detail template
148
+ else if (path.includes('_detail') || path.includes('-detail') || path.includes('_post') || path.includes('-post')) {
149
+ templateType = 'custom_detail';
150
+ }
151
+ // Check if it's in templates/ folder (likely a CMS template)
152
+ else if (path.includes('templates/')) {
153
+ // If it has collection-like patterns
154
+ if (content.includes('{{#each')) {
155
+ templateType = 'custom_index';
156
+ }
157
+ else if (content.includes('{{name}}') || content.includes('{{slug}}')) {
158
+ templateType = 'custom_detail';
159
+ }
160
+ }
161
+ const result = await (0, validate_template_1.validateTemplate)(content, templateType);
162
+ if (result.includes('ERRORS')) {
163
+ errors.push(`- Template ${path} has errors`);
164
+ templateValidations.push(`\n### ${path}\n${result}`);
165
+ }
166
+ else if (result.includes('WARNINGS')) {
167
+ warnings.push(`- Template ${path} has warnings`);
168
+ templateValidations.push(`\n### ${path}\n${result}`);
169
+ }
170
+ }
171
+ }
172
+ // 7. Check template URL consistency with manifest paths
173
+ if (manifest?.cmsTemplates && templateContents) {
174
+ // Extract collection paths from manifest
175
+ const collectionPaths = {};
176
+ for (const [key, value] of Object.entries(manifest.cmsTemplates)) {
177
+ if (key.endsWith('IndexPath') && typeof value === 'string') {
178
+ const slug = key.replace('IndexPath', '');
179
+ if (!collectionPaths[slug])
180
+ collectionPaths[slug] = {};
181
+ collectionPaths[slug].indexPath = value;
182
+ }
183
+ if (key.endsWith('DetailPath') && typeof value === 'string') {
184
+ const slug = key.replace('DetailPath', '');
185
+ if (!collectionPaths[slug])
186
+ collectionPaths[slug] = {};
187
+ collectionPaths[slug].detailPath = value;
188
+ }
189
+ }
190
+ // Check templates for URL mismatches
191
+ for (const [templatePath, content] of Object.entries(templateContents)) {
192
+ // Look for hardcoded links that might not match manifest
193
+ const hrefMatches = content.match(/href=["']\/([^"'{}]+)\/\{\{/g);
194
+ if (hrefMatches) {
195
+ for (const match of hrefMatches) {
196
+ // Extract the path like "/posts/" from href="/posts/{{slug}}"
197
+ const pathMatch = match.match(/href=["']\/([^"'{}]+)\//);
198
+ if (pathMatch) {
199
+ const usedPath = '/' + pathMatch[1];
200
+ // Check if this path exists in any of our defined collection paths
201
+ let pathFound = false;
202
+ for (const paths of Object.values(collectionPaths)) {
203
+ if (paths.detailPath === usedPath || paths.indexPath === usedPath) {
204
+ pathFound = true;
205
+ break;
206
+ }
207
+ }
208
+ if (!pathFound && Object.keys(collectionPaths).length > 0) {
209
+ const availablePaths = Object.entries(collectionPaths)
210
+ .map(([slug, p]) => `${slug}: ${p.indexPath || p.detailPath}`)
211
+ .join(', ');
212
+ warnings.push(`- Template ${templatePath}: Uses "${usedPath}" which doesn't match manifest paths (${availablePaths})`);
213
+ }
214
+ }
215
+ }
216
+ }
217
+ }
218
+ }
219
+ // 8. Check for common issues in file structure
220
+ const htmlFiles = fileList.filter(f => f.endsWith('.html'));
221
+ const cssFiles = fileList.filter(f => f.endsWith('.css'));
222
+ const jsFiles = fileList.filter(f => f.endsWith('.js'));
223
+ const imageFiles = fileList.filter(f => /\.(png|jpg|jpeg|gif|svg|webp|ico)$/i.test(f));
224
+ // Check CSS files are in public - these will 404 if not in public/
225
+ for (const css of cssFiles) {
226
+ if (!css.includes('public/')) {
227
+ const fileName = css.split('/').pop();
228
+ errors.push(`WRONG LOCATION: CSS file "${css}" must be in public/ folder.
229
+
230
+ Move to: public/css/${fileName}
231
+ Reference in HTML as: /css/${fileName}
232
+
233
+ Files outside public/ will not be served and will cause 404 errors.`);
234
+ }
235
+ }
236
+ // Check JS files are in public - these will 404 if not in public/
237
+ for (const js of jsFiles) {
238
+ if (!js.includes('public/') && !js.includes('node_modules')) {
239
+ const fileName = js.split('/').pop();
240
+ errors.push(`WRONG LOCATION: JS file "${js}" must be in public/ folder.
241
+
242
+ Move to: public/js/${fileName}
243
+ Reference in HTML as: /js/${fileName}
244
+
245
+ Files outside public/ will not be served and will cause 404 errors.`);
246
+ }
247
+ }
248
+ // Check images are in public - these will 404 if not in public/
249
+ for (const img of imageFiles) {
250
+ if (!img.includes('public/')) {
251
+ const fileName = img.split('/').pop();
252
+ errors.push(`WRONG LOCATION: Image file "${img}" must be in public/ folder.
253
+
254
+ Move to: public/images/${fileName}
255
+ Reference in HTML as: /images/${fileName}
256
+
257
+ Files outside public/ will not be served and will cause 404 errors.`);
258
+ }
259
+ }
260
+ // 9. Check JS files for potential form handler conflicts
261
+ if (templateContents) {
262
+ for (const [path, content] of Object.entries(templateContents)) {
263
+ if (path.endsWith('.js')) {
264
+ // Check for form submit handlers that might block CMS form submission
265
+ const hasSubmitListener = content.includes('addEventListener') && content.includes('submit');
266
+ const hasPreventDefault = content.includes('preventDefault');
267
+ const hasFormsEndpoint = content.includes('/_forms/');
268
+ if (hasSubmitListener && hasPreventDefault && !hasFormsEndpoint) {
269
+ warnings.push(`POTENTIAL FORM ISSUE: ${path} has form handlers with preventDefault() but no /_forms/ endpoint.
270
+
271
+ This JavaScript may block form submissions from reaching Fast Mode!
272
+
273
+ LOOK FOR patterns like:
274
+ form.addEventListener('submit', (e) => {
275
+ e.preventDefault();
276
+ showToast('Message sent!'); // FAKE - data not saved!
277
+ });
278
+
279
+ FIX BY either:
280
+ 1. Remove the original handler entirely (use native form submission)
281
+ 2. Update handler to POST to /_forms/{formName}
282
+
283
+ See get_conversion_guide(section: "forms") for proper form setup.`);
284
+ }
285
+ }
286
+ }
287
+ }
288
+ // Build result
289
+ let output = '';
290
+ if (errors.length === 0) {
291
+ output = `PACKAGE STRUCTURE VALID
292
+
293
+ ${validations.map(v => `- ${v}`).join('\n')}
294
+
295
+ Summary:
296
+ - ${htmlFiles.length} HTML file(s)
297
+ - ${cssFiles.length} CSS file(s)
298
+ - ${jsFiles.length} JS file(s)
299
+ - ${imageFiles.length} image file(s)`;
300
+ if (warnings.length > 0) {
301
+ output += `
302
+
303
+ Warnings (consider fixing):
304
+ ${warnings.join('\n')}`;
305
+ }
306
+ }
307
+ else {
308
+ output = `PACKAGE HAS ERRORS
309
+
310
+ Errors (must fix):
311
+ ${errors.join('\n')}
312
+
313
+ What passed:
314
+ ${validations.map(v => `- ${v}`).join('\n')}`;
315
+ if (warnings.length > 0) {
316
+ output += `
317
+
318
+ Warnings:
319
+ ${warnings.join('\n')}`;
320
+ }
321
+ }
322
+ // Add manifest details
323
+ output += `
324
+
325
+ ---
326
+ ## Manifest Validation Details
327
+ ${manifestResult}`;
328
+ // Add template validation details
329
+ if (templateValidations.length > 0) {
330
+ output += `
331
+
332
+ ---
333
+ ## Template Validation Details
334
+ ${templateValidations.join('\n')}`;
335
+ }
336
+ return output;
337
+ }
@@ -0,0 +1,12 @@
1
+ type TemplateType = 'custom_index' | 'custom_detail' | 'static_page';
2
+ /**
3
+ * Validates an HTML template for correct CMS token usage
4
+ *
5
+ * @param html - The HTML template content
6
+ * @param templateType - The type of template (custom_index, custom_detail, static_page)
7
+ * @param collectionSlug - For custom collections, the collection slug
8
+ * @param projectId - Optional: Project ID or name to validate against actual schema
9
+ */
10
+ export declare function validateTemplate(html: string, templateType: TemplateType, collectionSlug?: string, projectId?: string): Promise<string>;
11
+ export {};
12
+ //# sourceMappingURL=validate-template.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-template.d.ts","sourceRoot":"","sources":["../../src/tools/validate-template.ts"],"names":[],"mappings":"AAMA,KAAK,YAAY,GAAG,cAAc,GAAG,eAAe,GAAG,aAAa,CAAC;AAmUrE;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,YAAY,EAC1B,cAAc,CAAC,EAAE,MAAM,EACvB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CA8kBjB"}