meno-core 1.0.38 → 1.0.39
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/build-astro.ts +914 -0
- package/dist/build-static.js +2 -2
- package/dist/chunks/{chunk-UR7L5UZ3.js → chunk-HNAS6BSS.js} +2 -2
- package/dist/chunks/{chunk-EUCAKI5U.js → chunk-W6HDII4T.js} +8 -1
- package/dist/chunks/{chunk-EUCAKI5U.js.map → chunk-W6HDII4T.js.map} +2 -2
- package/dist/chunks/{chunk-JACS3C25.js → chunk-WK5XLASY.js} +2 -2
- package/dist/entries/server-router.js +2 -2
- package/dist/lib/client/index.js +5 -3
- package/dist/lib/client/index.js.map +2 -2
- package/dist/lib/server/index.js +1840 -5
- package/dist/lib/server/index.js.map +4 -4
- package/dist/lib/shared/index.js +5 -3
- package/dist/lib/shared/index.js.map +2 -2
- package/lib/client/theme.ts +4 -1
- package/lib/server/astro/componentEmitter.ts +208 -0
- package/lib/server/astro/cssCollector.ts +147 -0
- package/lib/server/astro/index.ts +5 -0
- package/lib/server/astro/nodeToAstro.ts +771 -0
- package/lib/server/astro/pageEmitter.ts +190 -0
- package/lib/server/astro/tailwindMapper.ts +547 -0
- package/lib/server/index.ts +3 -0
- package/lib/server/ssr/htmlGenerator.ts +3 -0
- package/lib/server/ssr/ssrRenderer.test.ts +8 -4
- package/lib/server/ssr/ssrRenderer.ts +1 -3
- package/lib/shared/themeDefaults.test.ts +1 -1
- package/lib/shared/themeDefaults.ts +4 -1
- package/package.json +1 -1
- /package/dist/chunks/{chunk-UR7L5UZ3.js.map → chunk-HNAS6BSS.js.map} +0 -0
- /package/dist/chunks/{chunk-JACS3C25.js.map → chunk-WK5XLASY.js.map} +0 -0
package/build-astro.ts
ADDED
|
@@ -0,0 +1,914 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Astro Export Build Script
|
|
3
|
+
* Renders all pages via the SSR pipeline, then wraps them as Astro page files
|
|
4
|
+
* with a shared layout, global CSS, and optional CMS content collections.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync, readdirSync, mkdirSync, rmSync, statSync, copyFileSync, writeFileSync } from "fs";
|
|
8
|
+
import { writeFile, readFile } from "fs/promises";
|
|
9
|
+
import { join } from "path";
|
|
10
|
+
import { createHash } from "crypto";
|
|
11
|
+
import { inspect, minifyJS as runtimeMinifyJS } from './lib/server/runtime';
|
|
12
|
+
import {
|
|
13
|
+
loadJSONFile,
|
|
14
|
+
loadComponentDirectory,
|
|
15
|
+
mapPageNameToPath,
|
|
16
|
+
parseJSON,
|
|
17
|
+
loadI18nConfig
|
|
18
|
+
} from "./lib/server/jsonLoader";
|
|
19
|
+
import { generateSSRHTML } from "./lib/server/ssrRenderer";
|
|
20
|
+
import type { SSRHTMLResult } from "./lib/server/ssr/htmlGenerator";
|
|
21
|
+
import { projectPaths } from "./lib/server/projectContext";
|
|
22
|
+
import { loadProjectConfig, generateFontCSS, generateFontPreloadTags } from "./lib/shared/fontLoader";
|
|
23
|
+
import { FileSystemCMSProvider } from "./lib/server/providers/fileSystemCMSProvider";
|
|
24
|
+
import { CMSService } from "./lib/server/services/cmsService";
|
|
25
|
+
import { isI18nValue, resolveI18nValue } from "./lib/shared/i18n";
|
|
26
|
+
import type { ComponentDefinition, JSONPage, CMSSchema, CMSItem, I18nConfig } from "./lib/shared/types";
|
|
27
|
+
import type { CMSFieldDefinition } from "./lib/shared/types/cms";
|
|
28
|
+
import { isItemDraftForLocale } from "./lib/shared/types";
|
|
29
|
+
import type { SlugMap } from "./lib/shared/slugTranslator";
|
|
30
|
+
import { renderPageSSR } from "./lib/server/ssr/ssrRenderer";
|
|
31
|
+
import { generateThemeColorVariablesCSS, generateVariablesCSS } from "./lib/server/cssGenerator";
|
|
32
|
+
import { generateAllInteractiveCSS } from "./lib/shared/cssGeneration";
|
|
33
|
+
import { colorService } from "./lib/server/services/ColorService";
|
|
34
|
+
import { variableService } from "./lib/server/services/VariableService";
|
|
35
|
+
import { configService } from "./lib/server/services/configService";
|
|
36
|
+
import { loadBreakpointConfig, loadResponsiveScalesConfig } from "./lib/server/jsonLoader";
|
|
37
|
+
import type { InteractiveStyles } from "./lib/shared/types/styles";
|
|
38
|
+
import { collectComponentLibraries, filterLibrariesByContext, mergeLibraries, generateLibraryTags } from "./lib/shared/libraryLoader";
|
|
39
|
+
import { migrateTemplatesDirectory } from "./lib/server/migrateTemplates";
|
|
40
|
+
import { emitAstroComponent } from "./lib/server/astro/componentEmitter";
|
|
41
|
+
import { emitAstroPage } from "./lib/server/astro/pageEmitter";
|
|
42
|
+
import { collectAllMappingClasses } from "./lib/server/astro/cssCollector";
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Helpers
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
function hashContent(content: string): string {
|
|
49
|
+
return createHash('sha256').update(content).digest('hex').slice(0, 8);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function copyDirectory(src: string, dest: string): void {
|
|
53
|
+
if (!existsSync(src)) return;
|
|
54
|
+
if (!existsSync(dest)) mkdirSync(dest, { recursive: true });
|
|
55
|
+
const files = readdirSync(src);
|
|
56
|
+
for (const file of files) {
|
|
57
|
+
const srcPath = join(src, file);
|
|
58
|
+
const destPath = join(dest, file);
|
|
59
|
+
const stat = statSync(srcPath);
|
|
60
|
+
if (stat.isDirectory()) copyDirectory(srcPath, destPath);
|
|
61
|
+
else copyFileSync(srcPath, destPath);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isCMSPage(pageData: JSONPage): boolean {
|
|
66
|
+
return pageData.meta?.source === 'cms' && !!pageData.meta?.cms;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Build URL path for a CMS item based on the URL pattern
|
|
71
|
+
*/
|
|
72
|
+
function buildCMSItemPath(
|
|
73
|
+
urlPattern: string,
|
|
74
|
+
item: CMSItem,
|
|
75
|
+
slugField: string,
|
|
76
|
+
locale: string,
|
|
77
|
+
i18nConfig: I18nConfig
|
|
78
|
+
): string {
|
|
79
|
+
let slug = item[slugField] ?? item._slug ?? item._id;
|
|
80
|
+
if (isI18nValue(slug)) {
|
|
81
|
+
slug = resolveI18nValue(slug, locale, i18nConfig) as string;
|
|
82
|
+
}
|
|
83
|
+
return urlPattern.replace('{{slug}}', String(slug));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Recursively scan a directory for .json files, returning relative paths.
|
|
88
|
+
*/
|
|
89
|
+
function scanJSONFiles(dir: string, prefix: string = ''): string[] {
|
|
90
|
+
const results: string[] = [];
|
|
91
|
+
if (!existsSync(dir)) return results;
|
|
92
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
93
|
+
for (const entry of entries) {
|
|
94
|
+
if (entry.isFile() && entry.name.endsWith('.json')) {
|
|
95
|
+
results.push(prefix ? `${prefix}/${entry.name}` : entry.name);
|
|
96
|
+
} else if (entry.isDirectory()) {
|
|
97
|
+
results.push(...scanJSONFiles(join(dir, entry.name), prefix ? `${prefix}/${entry.name}` : entry.name));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return results;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Compute the relative import path from a file at `fromDepth` levels under src/pages/
|
|
105
|
+
* back to src/layouts/BaseLayout.astro.
|
|
106
|
+
*/
|
|
107
|
+
function layoutImportPath(fileDepth: number): string {
|
|
108
|
+
// fileDepth = 0 means file is at src/pages/index.astro -> ../layouts/BaseLayout.astro
|
|
109
|
+
// fileDepth = 1 means src/pages/en/index.astro -> ../../layouts/BaseLayout.astro
|
|
110
|
+
const ups = '../'.repeat(fileDepth + 1);
|
|
111
|
+
return `${ups}layouts/BaseLayout.astro`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Escape a string for use inside a JS template literal (backtick string).
|
|
116
|
+
*/
|
|
117
|
+
function escapeTemplateLiteral(s: string): string {
|
|
118
|
+
return s.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Map a CMS field type to a Zod schema string for the Astro content config.
|
|
123
|
+
*/
|
|
124
|
+
function cmsFieldToZod(field: CMSFieldDefinition): string {
|
|
125
|
+
switch (field.type) {
|
|
126
|
+
case 'string':
|
|
127
|
+
case 'text':
|
|
128
|
+
case 'rich-text':
|
|
129
|
+
return 'z.string()';
|
|
130
|
+
case 'number':
|
|
131
|
+
return 'z.number()';
|
|
132
|
+
case 'boolean':
|
|
133
|
+
return 'z.boolean()';
|
|
134
|
+
case 'date':
|
|
135
|
+
return 'z.coerce.date()';
|
|
136
|
+
case 'select':
|
|
137
|
+
if (field.options && field.options.length > 0) {
|
|
138
|
+
const opts = field.options.map(o => `'${o.replace(/'/g, "\\'")}'`).join(', ');
|
|
139
|
+
return `z.enum([${opts}])`;
|
|
140
|
+
}
|
|
141
|
+
return 'z.string()';
|
|
142
|
+
case 'image':
|
|
143
|
+
case 'file':
|
|
144
|
+
return 'z.string()';
|
|
145
|
+
case 'reference':
|
|
146
|
+
return 'z.string()';
|
|
147
|
+
default:
|
|
148
|
+
return 'z.string()';
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
// Types
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
interface PageRenderResult {
|
|
157
|
+
/** Body HTML (inner content, no DOCTYPE wrapper) */
|
|
158
|
+
html: string;
|
|
159
|
+
/** Head meta tags HTML string */
|
|
160
|
+
meta: string;
|
|
161
|
+
/** Page title */
|
|
162
|
+
title: string;
|
|
163
|
+
/** Extracted JavaScript (if any) */
|
|
164
|
+
javascript: string;
|
|
165
|
+
/** Per-component CSS */
|
|
166
|
+
componentCSS?: string;
|
|
167
|
+
/** Locale used */
|
|
168
|
+
locale: string;
|
|
169
|
+
/** Interactive styles (hover, focus, etc.) */
|
|
170
|
+
interactiveStylesMap: Map<string, InteractiveStyles>;
|
|
171
|
+
/** The URL path this page will live at */
|
|
172
|
+
urlPath: string;
|
|
173
|
+
/** File depth relative to src/pages/ (0 = root, 1 = one dir deep, etc.) */
|
|
174
|
+
fileDepth: number;
|
|
175
|
+
/** Relative file path within src/pages/ */
|
|
176
|
+
astroFilePath: string;
|
|
177
|
+
/** Original page data (for Pass 2 component-structured emission) */
|
|
178
|
+
pageData?: JSONPage;
|
|
179
|
+
/** Page name without extension */
|
|
180
|
+
pageName?: string;
|
|
181
|
+
/** Whether this is a CMS template page */
|
|
182
|
+
isCMSPage?: boolean;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
interface AstroBuildStats {
|
|
186
|
+
pages: number;
|
|
187
|
+
cmsPages: number;
|
|
188
|
+
collections: number;
|
|
189
|
+
errors: number;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
// SSR Fallback page builder (used for CMS pages and error fallback)
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
|
|
196
|
+
function buildSSRFallbackPage(
|
|
197
|
+
result: PageRenderResult,
|
|
198
|
+
importPath: string,
|
|
199
|
+
fontPreloads: string,
|
|
200
|
+
libraryTags: { headCSS?: string; headJS?: string; bodyEndJS?: string },
|
|
201
|
+
defaultTheme: string,
|
|
202
|
+
scriptPaths: string[]
|
|
203
|
+
): string {
|
|
204
|
+
const escapedMeta = escapeTemplateLiteral(result.meta);
|
|
205
|
+
const escapedHTML = escapeTemplateLiteral(result.html);
|
|
206
|
+
const escapedFontPreloads = escapeTemplateLiteral(fontPreloads);
|
|
207
|
+
|
|
208
|
+
const scriptsArrayLiteral = scriptPaths.length > 0
|
|
209
|
+
? `[${scriptPaths.map(s => `"${s}"`).join(', ')}]`
|
|
210
|
+
: '[]';
|
|
211
|
+
|
|
212
|
+
const libraryTagsLiteral = `{ headCSS: \`${escapeTemplateLiteral(libraryTags.headCSS || '')}\`, headJS: \`${escapeTemplateLiteral(libraryTags.headJS || '')}\`, bodyEndJS: \`${escapeTemplateLiteral(libraryTags.bodyEndJS || '')}\` }`;
|
|
213
|
+
|
|
214
|
+
return `---
|
|
215
|
+
import BaseLayout from '${importPath}';
|
|
216
|
+
---
|
|
217
|
+
<BaseLayout
|
|
218
|
+
title="${result.title.replace(/"/g, '"')}"
|
|
219
|
+
meta={\`${escapedMeta}\`}
|
|
220
|
+
scripts={${scriptsArrayLiteral}}
|
|
221
|
+
locale="${result.locale}"
|
|
222
|
+
theme="${defaultTheme}"
|
|
223
|
+
fontPreloads={\`${escapedFontPreloads}\`}
|
|
224
|
+
libraryTags={${libraryTagsLiteral}}
|
|
225
|
+
>
|
|
226
|
+
<Fragment set:html={\`<div id="root">${escapedHTML}</div>\`} />
|
|
227
|
+
</BaseLayout>
|
|
228
|
+
`;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
// Main export
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
|
|
235
|
+
export async function buildAstroProject(
|
|
236
|
+
projectRoot?: string,
|
|
237
|
+
outputDir?: string
|
|
238
|
+
): Promise<AstroBuildStats> {
|
|
239
|
+
const startTime = Date.now();
|
|
240
|
+
|
|
241
|
+
console.log('🏗️ Building Astro export...\n');
|
|
242
|
+
|
|
243
|
+
// ----------------------------------------------------------
|
|
244
|
+
// 1. Setup: load project configuration
|
|
245
|
+
// ----------------------------------------------------------
|
|
246
|
+
configService.reset();
|
|
247
|
+
|
|
248
|
+
const projectConfig = await loadProjectConfig();
|
|
249
|
+
const siteUrl = (projectConfig as { siteUrl?: string }).siteUrl?.replace(/\/$/, '') || '';
|
|
250
|
+
|
|
251
|
+
const i18nConfig = await loadI18nConfig();
|
|
252
|
+
console.log(`🌐 Locales: ${i18nConfig.locales.map(l => l.code).join(', ')} (default: ${i18nConfig.defaultLocale})\n`);
|
|
253
|
+
|
|
254
|
+
await migrateTemplatesDirectory();
|
|
255
|
+
|
|
256
|
+
const { components, warnings, errors: compErrors } = await loadComponentDirectory(projectPaths.components());
|
|
257
|
+
const globalComponents: Record<string, ComponentDefinition> = {};
|
|
258
|
+
components.forEach((value, key) => { globalComponents[key] = value; });
|
|
259
|
+
for (const w of warnings) console.warn(` Warning: ${w}`);
|
|
260
|
+
for (const e of compErrors) console.error(` Error: ${e}`);
|
|
261
|
+
console.log(`Loaded ${components.size} global component(s)\n`);
|
|
262
|
+
|
|
263
|
+
const cmsProvider = new FileSystemCMSProvider(projectPaths.templates(), projectPaths.cms());
|
|
264
|
+
const cmsService = new CMSService(cmsProvider);
|
|
265
|
+
await cmsService.initialize();
|
|
266
|
+
console.log('CMS service initialized\n');
|
|
267
|
+
|
|
268
|
+
const themeConfig = await colorService.loadThemeConfig();
|
|
269
|
+
const variablesConfig = await variableService.loadConfig();
|
|
270
|
+
const breakpoints = await loadBreakpointConfig();
|
|
271
|
+
const responsiveScales = await loadResponsiveScalesConfig();
|
|
272
|
+
|
|
273
|
+
// Libraries (global + component)
|
|
274
|
+
await configService.load();
|
|
275
|
+
const globalLibraries = configService.getLibraries();
|
|
276
|
+
const componentLibraries = collectComponentLibraries(globalComponents);
|
|
277
|
+
|
|
278
|
+
// ----------------------------------------------------------
|
|
279
|
+
// 2. Clean and create output directory
|
|
280
|
+
// ----------------------------------------------------------
|
|
281
|
+
const outDir = outputDir || join(projectPaths.project, 'astro-export');
|
|
282
|
+
|
|
283
|
+
if (existsSync(outDir)) {
|
|
284
|
+
rmSync(outDir, { recursive: true, force: true });
|
|
285
|
+
}
|
|
286
|
+
mkdirSync(outDir, { recursive: true });
|
|
287
|
+
|
|
288
|
+
// Create directory structure
|
|
289
|
+
const srcDir = join(outDir, 'src');
|
|
290
|
+
const pagesOutDir = join(srcDir, 'pages');
|
|
291
|
+
const layoutsDir = join(srcDir, 'layouts');
|
|
292
|
+
const stylesDir = join(srcDir, 'styles');
|
|
293
|
+
const componentsOutDir = join(srcDir, 'components');
|
|
294
|
+
const publicDir = join(outDir, 'public');
|
|
295
|
+
const scriptsDir = join(publicDir, '_scripts');
|
|
296
|
+
for (const d of [srcDir, pagesOutDir, layoutsDir, stylesDir, componentsOutDir, publicDir]) {
|
|
297
|
+
mkdirSync(d, { recursive: true });
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ----------------------------------------------------------
|
|
301
|
+
// 3. Scan pages
|
|
302
|
+
// ----------------------------------------------------------
|
|
303
|
+
const pagesDir = projectPaths.pages();
|
|
304
|
+
if (!existsSync(pagesDir)) {
|
|
305
|
+
console.error('Pages directory not found!');
|
|
306
|
+
return { pages: 0, cmsPages: 0, collections: 0, errors: 1 };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const pageFiles = scanJSONFiles(pagesDir);
|
|
310
|
+
if (pageFiles.length === 0) {
|
|
311
|
+
console.warn('No pages found in ./pages directory');
|
|
312
|
+
return { pages: 0, cmsPages: 0, collections: 0, errors: 0 };
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
console.log(`Found ${pageFiles.length} page(s) to process\n`);
|
|
316
|
+
|
|
317
|
+
// Collect slug mappings (first pass)
|
|
318
|
+
const slugMappings: SlugMap[] = [];
|
|
319
|
+
for (const file of pageFiles) {
|
|
320
|
+
const pageName = file.replace('.json', '');
|
|
321
|
+
const basePath = mapPageNameToPath(pageName);
|
|
322
|
+
const pageContent = await loadJSONFile(join(pagesDir, file));
|
|
323
|
+
if (!pageContent) continue;
|
|
324
|
+
try {
|
|
325
|
+
const pageData = parseJSON<JSONPage>(pageContent);
|
|
326
|
+
if (pageData.meta?.slugs) {
|
|
327
|
+
const pageId = basePath === '/' ? 'index' : basePath.substring(1);
|
|
328
|
+
slugMappings.push({ pageId, slugs: pageData.meta.slugs });
|
|
329
|
+
}
|
|
330
|
+
} catch { /* ignore parse errors in first pass */ }
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ----------------------------------------------------------
|
|
334
|
+
// 4 & 5. Render all pages (regular + CMS templates)
|
|
335
|
+
// ----------------------------------------------------------
|
|
336
|
+
const allResults: PageRenderResult[] = [];
|
|
337
|
+
const allInteractiveStyles = new Map<string, InteractiveStyles>();
|
|
338
|
+
const allComponentCSS = new Set<string>();
|
|
339
|
+
const jsContents = new Map<string, string>(); // hash -> JS content
|
|
340
|
+
let errorCount = 0;
|
|
341
|
+
|
|
342
|
+
// Helper to merge interactive styles maps
|
|
343
|
+
function mergeInteractiveStyles(source: Map<string, InteractiveStyles>): void {
|
|
344
|
+
for (const [key, value] of source) {
|
|
345
|
+
if (!allInteractiveStyles.has(key)) {
|
|
346
|
+
allInteractiveStyles.set(key, value);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Helper to process a render result
|
|
352
|
+
function processRenderResult(
|
|
353
|
+
result: { html: string; meta: string; title: string; javascript: string; componentCSS?: string; locale: string; interactiveStylesMap: Map<string, InteractiveStyles>; preloadImages: any[]; neededCollections: Set<string> },
|
|
354
|
+
urlPath: string,
|
|
355
|
+
astroFilePath: string,
|
|
356
|
+
fileDepth: number,
|
|
357
|
+
pageData?: JSONPage,
|
|
358
|
+
pageName?: string,
|
|
359
|
+
isCMSPage?: boolean
|
|
360
|
+
): void {
|
|
361
|
+
// Collect interactive styles
|
|
362
|
+
mergeInteractiveStyles(result.interactiveStylesMap);
|
|
363
|
+
|
|
364
|
+
// Collect component CSS
|
|
365
|
+
if (result.componentCSS) {
|
|
366
|
+
allComponentCSS.add(result.componentCSS);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Deduplicate JavaScript by content hash
|
|
370
|
+
if (result.javascript) {
|
|
371
|
+
const hash = hashContent(result.javascript);
|
|
372
|
+
if (!jsContents.has(hash)) {
|
|
373
|
+
jsContents.set(hash, result.javascript);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
allResults.push({
|
|
378
|
+
html: result.html,
|
|
379
|
+
meta: result.meta,
|
|
380
|
+
title: result.title,
|
|
381
|
+
javascript: result.javascript,
|
|
382
|
+
componentCSS: result.componentCSS,
|
|
383
|
+
locale: result.locale,
|
|
384
|
+
interactiveStylesMap: result.interactiveStylesMap,
|
|
385
|
+
urlPath,
|
|
386
|
+
fileDepth,
|
|
387
|
+
astroFilePath,
|
|
388
|
+
pageData,
|
|
389
|
+
pageName,
|
|
390
|
+
isCMSPage,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// ---------- Regular pages ----------
|
|
395
|
+
for (const file of pageFiles) {
|
|
396
|
+
const pageName = file.replace('.json', '');
|
|
397
|
+
const basePath = mapPageNameToPath(pageName);
|
|
398
|
+
const pageContent = await loadJSONFile(join(pagesDir, file));
|
|
399
|
+
|
|
400
|
+
if (!pageContent) {
|
|
401
|
+
console.warn(` Skipping ${basePath} (empty file)`);
|
|
402
|
+
errorCount++;
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
try {
|
|
407
|
+
const pageData = parseJSON<JSONPage>(pageContent);
|
|
408
|
+
|
|
409
|
+
// Skip draft pages in production
|
|
410
|
+
const isDevBuild = process.env.MENO_DEV_BUILD === 'true';
|
|
411
|
+
if (pageData.meta?.draft === true && !isDevBuild) {
|
|
412
|
+
console.log(` Skipping draft: ${basePath}`);
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const slugs = pageData.meta?.slugs;
|
|
417
|
+
|
|
418
|
+
for (const localeConfig of i18nConfig.locales) {
|
|
419
|
+
const locale = localeConfig.code;
|
|
420
|
+
const isDefault = locale === i18nConfig.defaultLocale;
|
|
421
|
+
|
|
422
|
+
// Compute URL path
|
|
423
|
+
let slug: string;
|
|
424
|
+
if (slugs && slugs[locale]) {
|
|
425
|
+
slug = slugs[locale];
|
|
426
|
+
} else if (basePath === '/') {
|
|
427
|
+
slug = '';
|
|
428
|
+
} else {
|
|
429
|
+
slug = basePath.substring(1);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const urlPath = isDefault
|
|
433
|
+
? (slug === '' ? '/' : `/${slug}`)
|
|
434
|
+
: (slug === '' ? `/${locale}` : `/${locale}/${slug}`);
|
|
435
|
+
|
|
436
|
+
// Determine .astro file path relative to src/pages/
|
|
437
|
+
const astroFileName = slug === '' ? 'index.astro' : `${slug}.astro`;
|
|
438
|
+
const astroFilePath = isDefault ? astroFileName : `${locale}/${astroFileName}`;
|
|
439
|
+
const fileDepth = astroFilePath.split('/').length - 1;
|
|
440
|
+
|
|
441
|
+
const result = await renderPageSSR(
|
|
442
|
+
pageData,
|
|
443
|
+
globalComponents,
|
|
444
|
+
urlPath,
|
|
445
|
+
siteUrl,
|
|
446
|
+
locale,
|
|
447
|
+
i18nConfig,
|
|
448
|
+
slugMappings,
|
|
449
|
+
undefined, // cmsContext
|
|
450
|
+
cmsService,
|
|
451
|
+
true // isProductionBuild
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
processRenderResult(result, urlPath, astroFilePath, fileDepth, pageData, pageName, false);
|
|
455
|
+
console.log(` Rendered: ${urlPath}`);
|
|
456
|
+
}
|
|
457
|
+
} catch (error: any) {
|
|
458
|
+
console.error(` Error rendering ${basePath}:`, error?.message || error);
|
|
459
|
+
errorCount++;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// ---------- CMS template pages ----------
|
|
464
|
+
const templatesDir = projectPaths.templates();
|
|
465
|
+
const templateSchemas: CMSSchema[] = [];
|
|
466
|
+
let cmsPageCount = 0;
|
|
467
|
+
|
|
468
|
+
if (existsSync(templatesDir)) {
|
|
469
|
+
const templateFiles = readdirSync(templatesDir).filter(f => f.endsWith('.json'));
|
|
470
|
+
|
|
471
|
+
if (templateFiles.length > 0) {
|
|
472
|
+
console.log(`\nProcessing ${templateFiles.length} CMS template(s)...\n`);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
for (const file of templateFiles) {
|
|
476
|
+
const templateContent = await loadJSONFile(join(templatesDir, file));
|
|
477
|
+
if (!templateContent) continue;
|
|
478
|
+
|
|
479
|
+
try {
|
|
480
|
+
const pageData = parseJSON<JSONPage>(templateContent);
|
|
481
|
+
|
|
482
|
+
const isDevBuild = process.env.MENO_DEV_BUILD === 'true';
|
|
483
|
+
if (pageData.meta?.draft === true && !isDevBuild) {
|
|
484
|
+
console.log(` Skipping draft template: ${file}`);
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (!isCMSPage(pageData)) {
|
|
489
|
+
console.warn(` ${file} is in templates/ but missing meta.source: "cms"`);
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const cmsSchema = pageData.meta!.cms as CMSSchema;
|
|
494
|
+
templateSchemas.push(cmsSchema);
|
|
495
|
+
console.log(` CMS Collection: ${cmsSchema.id}`);
|
|
496
|
+
|
|
497
|
+
const items = await cmsService.queryItems({ collection: cmsSchema.id });
|
|
498
|
+
if (items.length === 0) {
|
|
499
|
+
console.log(` No items found in cms/${cmsSchema.id}/`);
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
console.log(` Found ${items.length} item(s)`);
|
|
504
|
+
|
|
505
|
+
for (const item of items) {
|
|
506
|
+
for (const localeConfig of i18nConfig.locales) {
|
|
507
|
+
const locale = localeConfig.code;
|
|
508
|
+
const isDefault = locale === i18nConfig.defaultLocale;
|
|
509
|
+
|
|
510
|
+
const isDevBuild = process.env.MENO_DEV_BUILD === 'true';
|
|
511
|
+
if (!isDevBuild && isItemDraftForLocale(item, locale)) {
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const itemPath = buildCMSItemPath(cmsSchema.urlPattern, item, cmsSchema.slugField, locale, i18nConfig);
|
|
516
|
+
const itemWithUrl: CMSItem = { ...item, _url: itemPath };
|
|
517
|
+
|
|
518
|
+
const result = await renderPageSSR(
|
|
519
|
+
pageData,
|
|
520
|
+
globalComponents,
|
|
521
|
+
itemPath,
|
|
522
|
+
siteUrl,
|
|
523
|
+
locale,
|
|
524
|
+
i18nConfig,
|
|
525
|
+
slugMappings,
|
|
526
|
+
{ cms: itemWithUrl },
|
|
527
|
+
cmsService,
|
|
528
|
+
true
|
|
529
|
+
);
|
|
530
|
+
|
|
531
|
+
// Determine .astro file path: strip leading slash, add .astro
|
|
532
|
+
const pathWithoutSlash = itemPath.startsWith('/') ? itemPath.substring(1) : itemPath;
|
|
533
|
+
const astroFilePath = isDefault
|
|
534
|
+
? `${pathWithoutSlash}.astro`
|
|
535
|
+
: `${locale}/${pathWithoutSlash}.astro`;
|
|
536
|
+
const fileDepth = astroFilePath.split('/').length - 1;
|
|
537
|
+
|
|
538
|
+
const urlPath = isDefault ? itemPath : `/${locale}${itemPath}`;
|
|
539
|
+
|
|
540
|
+
processRenderResult(result, urlPath, astroFilePath, fileDepth, pageData, file.replace('.json', ''), true);
|
|
541
|
+
console.log(` Rendered: ${urlPath}`);
|
|
542
|
+
cmsPageCount++;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
} catch (error: any) {
|
|
546
|
+
console.error(` Error processing template ${file}:`, error?.message || error);
|
|
547
|
+
errorCount++;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// ----------------------------------------------------------
|
|
553
|
+
// 6. Generate global CSS (Tailwind + theme + interactive styles)
|
|
554
|
+
// ----------------------------------------------------------
|
|
555
|
+
// Collect Tailwind safelist classes from mapping variants
|
|
556
|
+
const mappingClasses = collectAllMappingClasses(globalComponents, breakpoints);
|
|
557
|
+
|
|
558
|
+
const fontCSS = generateFontCSS();
|
|
559
|
+
const themeColorCSS = generateThemeColorVariablesCSS(themeConfig);
|
|
560
|
+
const variablesCSS = generateVariablesCSS(variablesConfig, breakpoints, responsiveScales);
|
|
561
|
+
const interactiveCSS = generateAllInteractiveCSS(allInteractiveStyles, breakpoints);
|
|
562
|
+
const componentCSSCombined = Array.from(allComponentCSS).join('\n');
|
|
563
|
+
|
|
564
|
+
const baseCSS = `@layer base {
|
|
565
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
566
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif; }
|
|
567
|
+
button { background: none; border: none; padding: 0; font: inherit; cursor: pointer; outline: inherit; }
|
|
568
|
+
img { display: block; width: 100%; height: 100%; }
|
|
569
|
+
picture { display: block; }
|
|
570
|
+
.olink { text-decoration: none; display: block; }
|
|
571
|
+
.oem { display: inline-block; }
|
|
572
|
+
}`;
|
|
573
|
+
|
|
574
|
+
const tailwindDirectives = `@tailwind base;
|
|
575
|
+
@tailwind components;
|
|
576
|
+
@tailwind utilities;`;
|
|
577
|
+
|
|
578
|
+
const globalCSS = [tailwindDirectives, fontCSS, themeColorCSS, variablesCSS, baseCSS, componentCSSCombined, interactiveCSS]
|
|
579
|
+
.filter(Boolean)
|
|
580
|
+
.join('\n\n');
|
|
581
|
+
|
|
582
|
+
await writeFile(join(stylesDir, 'global.css'), globalCSS, 'utf-8');
|
|
583
|
+
console.log(`\nGenerated global.css (${(globalCSS.length / 1024).toFixed(1)} KB)`);
|
|
584
|
+
|
|
585
|
+
// ----------------------------------------------------------
|
|
586
|
+
// 7. Generate BaseLayout.astro
|
|
587
|
+
// ----------------------------------------------------------
|
|
588
|
+
const fontPreloads = generateFontPreloadTags();
|
|
589
|
+
|
|
590
|
+
// Merge global + component libraries for the layout
|
|
591
|
+
const mergedLibraries = mergeLibraries(globalLibraries, componentLibraries);
|
|
592
|
+
const buildLibraries = filterLibrariesByContext(mergedLibraries, 'build');
|
|
593
|
+
const libraryTags = generateLibraryTags(buildLibraries);
|
|
594
|
+
|
|
595
|
+
const baseLayoutContent = `---
|
|
596
|
+
import '../styles/global.css';
|
|
597
|
+
|
|
598
|
+
interface Props {
|
|
599
|
+
title: string;
|
|
600
|
+
meta?: string;
|
|
601
|
+
scripts?: string[];
|
|
602
|
+
locale?: string;
|
|
603
|
+
theme?: string;
|
|
604
|
+
fontPreloads?: string;
|
|
605
|
+
libraryTags?: { headCSS?: string; headJS?: string; bodyEndJS?: string };
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const { title, meta = '', scripts = [], locale = 'en', theme = '${themeConfig.default || 'light'}', fontPreloads = '', libraryTags = {} } = Astro.props;
|
|
609
|
+
---
|
|
610
|
+
<!DOCTYPE html>
|
|
611
|
+
<html lang={locale} data-theme={theme}>
|
|
612
|
+
<head>
|
|
613
|
+
<meta charset="UTF-8">
|
|
614
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
615
|
+
<Fragment set:html={fontPreloads} />
|
|
616
|
+
<Fragment set:html={libraryTags.headCSS || ''} />
|
|
617
|
+
<Fragment set:html={libraryTags.headJS || ''} />
|
|
618
|
+
<Fragment set:html={meta} />
|
|
619
|
+
<title>{title}</title>
|
|
620
|
+
</head>
|
|
621
|
+
<body>
|
|
622
|
+
<slot />
|
|
623
|
+
{scripts.map((s) => <script src={s} />)}
|
|
624
|
+
<Fragment set:html={libraryTags.bodyEndJS || ''} />
|
|
625
|
+
</body>
|
|
626
|
+
</html>
|
|
627
|
+
`;
|
|
628
|
+
|
|
629
|
+
await writeFile(join(layoutsDir, 'BaseLayout.astro'), baseLayoutContent, 'utf-8');
|
|
630
|
+
console.log('Generated BaseLayout.astro');
|
|
631
|
+
|
|
632
|
+
// ----------------------------------------------------------
|
|
633
|
+
// 7.5. Generate component .astro files
|
|
634
|
+
// ----------------------------------------------------------
|
|
635
|
+
let componentFileCount = 0;
|
|
636
|
+
for (const [compName, compDef] of Object.entries(globalComponents)) {
|
|
637
|
+
try {
|
|
638
|
+
const astroContent = emitAstroComponent(compName, compDef, globalComponents, breakpoints);
|
|
639
|
+
await writeFile(join(componentsOutDir, `${compName}.astro`), astroContent, 'utf-8');
|
|
640
|
+
componentFileCount++;
|
|
641
|
+
} catch (error: any) {
|
|
642
|
+
console.warn(` Warning: could not generate component ${compName}: ${error?.message}`);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
console.log(`Generated ${componentFileCount} component .astro file(s)`);
|
|
646
|
+
|
|
647
|
+
// ----------------------------------------------------------
|
|
648
|
+
// 8. Generate .astro page files (component-structured)
|
|
649
|
+
// ----------------------------------------------------------
|
|
650
|
+
const defaultTheme = themeConfig.default || 'light';
|
|
651
|
+
|
|
652
|
+
for (const result of allResults) {
|
|
653
|
+
const importPath = layoutImportPath(result.fileDepth);
|
|
654
|
+
|
|
655
|
+
// Write JavaScript to public/_scripts/ if present
|
|
656
|
+
const scriptPaths: string[] = [];
|
|
657
|
+
if (result.javascript) {
|
|
658
|
+
const hash = hashContent(result.javascript);
|
|
659
|
+
const scriptFile = `${hash}.js`;
|
|
660
|
+
const scriptPublicPath = `/_scripts/${scriptFile}`;
|
|
661
|
+
|
|
662
|
+
if (!existsSync(scriptsDir)) {
|
|
663
|
+
mkdirSync(scriptsDir, { recursive: true });
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
const fullScriptPath = join(scriptsDir, scriptFile);
|
|
667
|
+
if (!existsSync(fullScriptPath)) {
|
|
668
|
+
await writeFile(fullScriptPath, result.javascript, 'utf-8');
|
|
669
|
+
}
|
|
670
|
+
scriptPaths.push(scriptPublicPath);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
let astroContent: string;
|
|
674
|
+
|
|
675
|
+
// Use component-structured emission for regular pages with page data
|
|
676
|
+
// CMS template pages keep the SSR fallback approach
|
|
677
|
+
if (result.pageData && !result.isCMSPage) {
|
|
678
|
+
try {
|
|
679
|
+
astroContent = emitAstroPage({
|
|
680
|
+
pageData: result.pageData,
|
|
681
|
+
globalComponents,
|
|
682
|
+
title: result.title,
|
|
683
|
+
meta: result.meta,
|
|
684
|
+
locale: result.locale,
|
|
685
|
+
theme: defaultTheme,
|
|
686
|
+
fontPreloads,
|
|
687
|
+
libraryTags,
|
|
688
|
+
scriptPaths,
|
|
689
|
+
layoutImportPath: importPath,
|
|
690
|
+
fileDepth: result.fileDepth,
|
|
691
|
+
ssrFallbacks: new Map(), // SSR fallbacks for complex nodes
|
|
692
|
+
pageName: result.pageName || 'index',
|
|
693
|
+
breakpoints,
|
|
694
|
+
});
|
|
695
|
+
} catch (error: any) {
|
|
696
|
+
// Fallback to SSR HTML if component emission fails
|
|
697
|
+
console.warn(` Warning: component emission failed for ${result.urlPath}, using SSR fallback: ${error?.message}`);
|
|
698
|
+
astroContent = buildSSRFallbackPage(result, importPath, fontPreloads, libraryTags, defaultTheme, scriptPaths);
|
|
699
|
+
}
|
|
700
|
+
} else {
|
|
701
|
+
// CMS pages and pages without pageData: use SSR fallback
|
|
702
|
+
astroContent = buildSSRFallbackPage(result, importPath, fontPreloads, libraryTags, defaultTheme, scriptPaths);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const astroFileFull = join(pagesOutDir, result.astroFilePath);
|
|
706
|
+
const astroFileDir = astroFileFull.substring(0, astroFileFull.lastIndexOf('/'));
|
|
707
|
+
if (!existsSync(astroFileDir)) {
|
|
708
|
+
mkdirSync(astroFileDir, { recursive: true });
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
await writeFile(astroFileFull, astroContent, 'utf-8');
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
console.log(`Generated ${allResults.length} .astro page file(s)`);
|
|
715
|
+
|
|
716
|
+
// ----------------------------------------------------------
|
|
717
|
+
// 9. Generate CMS content collections (if templates exist)
|
|
718
|
+
// ----------------------------------------------------------
|
|
719
|
+
let collectionCount = 0;
|
|
720
|
+
|
|
721
|
+
if (templateSchemas.length > 0) {
|
|
722
|
+
const contentDir = join(srcDir, 'content');
|
|
723
|
+
mkdirSync(contentDir, { recursive: true });
|
|
724
|
+
|
|
725
|
+
const collectionDefs: string[] = [];
|
|
726
|
+
|
|
727
|
+
for (const schema of templateSchemas) {
|
|
728
|
+
const collectionDir = join(contentDir, schema.id);
|
|
729
|
+
mkdirSync(collectionDir, { recursive: true });
|
|
730
|
+
|
|
731
|
+
// Copy CMS item JSON files, resolving i18n values to default locale
|
|
732
|
+
const cmsItemsDir = join(projectPaths.cms(), schema.id);
|
|
733
|
+
if (existsSync(cmsItemsDir)) {
|
|
734
|
+
const itemFiles = readdirSync(cmsItemsDir).filter(f => f.endsWith('.json'));
|
|
735
|
+
|
|
736
|
+
for (const itemFile of itemFiles) {
|
|
737
|
+
try {
|
|
738
|
+
const rawContent = await readFile(join(cmsItemsDir, itemFile), 'utf-8');
|
|
739
|
+
const item = JSON.parse(rawContent) as CMSItem;
|
|
740
|
+
|
|
741
|
+
// Resolve i18n values to the default locale
|
|
742
|
+
const resolved: Record<string, unknown> = {};
|
|
743
|
+
for (const [key, value] of Object.entries(item)) {
|
|
744
|
+
if (isI18nValue(value)) {
|
|
745
|
+
resolved[key] = resolveI18nValue(value, i18nConfig.defaultLocale, i18nConfig);
|
|
746
|
+
} else {
|
|
747
|
+
resolved[key] = value;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
await writeFile(
|
|
752
|
+
join(collectionDir, itemFile),
|
|
753
|
+
JSON.stringify(resolved, null, 2),
|
|
754
|
+
'utf-8'
|
|
755
|
+
);
|
|
756
|
+
} catch (err: any) {
|
|
757
|
+
console.warn(` Warning: could not process CMS item ${itemFile}: ${err?.message}`);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// Build Zod schema for this collection
|
|
763
|
+
const fieldDefs: string[] = [];
|
|
764
|
+
if (schema.fields) {
|
|
765
|
+
for (const [fieldName, fieldDef] of Object.entries(schema.fields)) {
|
|
766
|
+
const zodType = cmsFieldToZod(fieldDef);
|
|
767
|
+
const optional = fieldDef.required ? '' : '.optional()';
|
|
768
|
+
fieldDefs.push(` ${fieldName}: ${zodType}${optional}`);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
collectionDefs.push(` '${schema.id}': defineCollection({
|
|
773
|
+
type: 'data',
|
|
774
|
+
schema: z.object({
|
|
775
|
+
${fieldDefs.join(',\n')}
|
|
776
|
+
})
|
|
777
|
+
})`);
|
|
778
|
+
|
|
779
|
+
collectionCount++;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// Write src/content/config.ts
|
|
783
|
+
const configContent = `import { z, defineCollection } from 'astro:content';
|
|
784
|
+
|
|
785
|
+
const collections = {
|
|
786
|
+
${collectionDefs.join(',\n')}
|
|
787
|
+
};
|
|
788
|
+
|
|
789
|
+
export { collections };
|
|
790
|
+
`;
|
|
791
|
+
|
|
792
|
+
await writeFile(join(contentDir, 'config.ts'), configContent, 'utf-8');
|
|
793
|
+
console.log(`Generated ${collectionCount} content collection(s) with config.ts`);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// ----------------------------------------------------------
|
|
797
|
+
// 10. Copy assets to public/
|
|
798
|
+
// ----------------------------------------------------------
|
|
799
|
+
const assetDirs = ['fonts', 'images', 'icons', 'videos', 'assets'];
|
|
800
|
+
let copiedAssets = 0;
|
|
801
|
+
|
|
802
|
+
for (const dir of assetDirs) {
|
|
803
|
+
const srcAssetDir = join(projectPaths.project, dir);
|
|
804
|
+
if (existsSync(srcAssetDir)) {
|
|
805
|
+
copyDirectory(srcAssetDir, join(publicDir, dir));
|
|
806
|
+
copiedAssets++;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// Copy libraries folder if it exists
|
|
811
|
+
const librariesDir = join(projectPaths.project, 'libraries');
|
|
812
|
+
if (existsSync(librariesDir)) {
|
|
813
|
+
copyDirectory(librariesDir, join(publicDir, 'libraries'));
|
|
814
|
+
copiedAssets++;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
if (copiedAssets > 0) {
|
|
818
|
+
console.log(`Copied ${copiedAssets} asset director${copiedAssets === 1 ? 'y' : 'ies'} to public/`);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// ----------------------------------------------------------
|
|
822
|
+
// 11. Generate scaffold files
|
|
823
|
+
// ----------------------------------------------------------
|
|
824
|
+
|
|
825
|
+
// package.json
|
|
826
|
+
const packageJson = {
|
|
827
|
+
name: 'astro-export',
|
|
828
|
+
type: 'module',
|
|
829
|
+
version: '0.0.1',
|
|
830
|
+
private: true,
|
|
831
|
+
scripts: {
|
|
832
|
+
dev: 'astro dev',
|
|
833
|
+
start: 'astro dev',
|
|
834
|
+
build: 'astro build',
|
|
835
|
+
preview: 'astro preview',
|
|
836
|
+
},
|
|
837
|
+
dependencies: {
|
|
838
|
+
'astro': '^4.0.0',
|
|
839
|
+
'@astrojs/sitemap': '^3.0.0',
|
|
840
|
+
'@astrojs/tailwind': '^5.0.0',
|
|
841
|
+
'tailwindcss': '^3.4.0',
|
|
842
|
+
},
|
|
843
|
+
};
|
|
844
|
+
|
|
845
|
+
await writeFile(join(outDir, 'package.json'), JSON.stringify(packageJson, null, 2), 'utf-8');
|
|
846
|
+
|
|
847
|
+
// astro.config.mjs
|
|
848
|
+
const astroConfig = `import { defineConfig } from 'astro/config';
|
|
849
|
+
import sitemap from '@astrojs/sitemap';
|
|
850
|
+
import tailwind from '@astrojs/tailwind';
|
|
851
|
+
|
|
852
|
+
export default defineConfig({${siteUrl ? `\n site: '${siteUrl}',` : ''}
|
|
853
|
+
integrations: [sitemap(), tailwind({ applyBaseStyles: false })],
|
|
854
|
+
});
|
|
855
|
+
`;
|
|
856
|
+
|
|
857
|
+
// tailwind.config.mjs
|
|
858
|
+
const safelistArray = Array.from(mappingClasses);
|
|
859
|
+
const safelistLiteral = safelistArray.length > 0
|
|
860
|
+
? `\n safelist: [\n${safelistArray.map(c => ` '${c}'`).join(',\n')}\n ],`
|
|
861
|
+
: '';
|
|
862
|
+
|
|
863
|
+
const tailwindConfig = `/** @type {import('tailwindcss').Config} */
|
|
864
|
+
export default {
|
|
865
|
+
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],${safelistLiteral}
|
|
866
|
+
theme: {
|
|
867
|
+
extend: {},
|
|
868
|
+
},
|
|
869
|
+
plugins: [],
|
|
870
|
+
};
|
|
871
|
+
`;
|
|
872
|
+
|
|
873
|
+
await writeFile(join(outDir, 'tailwind.config.mjs'), tailwindConfig, 'utf-8');
|
|
874
|
+
|
|
875
|
+
await writeFile(join(outDir, 'astro.config.mjs'), astroConfig, 'utf-8');
|
|
876
|
+
|
|
877
|
+
// tsconfig.json
|
|
878
|
+
const tsConfig = {
|
|
879
|
+
extends: 'astro/tsconfigs/strict',
|
|
880
|
+
};
|
|
881
|
+
|
|
882
|
+
await writeFile(join(outDir, 'tsconfig.json'), JSON.stringify(tsConfig, null, 2), 'utf-8');
|
|
883
|
+
|
|
884
|
+
console.log('Generated package.json, astro.config.mjs, tailwind.config.mjs, tsconfig.json');
|
|
885
|
+
|
|
886
|
+
// ----------------------------------------------------------
|
|
887
|
+
// 12. Summary
|
|
888
|
+
// ----------------------------------------------------------
|
|
889
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
890
|
+
const totalPages = allResults.length;
|
|
891
|
+
|
|
892
|
+
console.log('\n' + '='.repeat(50));
|
|
893
|
+
console.log('Astro export complete!');
|
|
894
|
+
console.log(` Pages: ${totalPages - cmsPageCount}`);
|
|
895
|
+
if (cmsPageCount > 0) {
|
|
896
|
+
console.log(` CMS pages: ${cmsPageCount}`);
|
|
897
|
+
}
|
|
898
|
+
if (collectionCount > 0) {
|
|
899
|
+
console.log(` Content collections: ${collectionCount}`);
|
|
900
|
+
}
|
|
901
|
+
if (errorCount > 0) {
|
|
902
|
+
console.log(` Errors: ${errorCount}`);
|
|
903
|
+
}
|
|
904
|
+
console.log(` Time: ${elapsed}s`);
|
|
905
|
+
console.log(` Output: ${outDir}`);
|
|
906
|
+
console.log('');
|
|
907
|
+
|
|
908
|
+
return {
|
|
909
|
+
pages: totalPages - cmsPageCount,
|
|
910
|
+
cmsPages: cmsPageCount,
|
|
911
|
+
collections: collectionCount,
|
|
912
|
+
errors: errorCount,
|
|
913
|
+
};
|
|
914
|
+
}
|