meno-core 1.0.45 → 1.0.47
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 +211 -124
- package/dist/bin/cli.js +2 -2
- package/dist/build-static.js +7 -7
- package/dist/chunks/{chunk-NZTSJS5C.js → chunk-2QK6U5UK.js} +3 -2
- package/dist/chunks/{chunk-NZTSJS5C.js.map → chunk-2QK6U5UK.js.map} +2 -2
- package/dist/chunks/{chunk-TVH3TC2T.js → chunk-47UNLQUU.js} +6 -6
- package/dist/chunks/{chunk-F7MA62WG.js → chunk-BCLGRZ3U.js} +5 -5
- package/dist/chunks/{chunk-F7MA62WG.js.map → chunk-BCLGRZ3U.js.map} +2 -2
- package/dist/chunks/{chunk-5ZASE4IG.js → chunk-FED5MME6.js} +234 -11
- package/dist/chunks/{chunk-5ZASE4IG.js.map → chunk-FED5MME6.js.map} +3 -3
- package/dist/chunks/{chunk-BZQKEJQY.js → chunk-FGUZOYJX.js} +49 -30
- package/dist/chunks/chunk-FGUZOYJX.js.map +7 -0
- package/dist/chunks/{chunk-5Z5VQRTJ.js → chunk-I7YIGZXT.js} +4 -4
- package/dist/chunks/{chunk-5Z5VQRTJ.js.map → chunk-I7YIGZXT.js.map} +2 -2
- package/dist/chunks/{chunk-OUNJ76QM.js → chunk-LJFB5EBT.js} +5 -5
- package/dist/chunks/{chunk-GYF3ABI3.js → chunk-UUA5LEWF.js} +3 -3
- package/dist/chunks/{chunk-GYF3ABI3.js.map → chunk-UUA5LEWF.js.map} +2 -2
- package/dist/chunks/{chunk-WQSG5WHC.js → chunk-ZTKHJQ2Z.js} +2 -2
- package/dist/chunks/{configService-6KTT6GRT.js → configService-DYCUEURL.js} +3 -3
- package/dist/chunks/{constants-L5IKLB6U.js → constants-GWBAD66U.js} +2 -2
- package/dist/entries/server-router.js +7 -7
- package/dist/lib/client/index.js +7 -5
- package/dist/lib/client/index.js.map +2 -2
- package/dist/lib/server/index.js +631 -208
- package/dist/lib/server/index.js.map +3 -3
- package/dist/lib/shared/index.js +7 -3
- package/dist/lib/shared/index.js.map +2 -2
- package/dist/lib/test-utils/index.js +1 -1
- package/lib/client/core/ComponentBuilder.test.ts +21 -0
- package/lib/client/core/ComponentBuilder.ts +8 -1
- package/lib/client/templateEngine.test.ts +64 -0
- package/lib/server/astro/astroEmitHelpers.ts +23 -0
- package/lib/server/astro/cmsPageEmitter.ts +46 -3
- package/lib/server/astro/componentEmitter.test.ts +59 -0
- package/lib/server/astro/componentEmitter.ts +53 -12
- package/lib/server/astro/cssCollector.ts +58 -11
- package/lib/server/astro/nodeToAstro.test.ts +397 -5
- package/lib/server/astro/nodeToAstro.ts +494 -65
- package/lib/server/astro/pageEmitter.ts +46 -3
- package/lib/server/astro/tailwindMapper.test.ts +119 -0
- package/lib/server/astro/tailwindMapper.ts +67 -1
- package/lib/server/runtime/httpServer.ts +12 -4
- package/lib/server/ssr/htmlGenerator.test.ts +3 -2
- package/lib/server/ssr/htmlGenerator.ts +6 -1
- package/lib/server/ssr/imageMetadata.ts +15 -9
- package/lib/server/ssr/jsCollector.ts +2 -2
- package/lib/server/ssr/ssrRenderer.test.ts +79 -0
- package/lib/server/ssr/ssrRenderer.ts +35 -20
- package/lib/shared/constants.ts +1 -0
- package/lib/shared/cssGeneration.test.ts +109 -3
- package/lib/shared/cssGeneration.ts +98 -13
- package/lib/shared/cssNamedColors.ts +47 -0
- package/lib/shared/cssProperties.ts +2 -2
- package/lib/shared/index.ts +1 -0
- package/lib/shared/styleNodeUtils.test.ts +47 -1
- package/lib/shared/styleNodeUtils.ts +7 -7
- package/package.json +1 -1
- package/dist/chunks/chunk-BZQKEJQY.js.map +0 -7
- /package/dist/chunks/{chunk-TVH3TC2T.js.map → chunk-47UNLQUU.js.map} +0 -0
- /package/dist/chunks/{chunk-OUNJ76QM.js.map → chunk-LJFB5EBT.js.map} +0 -0
- /package/dist/chunks/{chunk-WQSG5WHC.js.map → chunk-ZTKHJQ2Z.js.map} +0 -0
- /package/dist/chunks/{configService-6KTT6GRT.js.map → configService-DYCUEURL.js.map} +0 -0
- /package/dist/chunks/{constants-L5IKLB6U.js.map → constants-GWBAD66U.js.map} +0 -0
package/build-astro.ts
CHANGED
|
@@ -29,11 +29,10 @@ import { isItemDraftForLocale } from "./lib/shared/types";
|
|
|
29
29
|
import type { SlugMap } from "./lib/shared/slugTranslator";
|
|
30
30
|
import { renderPageSSR } from "./lib/server/ssr/ssrRenderer";
|
|
31
31
|
import { generateThemeColorVariablesCSS, generateVariablesCSS } from "./lib/server/cssGenerator";
|
|
32
|
-
import { generateAllInteractiveCSS } from "./lib/shared/cssGeneration";
|
|
33
32
|
import { colorService } from "./lib/server/services/ColorService";
|
|
34
33
|
import { variableService } from "./lib/server/services/VariableService";
|
|
35
34
|
import { configService } from "./lib/server/services/configService";
|
|
36
|
-
import { loadBreakpointConfig, loadResponsiveScalesConfig } from "./lib/server/jsonLoader";
|
|
35
|
+
import { loadBreakpointConfig, loadResponsiveScalesConfig, loadIconsConfig } from "./lib/server/jsonLoader";
|
|
37
36
|
import type { InteractiveStyles } from "./lib/shared/types/styles";
|
|
38
37
|
import { collectComponentLibraries, filterLibrariesByContext, mergeLibraries, generateLibraryTags } from "./lib/shared/libraryLoader";
|
|
39
38
|
import { migrateTemplatesDirectory } from "./lib/server/migrateTemplates";
|
|
@@ -41,7 +40,8 @@ import { emitAstroComponent } from "./lib/server/astro/componentEmitter";
|
|
|
41
40
|
import { emitAstroPage } from "./lib/server/astro/pageEmitter";
|
|
42
41
|
import { emitCMSPage } from './lib/server/astro/cmsPageEmitter';
|
|
43
42
|
import { collectAllMappingClasses } from "./lib/server/astro/cssCollector";
|
|
44
|
-
import { buildImageMetadataMap } from "./lib/server/ssr/imageMetadata";
|
|
43
|
+
import { buildImageMetadataMap, RESPONSIVE_WIDTHS } from "./lib/server/ssr/imageMetadata";
|
|
44
|
+
import { needsFormHandler, formHandlerScript } from "./lib/client/scripts/formHandler";
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
// ---------------------------------------------------------------------------
|
|
@@ -52,19 +52,50 @@ function hashContent(content: string): string {
|
|
|
52
52
|
return createHash('sha256').update(content).digest('hex').slice(0, 8);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
function
|
|
55
|
+
function writePageScript(javascript: string | undefined, scriptsDir: string): string[] {
|
|
56
|
+
if (!javascript) return [];
|
|
57
|
+
const hash = hashContent(javascript);
|
|
58
|
+
const scriptFile = `${hash}.js`;
|
|
59
|
+
if (!existsSync(scriptsDir)) {
|
|
60
|
+
mkdirSync(scriptsDir, { recursive: true });
|
|
61
|
+
}
|
|
62
|
+
const fullScriptPath = join(scriptsDir, scriptFile);
|
|
63
|
+
if (!existsSync(fullScriptPath)) {
|
|
64
|
+
writeFileSync(fullScriptPath, javascript, 'utf-8');
|
|
65
|
+
}
|
|
66
|
+
return [`/_scripts/${scriptFile}`];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function copyDirectory(
|
|
70
|
+
src: string,
|
|
71
|
+
dest: string,
|
|
72
|
+
filter?: (filename: string) => boolean,
|
|
73
|
+
): void {
|
|
56
74
|
if (!existsSync(src)) return;
|
|
57
75
|
if (!existsSync(dest)) mkdirSync(dest, { recursive: true });
|
|
58
76
|
const files = readdirSync(src);
|
|
59
77
|
for (const file of files) {
|
|
78
|
+
if (filter && !filter(file)) continue;
|
|
60
79
|
const srcPath = join(src, file);
|
|
61
80
|
const destPath = join(dest, file);
|
|
62
81
|
const stat = statSync(srcPath);
|
|
63
|
-
if (stat.isDirectory()) copyDirectory(srcPath, destPath);
|
|
82
|
+
if (stat.isDirectory()) copyDirectory(srcPath, destPath, filter);
|
|
64
83
|
else copyFileSync(srcPath, destPath);
|
|
65
84
|
}
|
|
66
85
|
}
|
|
67
86
|
|
|
87
|
+
// Astro's <Picture> re-derives responsive variants from originals at build
|
|
88
|
+
// time, so the pre-baked -{width}.webp/.avif files and the SSR-only
|
|
89
|
+
// manifest.json are dead weight in the exported project.
|
|
90
|
+
const imageVariantSuffixRe = new RegExp(
|
|
91
|
+
`-(${RESPONSIVE_WIDTHS.join('|')})\\.(webp|avif)$`,
|
|
92
|
+
);
|
|
93
|
+
function shouldCopyImageForAstro(filename: string): boolean {
|
|
94
|
+
if (filename === 'manifest.json') return false;
|
|
95
|
+
if (imageVariantSuffixRe.test(filename)) return false;
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
|
|
68
99
|
function isCMSPage(pageData: JSONPage): boolean {
|
|
69
100
|
return pageData.meta?.source === 'cms' && !!pageData.meta?.cms;
|
|
70
101
|
}
|
|
@@ -208,6 +239,8 @@ interface PageRenderResult {
|
|
|
208
239
|
isCMSPage?: boolean;
|
|
209
240
|
/** SSR fallback HTML for complex nodes (list, locale-list) keyed by element path */
|
|
210
241
|
ssrFallbackCollector?: Map<string, string>;
|
|
242
|
+
/** Raw-HTML slice → processed HTML captured during SSR (for Astro exporter parity) */
|
|
243
|
+
processedRawHtmlCollector?: Map<string, string>;
|
|
211
244
|
}
|
|
212
245
|
|
|
213
246
|
interface AstroBuildStats {
|
|
@@ -264,10 +297,6 @@ export async function buildAstroProject(
|
|
|
264
297
|
projectRoot?: string,
|
|
265
298
|
outputDir?: string
|
|
266
299
|
): Promise<AstroBuildStats> {
|
|
267
|
-
const startTime = Date.now();
|
|
268
|
-
|
|
269
|
-
console.log('🏗️ Building Astro export...\n');
|
|
270
|
-
|
|
271
300
|
// ----------------------------------------------------------
|
|
272
301
|
// 1. Setup: load project configuration
|
|
273
302
|
// ----------------------------------------------------------
|
|
@@ -277,7 +306,6 @@ export async function buildAstroProject(
|
|
|
277
306
|
const siteUrl = (projectConfig as { siteUrl?: string }).siteUrl?.replace(/\/$/, '') || '';
|
|
278
307
|
|
|
279
308
|
const i18nConfig = await loadI18nConfig();
|
|
280
|
-
console.log(`🌐 Locales: ${i18nConfig.locales.map(l => l.code).join(', ')} (default: ${i18nConfig.defaultLocale})\n`);
|
|
281
309
|
|
|
282
310
|
await migrateTemplatesDirectory();
|
|
283
311
|
|
|
@@ -286,12 +314,10 @@ export async function buildAstroProject(
|
|
|
286
314
|
components.forEach((value, key) => { globalComponents[key] = value; });
|
|
287
315
|
for (const w of warnings) console.warn(` Warning: ${w}`);
|
|
288
316
|
for (const e of compErrors) console.error(` Error: ${e}`);
|
|
289
|
-
console.log(`Loaded ${components.size} global component(s)\n`);
|
|
290
317
|
|
|
291
318
|
const cmsProvider = new FileSystemCMSProvider(projectPaths.templates(), projectPaths.cms());
|
|
292
319
|
const cmsService = new CMSService(cmsProvider);
|
|
293
320
|
await cmsService.initialize();
|
|
294
|
-
console.log('CMS service initialized\n');
|
|
295
321
|
|
|
296
322
|
const themeConfig = await colorService.loadThemeConfig();
|
|
297
323
|
const variablesConfig = await variableService.loadConfig();
|
|
@@ -305,9 +331,6 @@ export async function buildAstroProject(
|
|
|
305
331
|
|
|
306
332
|
// Build image metadata map for responsive image generation
|
|
307
333
|
const imageMetadataMap = await buildImageMetadataMap();
|
|
308
|
-
if (imageMetadataMap.size > 0) {
|
|
309
|
-
console.log(`Loaded image metadata for ${imageMetadataMap.size} image(s)\n`);
|
|
310
|
-
}
|
|
311
334
|
|
|
312
335
|
// ----------------------------------------------------------
|
|
313
336
|
// 2. Clean and create output directory
|
|
@@ -346,8 +369,6 @@ export async function buildAstroProject(
|
|
|
346
369
|
return { pages: 0, cmsPages: 0, collections: 0, errors: 0 };
|
|
347
370
|
}
|
|
348
371
|
|
|
349
|
-
console.log(`Found ${pageFiles.length} page(s) to process\n`);
|
|
350
|
-
|
|
351
372
|
// Collect slug mappings (first pass)
|
|
352
373
|
const slugMappings: SlugMap[] = [];
|
|
353
374
|
for (const file of pageFiles) {
|
|
@@ -372,6 +393,7 @@ export async function buildAstroProject(
|
|
|
372
393
|
const allComponentCSS = new Set<string>();
|
|
373
394
|
const jsContents = new Map<string, string>(); // hash -> JS content
|
|
374
395
|
let errorCount = 0;
|
|
396
|
+
let projectNeedsFormHandler = false;
|
|
375
397
|
|
|
376
398
|
// Helper to merge interactive styles maps
|
|
377
399
|
function mergeInteractiveStyles(source: Map<string, InteractiveStyles>): void {
|
|
@@ -384,7 +406,7 @@ export async function buildAstroProject(
|
|
|
384
406
|
|
|
385
407
|
// Helper to process a render result
|
|
386
408
|
function processRenderResult(
|
|
387
|
-
result: { html: string; meta: string; title: string; javascript: string; componentCSS?: string; locale: string; interactiveStylesMap: Map<string, InteractiveStyles>; preloadImages: any[]; neededCollections: Set<string>; ssrFallbackCollector?: Map<string, string> },
|
|
409
|
+
result: { html: string; meta: string; title: string; javascript: string; componentCSS?: string; locale: string; interactiveStylesMap: Map<string, InteractiveStyles>; preloadImages: any[]; neededCollections: Set<string>; ssrFallbackCollector?: Map<string, string>; processedRawHtmlCollector?: Map<string, string> },
|
|
388
410
|
urlPath: string,
|
|
389
411
|
astroFilePath: string,
|
|
390
412
|
fileDepth: number,
|
|
@@ -408,6 +430,11 @@ export async function buildAstroProject(
|
|
|
408
430
|
}
|
|
409
431
|
}
|
|
410
432
|
|
|
433
|
+
// Detect forms that need the fetch handler
|
|
434
|
+
if (!projectNeedsFormHandler && needsFormHandler(result.html)) {
|
|
435
|
+
projectNeedsFormHandler = true;
|
|
436
|
+
}
|
|
437
|
+
|
|
411
438
|
allResults.push({
|
|
412
439
|
html: result.html,
|
|
413
440
|
meta: result.meta,
|
|
@@ -423,6 +450,7 @@ export async function buildAstroProject(
|
|
|
423
450
|
pageName,
|
|
424
451
|
isCMSPage,
|
|
425
452
|
ssrFallbackCollector: result.ssrFallbackCollector,
|
|
453
|
+
processedRawHtmlCollector: result.processedRawHtmlCollector,
|
|
426
454
|
});
|
|
427
455
|
}
|
|
428
456
|
|
|
@@ -444,7 +472,6 @@ export async function buildAstroProject(
|
|
|
444
472
|
// Skip draft pages in production
|
|
445
473
|
const isDevBuild = process.env.MENO_DEV_BUILD === 'true';
|
|
446
474
|
if (pageData.meta?.draft === true && !isDevBuild) {
|
|
447
|
-
console.log(` Skipping draft: ${basePath}`);
|
|
448
475
|
continue;
|
|
449
476
|
}
|
|
450
477
|
|
|
@@ -487,7 +514,6 @@ export async function buildAstroProject(
|
|
|
487
514
|
);
|
|
488
515
|
|
|
489
516
|
processRenderResult(result, urlPath, astroFilePath, fileDepth, pageData, pageName, false);
|
|
490
|
-
console.log(` Rendered: ${urlPath}`);
|
|
491
517
|
}
|
|
492
518
|
} catch (error: any) {
|
|
493
519
|
console.error(` Error rendering ${basePath}:`, error?.message || error);
|
|
@@ -499,9 +525,56 @@ export async function buildAstroProject(
|
|
|
499
525
|
const fontPreloads = generateFontPreloadTags();
|
|
500
526
|
const mergedLibraries = mergeLibraries(globalLibraries, componentLibraries);
|
|
501
527
|
const buildLibraries = filterLibrariesByContext(mergedLibraries, 'build');
|
|
502
|
-
|
|
528
|
+
|
|
529
|
+
// Mirror htmlGenerator.ts: local CSS libraries with `inline !== false` are inlined
|
|
530
|
+
// into a <style> tag, otherwise the file is copied to public/ so the <link href>
|
|
531
|
+
// resolves. This keeps any `/some-file.css` referenced in project.config.json
|
|
532
|
+
// libraries working, not just the special `custom.css` case.
|
|
533
|
+
const inlineContents = new Map<string, string>();
|
|
534
|
+
const localLibsToCopy: string[] = [];
|
|
535
|
+
for (const css of buildLibraries.css || []) {
|
|
536
|
+
if (!css.url.startsWith('/')) continue;
|
|
537
|
+
const shouldInline = css.inline !== false;
|
|
538
|
+
const relPath = css.url.slice(1);
|
|
539
|
+
const srcPath = join(projectPaths.project, relPath);
|
|
540
|
+
if (!existsSync(srcPath)) continue;
|
|
541
|
+
if (shouldInline) {
|
|
542
|
+
try {
|
|
543
|
+
inlineContents.set(css.url, await readFile(srcPath, 'utf-8'));
|
|
544
|
+
} catch {
|
|
545
|
+
localLibsToCopy.push(relPath);
|
|
546
|
+
}
|
|
547
|
+
} else {
|
|
548
|
+
localLibsToCopy.push(relPath);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
for (const js of buildLibraries.js || []) {
|
|
552
|
+
if (js.url.startsWith('/')) {
|
|
553
|
+
const relPath = js.url.slice(1);
|
|
554
|
+
if (existsSync(join(projectPaths.project, relPath))) {
|
|
555
|
+
localLibsToCopy.push(relPath);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
const libraryTags = generateLibraryTags(buildLibraries, inlineContents);
|
|
503
560
|
const defaultTheme = themeConfig.default || 'light';
|
|
504
561
|
|
|
562
|
+
// Global customCode (head/bodyStart/bodyEnd) and icons from project.config.json
|
|
563
|
+
// are page-independent, so we bake them directly into BaseLayout.astro rather
|
|
564
|
+
// than plumbing them through every page's props. Mirrors htmlGenerator.ts's
|
|
565
|
+
// SSR output (htmlGenerator.ts:269-507 for customCode, 368-473 for icons).
|
|
566
|
+
const customCode = configService.getCustomCode();
|
|
567
|
+
const iconsConfig = await loadIconsConfig();
|
|
568
|
+
const faviconTag = iconsConfig.favicon
|
|
569
|
+
? `<link rel="icon" href="${iconsConfig.favicon.replace(/"/g, '"')}" />`
|
|
570
|
+
: '';
|
|
571
|
+
const appleTouchIconTag = iconsConfig.appleTouchIcon
|
|
572
|
+
? `<link rel="apple-touch-icon" href="${iconsConfig.appleTouchIcon.replace(/"/g, '"')}" />`
|
|
573
|
+
: '';
|
|
574
|
+
const iconTagsHtml = [faviconTag, appleTouchIconTag].filter(Boolean).join('\n ');
|
|
575
|
+
|
|
576
|
+
const remConversionConfig = configService.getRemConversion();
|
|
577
|
+
|
|
505
578
|
// ---------- CMS template pages ----------
|
|
506
579
|
const templatesDir = projectPaths.templates();
|
|
507
580
|
const templateSchemas: CMSSchema[] = [];
|
|
@@ -510,10 +583,6 @@ export async function buildAstroProject(
|
|
|
510
583
|
if (existsSync(templatesDir)) {
|
|
511
584
|
const templateFiles = readdirSync(templatesDir).filter(f => f.endsWith('.json'));
|
|
512
585
|
|
|
513
|
-
if (templateFiles.length > 0) {
|
|
514
|
-
console.log(`\nProcessing ${templateFiles.length} CMS template(s)...\n`);
|
|
515
|
-
}
|
|
516
|
-
|
|
517
586
|
for (const file of templateFiles) {
|
|
518
587
|
const templateContent = await loadJSONFile(join(templatesDir, file));
|
|
519
588
|
if (!templateContent) continue;
|
|
@@ -523,7 +592,6 @@ export async function buildAstroProject(
|
|
|
523
592
|
|
|
524
593
|
const isDevBuild = process.env.MENO_DEV_BUILD === 'true';
|
|
525
594
|
if (pageData.meta?.draft === true && !isDevBuild) {
|
|
526
|
-
console.log(` Skipping draft template: ${file}`);
|
|
527
595
|
continue;
|
|
528
596
|
}
|
|
529
597
|
|
|
@@ -534,18 +602,11 @@ export async function buildAstroProject(
|
|
|
534
602
|
|
|
535
603
|
const cmsSchema = pageData.meta!.cms as CMSSchema;
|
|
536
604
|
templateSchemas.push(cmsSchema);
|
|
537
|
-
console.log(` CMS Collection: ${cmsSchema.id}`);
|
|
538
605
|
|
|
539
606
|
// Count items for stats
|
|
540
607
|
const items = await cmsService.queryItems({ collection: cmsSchema.id });
|
|
541
608
|
const itemCount = items.length;
|
|
542
609
|
|
|
543
|
-
if (itemCount === 0) {
|
|
544
|
-
console.log(` No items found in cms/${cmsSchema.id}/`);
|
|
545
|
-
} else {
|
|
546
|
-
console.log(` Found ${itemCount} item(s)`);
|
|
547
|
-
}
|
|
548
|
-
|
|
549
610
|
// Render SSR once for metadata collection (interactive styles, component CSS, JS)
|
|
550
611
|
const defaultLocale = i18nConfig.defaultLocale;
|
|
551
612
|
const dummyPath = cmsSchema.urlPattern.replace('{{slug}}', '__placeholder__');
|
|
@@ -626,11 +687,14 @@ export async function buildAstroProject(
|
|
|
626
687
|
ssrFallbacks,
|
|
627
688
|
pageName: file.replace('.json', ''),
|
|
628
689
|
breakpoints,
|
|
690
|
+
responsiveScales,
|
|
629
691
|
imageMetadataMap,
|
|
630
692
|
i18nConfig,
|
|
631
693
|
isMultiLocale: false, // Each file handles one locale
|
|
632
694
|
slugMappings,
|
|
633
695
|
imageFormat: configService.getImageFormat(),
|
|
696
|
+
processedRawHtml: metaResult.processedRawHtmlCollector,
|
|
697
|
+
remConfig: remConversionConfig,
|
|
634
698
|
});
|
|
635
699
|
|
|
636
700
|
const astroFileFull = join(pagesOutDir, astroFilePath);
|
|
@@ -642,8 +706,6 @@ export async function buildAstroProject(
|
|
|
642
706
|
await writeFile(astroFileFull, astroContent, 'utf-8');
|
|
643
707
|
}
|
|
644
708
|
|
|
645
|
-
console.log(` Generated: ${pathPrefix}[slug].astro (${itemCount} items × ${localesToEmit.length} locale(s))`);
|
|
646
|
-
|
|
647
709
|
cmsPageCount += itemCount * i18nConfig.locales.length;
|
|
648
710
|
} catch (error: any) {
|
|
649
711
|
console.error(` Error processing template ${file}:`, error?.message || error);
|
|
@@ -656,39 +718,56 @@ export async function buildAstroProject(
|
|
|
656
718
|
// 6. Generate global CSS (Tailwind + theme + interactive styles)
|
|
657
719
|
// ----------------------------------------------------------
|
|
658
720
|
// Collect Tailwind safelist classes from mapping variants
|
|
659
|
-
const mappingClasses = collectAllMappingClasses(globalComponents, breakpoints);
|
|
721
|
+
const mappingClasses = collectAllMappingClasses(globalComponents, breakpoints, responsiveScales);
|
|
660
722
|
|
|
661
723
|
const fontCSS = generateFontCSS();
|
|
662
724
|
const themeColorCSS = generateThemeColorVariablesCSS(themeConfig);
|
|
663
725
|
const variablesCSS = generateVariablesCSS(variablesConfig, breakpoints, responsiveScales);
|
|
664
|
-
const remConversionConfig = configService.getRemConversion();
|
|
665
|
-
const interactiveCSS = generateAllInteractiveCSS(allInteractiveStyles, breakpoints, remConversionConfig);
|
|
666
726
|
const componentCSSCombined = Array.from(allComponentCSS).join('\n');
|
|
667
727
|
|
|
668
728
|
const baseCSS = `@layer base {
|
|
669
729
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
670
730
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif; }
|
|
671
731
|
button { background: none; border: none; padding: 0; font: inherit; cursor: pointer; outline: inherit; }
|
|
672
|
-
img {
|
|
732
|
+
img { max-width: 100%; height: auto; }
|
|
673
733
|
picture { display: block; }
|
|
674
|
-
.olink { text-decoration: none; display: block; }
|
|
734
|
+
.olink { text-decoration: none; display: block; color: inherit; }
|
|
675
735
|
.oem { display: inline-block; }
|
|
676
736
|
}`;
|
|
677
737
|
|
|
678
|
-
const
|
|
679
|
-
|
|
680
|
-
|
|
738
|
+
const safelistClasses = Array.from(mappingClasses);
|
|
739
|
+
const safelistDirectives = safelistClasses
|
|
740
|
+
.map(c => `@source inline("${c}");`)
|
|
741
|
+
.join('\n');
|
|
742
|
+
const tailwindDirectives = safelistDirectives
|
|
743
|
+
? `@import "tailwindcss";\n\n${safelistDirectives}`
|
|
744
|
+
: `@import "tailwindcss";`;
|
|
681
745
|
|
|
682
|
-
const globalCSS = [tailwindDirectives, fontCSS, themeColorCSS, variablesCSS, baseCSS, componentCSSCombined
|
|
746
|
+
const globalCSS = [tailwindDirectives, fontCSS, themeColorCSS, variablesCSS, baseCSS, componentCSSCombined]
|
|
683
747
|
.filter(Boolean)
|
|
684
748
|
.join('\n\n');
|
|
685
749
|
|
|
686
750
|
await writeFile(join(stylesDir, 'global.css'), globalCSS, 'utf-8');
|
|
687
|
-
console.log(`\nGenerated global.css (${(globalCSS.length / 1024).toFixed(1)} KB)`);
|
|
688
751
|
|
|
689
752
|
// ----------------------------------------------------------
|
|
690
753
|
// 7. Generate BaseLayout.astro
|
|
691
754
|
// ----------------------------------------------------------
|
|
755
|
+
// Escape for embedding inside Astro <Fragment set:html={`...`}> template
|
|
756
|
+
// literals in the generated BaseLayout file.
|
|
757
|
+
const escForTemplateLiteral = (s: string) => s
|
|
758
|
+
.replace(/\\/g, '\\\\')
|
|
759
|
+
.replace(/`/g, '\\`')
|
|
760
|
+
.replace(/\$\{/g, '\\${');
|
|
761
|
+
|
|
762
|
+
const customHeadLiteral = escForTemplateLiteral(customCode.head || '');
|
|
763
|
+
const customBodyStartLiteral = escForTemplateLiteral(customCode.bodyStart || '');
|
|
764
|
+
const customBodyEndLiteral = escForTemplateLiteral(customCode.bodyEnd || '');
|
|
765
|
+
const iconTagsLiteral = escForTemplateLiteral(iconTagsHtml);
|
|
766
|
+
|
|
767
|
+
const formHandlerBlock = projectNeedsFormHandler
|
|
768
|
+
? `\n <script is:inline>\n${formHandlerScript}\n </script>`
|
|
769
|
+
: '';
|
|
770
|
+
|
|
692
771
|
const baseLayoutContent = `---
|
|
693
772
|
import '../styles/global.css';
|
|
694
773
|
|
|
@@ -709,22 +788,25 @@ const { title, meta = '', scripts = [], locale = 'en', theme = '${themeConfig.de
|
|
|
709
788
|
<head>
|
|
710
789
|
<meta charset="UTF-8">
|
|
711
790
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
791
|
+
<Fragment set:html={\`${iconTagsLiteral}\`} />
|
|
712
792
|
<Fragment set:html={fontPreloads} />
|
|
713
793
|
<Fragment set:html={libraryTags.headCSS || ''} />
|
|
714
794
|
<Fragment set:html={libraryTags.headJS || ''} />
|
|
715
795
|
<Fragment set:html={meta} />
|
|
796
|
+
<Fragment set:html={\`${customHeadLiteral}\`} />
|
|
716
797
|
<title>{title}</title>
|
|
717
798
|
</head>
|
|
718
799
|
<body>
|
|
800
|
+
<Fragment set:html={\`${customBodyStartLiteral}\`} />
|
|
719
801
|
<slot />
|
|
720
802
|
{scripts.map((s) => <script src={s} />)}
|
|
721
803
|
<Fragment set:html={libraryTags.bodyEndJS || ''} />
|
|
804
|
+
<Fragment set:html={\`${customBodyEndLiteral}\`} />${formHandlerBlock}
|
|
722
805
|
</body>
|
|
723
806
|
</html>
|
|
724
807
|
`;
|
|
725
808
|
|
|
726
809
|
await writeFile(join(layoutsDir, 'BaseLayout.astro'), baseLayoutContent, 'utf-8');
|
|
727
|
-
console.log('Generated BaseLayout.astro');
|
|
728
810
|
|
|
729
811
|
// ----------------------------------------------------------
|
|
730
812
|
// 7.5. Generate component .astro files
|
|
@@ -732,38 +814,21 @@ const { title, meta = '', scripts = [], locale = 'en', theme = '${themeConfig.de
|
|
|
732
814
|
let componentFileCount = 0;
|
|
733
815
|
for (const [compName, compDef] of Object.entries(globalComponents)) {
|
|
734
816
|
try {
|
|
735
|
-
const astroContent = emitAstroComponent(compName, compDef, globalComponents, breakpoints, i18nConfig.defaultLocale);
|
|
817
|
+
const astroContent = emitAstroComponent(compName, compDef, globalComponents, breakpoints, i18nConfig.defaultLocale, responsiveScales, remConversionConfig);
|
|
736
818
|
await writeFile(join(componentsOutDir, `${compName}.astro`), astroContent, 'utf-8');
|
|
737
819
|
componentFileCount++;
|
|
738
820
|
} catch (error: any) {
|
|
739
821
|
console.warn(` Warning: could not generate component ${compName}: ${error?.message}`);
|
|
740
822
|
}
|
|
741
823
|
}
|
|
742
|
-
console.log(`Generated ${componentFileCount} component .astro file(s)`);
|
|
743
|
-
|
|
744
824
|
// ----------------------------------------------------------
|
|
745
825
|
// 8. Generate .astro page files (component-structured)
|
|
746
826
|
// ----------------------------------------------------------
|
|
747
827
|
for (const result of allResults) {
|
|
748
828
|
const importPath = layoutImportPath(result.fileDepth);
|
|
749
829
|
|
|
750
|
-
// Write JavaScript to public/_scripts/ if present
|
|
751
|
-
|
|
752
|
-
if (result.javascript) {
|
|
753
|
-
const hash = hashContent(result.javascript);
|
|
754
|
-
const scriptFile = `${hash}.js`;
|
|
755
|
-
const scriptPublicPath = `/_scripts/${scriptFile}`;
|
|
756
|
-
|
|
757
|
-
if (!existsSync(scriptsDir)) {
|
|
758
|
-
mkdirSync(scriptsDir, { recursive: true });
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
const fullScriptPath = join(scriptsDir, scriptFile);
|
|
762
|
-
if (!existsSync(fullScriptPath)) {
|
|
763
|
-
await writeFile(fullScriptPath, result.javascript, 'utf-8');
|
|
764
|
-
}
|
|
765
|
-
scriptPaths.push(scriptPublicPath);
|
|
766
|
-
}
|
|
830
|
+
// Write JavaScript to public/_scripts/ if present (only needed for SSR fallback pages)
|
|
831
|
+
let scriptPaths: string[] = [];
|
|
767
832
|
|
|
768
833
|
let astroContent: string;
|
|
769
834
|
|
|
@@ -779,6 +844,8 @@ const { title, meta = '', scripts = [], locale = 'en', theme = '${themeConfig.de
|
|
|
779
844
|
? computePageSlugMap(result.pageData.meta.slugs, i18nConfig)
|
|
780
845
|
: undefined;
|
|
781
846
|
|
|
847
|
+
// Component-structured pages don't need page-level _scripts/*.js
|
|
848
|
+
// because each .astro component already has its own inline <script>
|
|
782
849
|
astroContent = emitAstroPage({
|
|
783
850
|
pageData: result.pageData,
|
|
784
851
|
globalComponents,
|
|
@@ -788,25 +855,30 @@ const { title, meta = '', scripts = [], locale = 'en', theme = '${themeConfig.de
|
|
|
788
855
|
theme: defaultTheme,
|
|
789
856
|
fontPreloads,
|
|
790
857
|
libraryTags,
|
|
791
|
-
scriptPaths,
|
|
858
|
+
scriptPaths: [],
|
|
792
859
|
layoutImportPath: importPath,
|
|
793
860
|
fileDepth: result.fileDepth,
|
|
794
861
|
ssrFallbacks,
|
|
795
862
|
pageName: result.pageName || 'index',
|
|
796
863
|
breakpoints,
|
|
864
|
+
responsiveScales,
|
|
797
865
|
imageMetadataMap,
|
|
798
866
|
i18nConfig: i18nConfig.locales.length > 1 ? i18nConfig : undefined,
|
|
799
867
|
currentPageSlugMap: pageSlugMap,
|
|
800
868
|
slugMappings: i18nConfig.locales.length > 1 ? slugMappings : undefined,
|
|
801
869
|
imageFormat: configService.getImageFormat(),
|
|
870
|
+
processedRawHtml: result.processedRawHtmlCollector,
|
|
871
|
+
remConfig: remConversionConfig,
|
|
802
872
|
});
|
|
803
873
|
} catch (error: any) {
|
|
804
|
-
// Fallback to SSR HTML if component emission fails
|
|
874
|
+
// Fallback to SSR HTML if component emission fails — needs page-level script
|
|
805
875
|
console.warn(` Warning: component emission failed for ${result.urlPath}, using SSR fallback: ${error?.message}`);
|
|
876
|
+
scriptPaths = writePageScript(result.javascript, scriptsDir);
|
|
806
877
|
astroContent = buildSSRFallbackPage(result, importPath, fontPreloads, libraryTags, defaultTheme, scriptPaths);
|
|
807
878
|
}
|
|
808
879
|
} else {
|
|
809
|
-
// Pages without pageData: use SSR fallback
|
|
880
|
+
// Pages without pageData: use SSR fallback — needs page-level script
|
|
881
|
+
scriptPaths = writePageScript(result.javascript, scriptsDir);
|
|
810
882
|
astroContent = buildSSRFallbackPage(result, importPath, fontPreloads, libraryTags, defaultTheme, scriptPaths);
|
|
811
883
|
}
|
|
812
884
|
|
|
@@ -819,7 +891,26 @@ const { title, meta = '', scripts = [], locale = 'en', theme = '${themeConfig.de
|
|
|
819
891
|
await writeFile(astroFileFull, astroContent, 'utf-8');
|
|
820
892
|
}
|
|
821
893
|
|
|
822
|
-
|
|
894
|
+
// ----------------------------------------------------------
|
|
895
|
+
// 8.5. Generate robots.txt endpoint
|
|
896
|
+
// ----------------------------------------------------------
|
|
897
|
+
const robotsTsContent = `import type { APIRoute } from 'astro';
|
|
898
|
+
|
|
899
|
+
export const GET: APIRoute = () => {
|
|
900
|
+
const siteUrl = import.meta.env.SITE;
|
|
901
|
+
const robotsTxt = [
|
|
902
|
+
'User-agent: *',
|
|
903
|
+
'Allow: /',
|
|
904
|
+
'',
|
|
905
|
+
siteUrl ? \`Sitemap: \${siteUrl}/sitemap-index.xml\` : '',
|
|
906
|
+
].filter(Boolean).join('\\n');
|
|
907
|
+
|
|
908
|
+
return new Response(robotsTxt, {
|
|
909
|
+
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
910
|
+
});
|
|
911
|
+
};
|
|
912
|
+
`;
|
|
913
|
+
await writeFile(join(pagesOutDir, 'robots.txt.ts'), robotsTsContent, 'utf-8');
|
|
823
914
|
|
|
824
915
|
// ----------------------------------------------------------
|
|
825
916
|
// 9. Generate CMS content collections (if templates exist)
|
|
@@ -871,7 +962,7 @@ const { title, meta = '', scripts = [], locale = 'en', theme = '${themeConfig.de
|
|
|
871
962
|
}
|
|
872
963
|
|
|
873
964
|
collectionDefs.push(` '${schema.id}': defineCollection({
|
|
874
|
-
|
|
965
|
+
loader: glob({ pattern: '**/*.json', base: './src/content/${schema.id}' }),
|
|
875
966
|
schema: z.object({
|
|
876
967
|
${fieldDefs.join(',\n')}
|
|
877
968
|
})
|
|
@@ -880,8 +971,9 @@ ${fieldDefs.join(',\n')}
|
|
|
880
971
|
collectionCount++;
|
|
881
972
|
}
|
|
882
973
|
|
|
883
|
-
// Write src/content/config.ts
|
|
974
|
+
// Write src/content.config.ts (Astro 6 location — legacy src/content/config.ts is rejected)
|
|
884
975
|
const configContent = `import { z, defineCollection } from 'astro:content';
|
|
976
|
+
import { glob } from 'astro/loaders';
|
|
885
977
|
|
|
886
978
|
const collections = {
|
|
887
979
|
${collectionDefs.join(',\n')}
|
|
@@ -890,33 +982,53 @@ ${collectionDefs.join(',\n')}
|
|
|
890
982
|
export { collections };
|
|
891
983
|
`;
|
|
892
984
|
|
|
893
|
-
await writeFile(join(
|
|
894
|
-
console.log(`Generated ${collectionCount} content collection(s) with config.ts`);
|
|
985
|
+
await writeFile(join(srcDir, 'content.config.ts'), configContent, 'utf-8');
|
|
895
986
|
}
|
|
896
987
|
|
|
897
988
|
// ----------------------------------------------------------
|
|
898
|
-
// 10. Copy assets
|
|
989
|
+
// 10. Copy assets
|
|
899
990
|
// ----------------------------------------------------------
|
|
900
|
-
|
|
901
|
-
|
|
991
|
+
// Images go to src/assets/images so Astro's asset pipeline can process
|
|
992
|
+
// them via astro:assets `<Picture>` (hashing, on-edit reprocessing).
|
|
993
|
+
// Everything else stays in public/ — fonts/icons/videos need stable URLs.
|
|
994
|
+
|
|
995
|
+
const imagesSrcDir = join(projectPaths.project, 'images');
|
|
996
|
+
if (existsSync(imagesSrcDir)) {
|
|
997
|
+
// src/assets/images: used by static <Picture> via ESM imports + Vite asset
|
|
998
|
+
// pipeline. Pre-baked responsive variants and manifest.json are excluded —
|
|
999
|
+
// Astro regenerates those from the originals.
|
|
1000
|
+
copyDirectory(imagesSrcDir, join(srcDir, 'assets', 'images'), shouldCopyImageForAstro);
|
|
1001
|
+
// public/images: used by the legacy <img>/<picture> srcset path and
|
|
1002
|
+
// rich-text image rewrites, which emit plain `/images/...` URLs pointing at
|
|
1003
|
+
// the pre-built variants. Without this mirror, any image not routed through
|
|
1004
|
+
// the static Picture path (variants referenced directly in a srcset,
|
|
1005
|
+
// CMS/template-bound images, component-prop images) 404s in `astro dev`.
|
|
1006
|
+
copyDirectory(imagesSrcDir, join(publicDir, 'images'));
|
|
1007
|
+
}
|
|
902
1008
|
|
|
903
|
-
|
|
1009
|
+
const publicAssetDirs = ['fonts', 'icons', 'videos', 'assets'];
|
|
1010
|
+
for (const dir of publicAssetDirs) {
|
|
904
1011
|
const srcAssetDir = join(projectPaths.project, dir);
|
|
905
1012
|
if (existsSync(srcAssetDir)) {
|
|
906
1013
|
copyDirectory(srcAssetDir, join(publicDir, dir));
|
|
907
|
-
|
|
908
|
-
}
|
|
1014
|
+
}
|
|
909
1015
|
}
|
|
910
1016
|
|
|
911
1017
|
// Copy libraries folder if it exists
|
|
912
1018
|
const librariesDir = join(projectPaths.project, 'libraries');
|
|
913
1019
|
if (existsSync(librariesDir)) {
|
|
914
1020
|
copyDirectory(librariesDir, join(publicDir, 'libraries'));
|
|
915
|
-
copiedAssets++;
|
|
916
1021
|
}
|
|
917
1022
|
|
|
918
|
-
|
|
919
|
-
|
|
1023
|
+
// Copy any project-root library files referenced by absolute URL in
|
|
1024
|
+
// project.config.json (e.g. `/custom.css`). Only copies entries we already
|
|
1025
|
+
// validated exist on disk during library tag generation above.
|
|
1026
|
+
for (const relPath of localLibsToCopy) {
|
|
1027
|
+
const srcPath = join(projectPaths.project, relPath);
|
|
1028
|
+
const destPath = join(publicDir, relPath);
|
|
1029
|
+
const destDir = destPath.substring(0, destPath.lastIndexOf('/'));
|
|
1030
|
+
if (destDir && !existsSync(destDir)) mkdirSync(destDir, { recursive: true });
|
|
1031
|
+
copyFileSync(srcPath, destPath);
|
|
920
1032
|
}
|
|
921
1033
|
|
|
922
1034
|
// ----------------------------------------------------------
|
|
@@ -936,9 +1048,14 @@ export { collections };
|
|
|
936
1048
|
preview: 'astro preview',
|
|
937
1049
|
},
|
|
938
1050
|
dependencies: {
|
|
939
|
-
'astro': '^
|
|
940
|
-
'@astrojs/
|
|
941
|
-
'tailwindcss': '^
|
|
1051
|
+
'astro': '^6.0.0',
|
|
1052
|
+
'@astrojs/sitemap': '^3.0.0',
|
|
1053
|
+
'@tailwindcss/vite': '^4.0.0',
|
|
1054
|
+
'tailwindcss': '^4.0.0',
|
|
1055
|
+
},
|
|
1056
|
+
// Astro 6 expects Vite 7; pin it so npm doesn't pull Vite 8+ and warn.
|
|
1057
|
+
overrides: {
|
|
1058
|
+
'vite': '^7.0.0',
|
|
942
1059
|
},
|
|
943
1060
|
};
|
|
944
1061
|
|
|
@@ -951,31 +1068,17 @@ export { collections };
|
|
|
951
1068
|
: '';
|
|
952
1069
|
|
|
953
1070
|
const astroConfig = `import { defineConfig } from 'astro/config';
|
|
954
|
-
import
|
|
1071
|
+
import tailwindcss from '@tailwindcss/vite';
|
|
1072
|
+
import sitemap from '@astrojs/sitemap';
|
|
955
1073
|
|
|
956
1074
|
export default defineConfig({${siteUrl ? `\n site: '${siteUrl}',` : ''}${i18nBlock}
|
|
957
|
-
integrations: [
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
// tailwind.config.mjs
|
|
962
|
-
const safelistArray = Array.from(mappingClasses);
|
|
963
|
-
const safelistLiteral = safelistArray.length > 0
|
|
964
|
-
? `\n safelist: [\n${safelistArray.map(c => ` '${c}'`).join(',\n')}\n ],`
|
|
965
|
-
: '';
|
|
966
|
-
|
|
967
|
-
const tailwindConfig = `/** @type {import('tailwindcss').Config} */
|
|
968
|
-
export default {
|
|
969
|
-
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],${safelistLiteral}
|
|
970
|
-
theme: {
|
|
971
|
-
extend: {},
|
|
1075
|
+
integrations: [sitemap()],
|
|
1076
|
+
vite: {
|
|
1077
|
+
plugins: [tailwindcss()],
|
|
972
1078
|
},
|
|
973
|
-
|
|
974
|
-
};
|
|
1079
|
+
});
|
|
975
1080
|
`;
|
|
976
1081
|
|
|
977
|
-
await writeFile(join(outDir, 'tailwind.config.mjs'), tailwindConfig, 'utf-8');
|
|
978
|
-
|
|
979
1082
|
await writeFile(join(outDir, 'astro.config.mjs'), astroConfig, 'utf-8');
|
|
980
1083
|
|
|
981
1084
|
// tsconfig.json
|
|
@@ -985,30 +1088,14 @@ export default {
|
|
|
985
1088
|
|
|
986
1089
|
await writeFile(join(outDir, 'tsconfig.json'), JSON.stringify(tsConfig, null, 2), 'utf-8');
|
|
987
1090
|
|
|
988
|
-
|
|
1091
|
+
// src/env.d.ts — resolves astro:assets and other virtual module types in IDE
|
|
1092
|
+
await writeFile(join(outDir, 'src', 'env.d.ts'), '/// <reference path="../.astro/types.d.ts" />\n', 'utf-8');
|
|
989
1093
|
|
|
990
1094
|
// ----------------------------------------------------------
|
|
991
1095
|
// 12. Summary
|
|
992
1096
|
// ----------------------------------------------------------
|
|
993
|
-
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
994
1097
|
const totalPages = allResults.length;
|
|
995
1098
|
|
|
996
|
-
console.log('\n' + '='.repeat(50));
|
|
997
|
-
console.log('Astro export complete!');
|
|
998
|
-
console.log(` Pages: ${totalPages - cmsPageCount}`);
|
|
999
|
-
if (cmsPageCount > 0) {
|
|
1000
|
-
console.log(` CMS pages: ${cmsPageCount}`);
|
|
1001
|
-
}
|
|
1002
|
-
if (collectionCount > 0) {
|
|
1003
|
-
console.log(` Content collections: ${collectionCount}`);
|
|
1004
|
-
}
|
|
1005
|
-
if (errorCount > 0) {
|
|
1006
|
-
console.log(` Errors: ${errorCount}`);
|
|
1007
|
-
}
|
|
1008
|
-
console.log(` Time: ${elapsed}s`);
|
|
1009
|
-
console.log(` Output: ${outDir}`);
|
|
1010
|
-
console.log('');
|
|
1011
|
-
|
|
1012
1099
|
return {
|
|
1013
1100
|
pages: totalPages - cmsPageCount,
|
|
1014
1101
|
cmsPages: cmsPageCount,
|