"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.validatePackage = validatePackage;
const validate_manifest_1 = require("./validate-manifest");
const validate_template_1 = require("./validate-template");
/**
* Validates the complete structure of a CMS website package
*/
async function validatePackage(fileList, manifestContent, templateContents) {
const errors = [];
const warnings = [];
const validations = [];
// 1. Check required structure
const hasManifest = fileList.some(f => f === 'manifest.json' || f.endsWith('/manifest.json'));
const hasPagesFolder = fileList.some(f => f.includes('pages/'));
const hasPublicFolder = fileList.some(f => f.includes('public/'));
if (!hasManifest) {
errors.push('- Missing manifest.json at package root');
}
else {
validations.push('manifest.json found');
}
if (!hasPagesFolder) {
warnings.push('- No pages/ folder found - static pages should be in pages/');
}
else {
validations.push('pages/ folder found');
}
if (!hasPublicFolder) {
warnings.push('- No public/ folder found - assets (CSS, JS, images) should be in public/');
}
else {
validations.push('public/ folder found');
}
// 1b. Check for WRONG folder structures (common AI mistakes)
const hasAssetsFolder = fileList.some(f => f.includes('assets/') && !f.includes('public/assets/'));
const hasCollectionsFolder = fileList.some(f => f.includes('collections/') && f.endsWith('.html'));
const hasTemplatesFolder = fileList.some(f => f.includes('templates/'));
// Error: Using /assets/ instead of /public/
if (hasAssetsFolder && !hasPublicFolder) {
errors.push(`WRONG FOLDER: Using "assets/" instead of "public/"
Fast Mode serves static assets from the /public/ folder, NOT /assets/.
WRONG structure:
assets/
├── css/style.css ← Won't be served!
└── js/main.js ← Won't be served!
CORRECT structure:
public/
├── css/style.css ← Served at /css/style.css
└── js/main.js ← Served at /js/main.js
Move all files from assets/ to public/`);
}
else if (hasAssetsFolder) {
warnings.push(`- Found assets/ folder - assets should be in public/, not assets/`);
}
// Error: Using /collections/ instead of /templates/
if (hasCollectionsFolder) {
errors.push(`WRONG FOLDER: Using "collections/" instead of "templates/"
CMS templates should be in the templates/ folder, NOT collections/.
WRONG structure:
collections/
├── posts/
│ ├── index.html ← Wrong location!
│ └── detail.html ← Wrong location!
CORRECT structure:
templates/
├── posts_index.html
└── posts_detail.html
Note: Template files use {collection}_index.html and {collection}_detail.html naming.`);
}
// Warning: No templates folder but has cmsTemplates in manifest (potential issue)
if (!hasTemplatesFolder && hasPagesFolder) {
// This is just a hint - might be fine if no CMS content
// We'll check more specifically later when validating manifest
}
// 2. Validate manifest
const manifestResult = await (0, validate_manifest_1.validateManifest)(manifestContent);
if (manifestResult.includes('INVALID')) {
errors.push('- Manifest validation failed (see details below)');
}
else if (manifestResult.includes('WARNINGS')) {
warnings.push('- Manifest has warnings (see details below)');
}
else {
validations.push('manifest.json is valid');
}
// 3. Parse manifest for template checks
let manifest = null;
try {
manifest = JSON.parse(manifestContent);
}
catch {
// Already caught in manifest validation
}
// 4. Check that referenced files exist
if (manifest?.pages && Array.isArray(manifest.pages)) {
for (const page of manifest.pages) {
const p = page;
if (typeof p.file === 'string') {
const fileExists = fileList.some(f => f === p.file || f.endsWith('/' + p.file));
if (!fileExists) {
errors.push(`- Page file not found: ${p.file} (referenced by path ${p.path})`);
}
}
}
}
// 5. Check CMS template files exist (detect custom collections from manifest)
if (manifest?.cmsTemplates) {
const templates = manifest.cmsTemplates;
const checkedTemplates = new Set();
for (const [key, value] of Object.entries(templates)) {
// Skip path keys and non-string values
if (key.endsWith('Path') || typeof value !== 'string' || !value.endsWith('.html')) {
continue;
}
// Avoid duplicate checks
if (checkedTemplates.has(value))
continue;
checkedTemplates.add(value);
const fileExists = fileList.some(f => f === value || f.endsWith('/' + value));
if (!fileExists) {
errors.push(`- Template file not found: ${value} (cmsTemplates.${key})`);
}
else {
validations.push(`${key} template exists`);
}
}
}
// 6. Validate template contents if provided
const templateValidations = [];
if (templateContents) {
for (const [path, content] of Object.entries(templateContents)) {
// Determine template type based on path
let templateType = 'static_page';
// Check if it's an index template
if (path.includes('_index') || path.includes('-index')) {
templateType = 'custom_index';
}
// Check if it's a detail template
else if (path.includes('_detail') || path.includes('-detail') || path.includes('_post') || path.includes('-post')) {
templateType = 'custom_detail';
}
// Check if it's in templates/ folder (likely a CMS template)
else if (path.includes('templates/')) {
// If it has collection-like patterns
if (content.includes('{{#each')) {
templateType = 'custom_index';
}
else if (content.includes('{{name}}') || content.includes('{{slug}}')) {
templateType = 'custom_detail';
}
}
const result = await (0, validate_template_1.validateTemplate)(content, templateType);
if (result.includes('ERRORS')) {
errors.push(`- Template ${path} has errors`);
templateValidations.push(`\n### ${path}\n${result}`);
}
else if (result.includes('WARNINGS')) {
warnings.push(`- Template ${path} has warnings`);
templateValidations.push(`\n### ${path}\n${result}`);
}
}
}
// 7. Check template URL consistency with manifest paths
if (manifest?.cmsTemplates && templateContents) {
// Extract collection paths from manifest
const collectionPaths = {};
for (const [key, value] of Object.entries(manifest.cmsTemplates)) {
if (key.endsWith('IndexPath') && typeof value === 'string') {
const slug = key.replace('IndexPath', '');
if (!collectionPaths[slug])
collectionPaths[slug] = {};
collectionPaths[slug].indexPath = value;
}
if (key.endsWith('DetailPath') && typeof value === 'string') {
const slug = key.replace('DetailPath', '');
if (!collectionPaths[slug])
collectionPaths[slug] = {};
collectionPaths[slug].detailPath = value;
}
}
// Check templates for URL mismatches
for (const [templatePath, content] of Object.entries(templateContents)) {
// Look for hardcoded links that might not match manifest
const hrefMatches = content.match(/href=["']\/([^"'{}]+)\/\{\{/g);
if (hrefMatches) {
for (const match of hrefMatches) {
// Extract the path like "/posts/" from href="/posts/{{slug}}"
const pathMatch = match.match(/href=["']\/([^"'{}]+)\//);
if (pathMatch) {
const usedPath = '/' + pathMatch[1];
// Check if this path exists in any of our defined collection paths
let pathFound = false;
for (const paths of Object.values(collectionPaths)) {
if (paths.detailPath === usedPath || paths.indexPath === usedPath) {
pathFound = true;
break;
}
}
if (!pathFound && Object.keys(collectionPaths).length > 0) {
const availablePaths = Object.entries(collectionPaths)
.map(([slug, p]) => `${slug}: ${p.indexPath || p.detailPath}`)
.join(', ');
warnings.push(`- Template ${templatePath}: Uses "${usedPath}" which doesn't match manifest paths (${availablePaths})`);
}
}
}
}
}
}
// 8. Check for common issues in file structure
const htmlFiles = fileList.filter(f => f.endsWith('.html'));
const cssFiles = fileList.filter(f => f.endsWith('.css'));
const jsFiles = fileList.filter(f => f.endsWith('.js'));
const imageFiles = fileList.filter(f => /\.(png|jpg|jpeg|gif|svg|webp|ico)$/i.test(f));
// Check CSS files are in public - these will 404 if not in public/
for (const css of cssFiles) {
if (!css.includes('public/')) {
const fileName = css.split('/').pop();
errors.push(`WRONG LOCATION: CSS file "${css}" must be in public/ folder.
Move to: public/css/${fileName}
Reference in HTML as: /css/${fileName}
Files outside public/ will not be served and will cause 404 errors.`);
}
}
// Check JS files are in public - these will 404 if not in public/
for (const js of jsFiles) {
if (!js.includes('public/') && !js.includes('node_modules')) {
const fileName = js.split('/').pop();
errors.push(`WRONG LOCATION: JS file "${js}" must be in public/ folder.
Move to: public/js/${fileName}
Reference in HTML as: /js/${fileName}
Files outside public/ will not be served and will cause 404 errors.`);
}
}
// Check images are in public - these will 404 if not in public/
for (const img of imageFiles) {
if (!img.includes('public/')) {
const fileName = img.split('/').pop();
errors.push(`WRONG LOCATION: Image file "${img}" must be in public/ folder.
Move to: public/images/${fileName}
Reference in HTML as: /images/${fileName}
Files outside public/ will not be served and will cause 404 errors.`);
}
}
// 9. Check JS files for potential form handler conflicts
if (templateContents) {
for (const [path, content] of Object.entries(templateContents)) {
if (path.endsWith('.js')) {
// Check for form submit handlers that might block CMS form submission
const hasSubmitListener = content.includes('addEventListener') && content.includes('submit');
const hasPreventDefault = content.includes('preventDefault');
const hasFormsEndpoint = content.includes('/_forms/');
if (hasSubmitListener && hasPreventDefault && !hasFormsEndpoint) {
warnings.push(`POTENTIAL FORM ISSUE: ${path} has form handlers with preventDefault() but no /_forms/ endpoint.
This JavaScript may block form submissions from reaching Fast Mode!
LOOK FOR patterns like:
form.addEventListener('submit', (e) => {
e.preventDefault();
showToast('Message sent!'); // FAKE - data not saved!
});
FIX BY either:
1. Remove the original handler entirely (use native form submission)
2. Update handler to POST to /_forms/{formName}
See get_conversion_guide(section: "forms") for proper form setup.`);
}
}
}
}
// Build result
let output = '';
if (errors.length === 0) {
output = `PACKAGE STRUCTURE VALID
${validations.map(v => `- ${v}`).join('\n')}
Summary:
- ${htmlFiles.length} HTML file(s)
- ${cssFiles.length} CSS file(s)
- ${jsFiles.length} JS file(s)
- ${imageFiles.length} image file(s)`;
if (warnings.length > 0) {
output += `
Warnings (consider fixing):
${warnings.join('\n')}`;
}
}
else {
output = `PACKAGE HAS ERRORS
Errors (must fix):
${errors.join('\n')}
What passed:
${validations.map(v => `- ${v}`).join('\n')}`;
if (warnings.length > 0) {
output += `
Warnings:
${warnings.join('\n')}`;
}
}
// Add manifest details
output += `
---
## Manifest Validation Details
${manifestResult}`;
// Add template validation details
if (templateValidations.length > 0) {
output += `
---
## Template Validation Details
${templateValidations.join('\n')}`;
}
return output;
}