meno-core 1.0.52 → 1.0.53
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 +183 -13
- package/build-next.ts +1361 -0
- package/build-static.ts +7 -5
- package/dist/bin/cli.js +2 -2
- package/dist/build-static.js +6 -6
- package/dist/chunks/{chunk-HNLUO36W.js → chunk-GZHGVVW3.js} +2 -2
- package/dist/chunks/chunk-GZHGVVW3.js.map +7 -0
- package/dist/chunks/{chunk-LPVETICS.js → chunk-H3GJ4H2U.js} +185 -1
- package/dist/chunks/chunk-H3GJ4H2U.js.map +7 -0
- package/dist/chunks/{chunk-CXCBV2M7.js → chunk-IGYR22T6.js} +76 -270
- package/dist/chunks/chunk-IGYR22T6.js.map +7 -0
- package/dist/chunks/{chunk-LHLHPYSP.js → chunk-JGP5A3Y5.js} +12 -11
- package/dist/chunks/chunk-JGP5A3Y5.js.map +7 -0
- package/dist/chunks/{chunk-7NIC4I3V.js → chunk-JGWFTO6P.js} +167 -21
- package/dist/chunks/chunk-JGWFTO6P.js.map +7 -0
- package/dist/chunks/{chunk-EDQSMAMP.js → chunk-O3NAGJP4.js} +85 -4
- package/dist/chunks/chunk-O3NAGJP4.js.map +7 -0
- package/dist/chunks/{chunk-H4JSCDNW.js → chunk-QB2LNO4W.js} +24 -1
- package/dist/chunks/chunk-QB2LNO4W.js.map +7 -0
- package/dist/chunks/{chunk-A725KYFK.js → chunk-R6XHAFBF.js} +561 -112
- package/dist/chunks/chunk-R6XHAFBF.js.map +7 -0
- package/dist/chunks/{chunk-J23ZX5AP.js → chunk-X754AHS5.js} +277 -1
- package/dist/chunks/chunk-X754AHS5.js.map +7 -0
- package/dist/chunks/{chunk-2QK6U5UK.js → chunk-YBLHKYFF.js} +12 -2
- package/dist/chunks/chunk-YBLHKYFF.js.map +7 -0
- package/dist/chunks/{constants-GWBAD66U.js → constants-STK2YBIW.js} +2 -2
- package/dist/entries/server-router.js +7 -7
- package/dist/lib/client/index.js +354 -59
- package/dist/lib/client/index.js.map +4 -4
- package/dist/lib/server/index.js +1458 -190
- package/dist/lib/server/index.js.map +4 -4
- package/dist/lib/shared/index.js +202 -34
- package/dist/lib/shared/index.js.map +4 -4
- package/dist/lib/test-utils/index.js +1 -1
- package/entries/client-router.tsx +5 -165
- package/lib/client/ErrorBoundary.test.tsx +27 -25
- package/lib/client/ErrorBoundary.tsx +34 -19
- package/lib/client/core/ComponentBuilder.ts +19 -2
- package/lib/client/core/builders/embedBuilder.ts +8 -4
- package/lib/client/core/builders/listBuilder.ts +23 -4
- package/lib/client/fontFamiliesService.test.ts +76 -0
- package/lib/client/fontFamiliesService.ts +69 -0
- package/lib/client/hmrCssReload.ts +160 -0
- package/lib/client/hooks/useColorVariables.ts +2 -0
- package/lib/client/index.ts +4 -0
- package/lib/client/meno-filter/ui.ts +2 -0
- package/lib/client/routing/RouteLoader.test.ts +2 -2
- package/lib/client/routing/RouteLoader.ts +8 -2
- package/lib/client/routing/Router.tsx +81 -15
- package/lib/client/scripts/ScriptExecutor.test.ts +143 -0
- package/lib/client/scripts/ScriptExecutor.ts +56 -2
- package/lib/client/styles/StyleInjector.ts +20 -5
- package/lib/client/styles/UtilityClassCollector.ts +7 -1
- package/lib/client/styles/cspNonce.test.ts +67 -0
- package/lib/client/styles/cspNonce.ts +63 -0
- package/lib/client/templateEngine.test.ts +80 -0
- package/lib/client/templateEngine.ts +5 -0
- package/lib/server/astro/cmsPageEmitter.ts +35 -5
- package/lib/server/astro/componentEmitter.ts +61 -5
- package/lib/server/astro/nodeToAstro.ts +149 -11
- package/lib/server/astro/normalizeOrphanTemplateProps.test.ts +264 -0
- package/lib/server/astro/normalizeOrphanTemplateProps.ts +184 -0
- package/lib/server/createServer.ts +11 -0
- package/lib/server/draftPageStore.ts +49 -0
- package/lib/server/fileWatcher.ts +62 -2
- package/lib/server/index.ts +13 -1
- package/lib/server/providers/fileSystemPageProvider.ts +8 -0
- package/lib/server/routes/api/components.ts +9 -4
- package/lib/server/routes/api/core-routes.ts +2 -2
- package/lib/server/routes/api/pages.ts +14 -22
- package/lib/server/routes/api/shared.ts +56 -0
- package/lib/server/routes/index.ts +90 -0
- package/lib/server/routes/pages.ts +13 -6
- package/lib/server/services/componentService.test.ts +199 -2
- package/lib/server/services/componentService.ts +354 -49
- package/lib/server/services/fileWatcherService.ts +4 -24
- package/lib/server/services/pageService.test.ts +23 -0
- package/lib/server/services/pageService.ts +124 -6
- package/lib/server/ssr/attributeBuilder.ts +8 -2
- package/lib/server/ssr/buildErrorOverlay.ts +1 -1
- package/lib/server/ssr/errorOverlay.test.ts +21 -2
- package/lib/server/ssr/errorOverlay.ts +38 -11
- package/lib/server/ssr/htmlGenerator.test.ts +53 -13
- package/lib/server/ssr/htmlGenerator.ts +71 -27
- package/lib/server/ssr/liveReloadIntegration.test.ts +123 -2
- package/lib/server/ssr/ssrRenderer.test.ts +67 -0
- package/lib/server/ssr/ssrRenderer.ts +94 -9
- package/lib/server/websocketManager.ts +0 -1
- package/lib/shared/componentRefs.ts +45 -0
- package/lib/shared/constants.ts +8 -0
- package/lib/shared/cssGeneration.ts +2 -0
- package/lib/shared/cssProperties.ts +184 -0
- package/lib/shared/expressionEvaluator.ts +54 -0
- package/lib/shared/fontCss.ts +101 -0
- package/lib/shared/fontLoader.ts +8 -86
- package/lib/shared/friendlyError.test.ts +87 -0
- package/lib/shared/friendlyError.ts +121 -0
- package/lib/shared/hrefRefs.test.ts +130 -0
- package/lib/shared/hrefRefs.ts +100 -0
- package/lib/shared/index.ts +52 -0
- package/lib/shared/inlineSvgStyleRules.test.ts +108 -0
- package/lib/shared/inlineSvgStyleRules.ts +134 -0
- package/lib/shared/interfaces/contentProvider.ts +13 -0
- package/lib/shared/itemTemplateUtils.test.ts +14 -0
- package/lib/shared/itemTemplateUtils.ts +4 -1
- package/lib/shared/registry/NodeTypeDefinition.ts +1 -1
- package/lib/shared/registry/nodeTypes/LinkNodeType.ts +1 -1
- package/lib/shared/slugTranslator.test.ts +24 -0
- package/lib/shared/slugTranslator.ts +24 -0
- package/lib/shared/styleNodeUtils.ts +4 -1
- package/lib/shared/tree/PathBuilder.test.ts +128 -1
- package/lib/shared/tree/PathBuilder.ts +83 -31
- package/lib/shared/types/comment.ts +99 -0
- package/lib/shared/types/index.ts +12 -0
- package/lib/shared/types/rendering.ts +8 -0
- package/lib/shared/utilityClassConfig.ts +4 -2
- package/lib/shared/utilityClassMapper.test.ts +24 -0
- package/lib/shared/validation/commentValidators.ts +69 -0
- package/lib/shared/validation/index.ts +1 -0
- package/lib/shared/viewportUnits.integration.test.ts +42 -0
- package/lib/shared/viewportUnits.test.ts +103 -0
- package/lib/shared/viewportUnits.ts +63 -0
- package/lib/test-utils/dom-setup.ts +6 -0
- package/package.json +1 -1
- package/dist/chunks/chunk-2QK6U5UK.js.map +0 -7
- package/dist/chunks/chunk-7NIC4I3V.js.map +0 -7
- package/dist/chunks/chunk-A725KYFK.js.map +0 -7
- package/dist/chunks/chunk-CXCBV2M7.js.map +0 -7
- package/dist/chunks/chunk-EDQSMAMP.js.map +0 -7
- package/dist/chunks/chunk-H4JSCDNW.js.map +0 -7
- package/dist/chunks/chunk-HNLUO36W.js.map +0 -7
- package/dist/chunks/chunk-J23ZX5AP.js.map +0 -7
- package/dist/chunks/chunk-LHLHPYSP.js.map +0 -7
- package/dist/chunks/chunk-LPVETICS.js.map +0 -7
- /package/dist/chunks/{constants-GWBAD66U.js.map → constants-STK2YBIW.js.map} +0 -0
package/dist/lib/server/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
generateBuildErrorPage
|
|
3
|
-
} from "../../chunks/chunk-
|
|
3
|
+
} from "../../chunks/chunk-GZHGVVW3.js";
|
|
4
4
|
import {
|
|
5
5
|
buildStaticPages
|
|
6
|
-
} from "../../chunks/chunk-
|
|
6
|
+
} from "../../chunks/chunk-JGP5A3Y5.js";
|
|
7
7
|
import {
|
|
8
8
|
ComponentService,
|
|
9
9
|
EnumService,
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
logResponseTime,
|
|
33
33
|
withErrorHandling,
|
|
34
34
|
withLogging
|
|
35
|
-
} from "../../chunks/chunk-
|
|
35
|
+
} from "../../chunks/chunk-R6XHAFBF.js";
|
|
36
36
|
import {
|
|
37
37
|
CMSService,
|
|
38
38
|
ColorService,
|
|
@@ -43,26 +43,21 @@ import {
|
|
|
43
43
|
buildAttributes,
|
|
44
44
|
buildComponentHTML,
|
|
45
45
|
buildImageMetadataMap,
|
|
46
|
-
buildSlugIndex,
|
|
47
46
|
clearJSValidationCache,
|
|
48
47
|
collectComponentCSS,
|
|
49
48
|
collectComponentJavaScript,
|
|
50
|
-
collectComponentLibraries,
|
|
51
49
|
colorService,
|
|
52
50
|
createI18nResolver,
|
|
53
51
|
escapeHtml,
|
|
54
52
|
extractPageMeta,
|
|
55
|
-
filterLibrariesByContext,
|
|
56
53
|
formHandlerScript,
|
|
57
54
|
generateFontCSS,
|
|
58
55
|
generateFontPreloadTags,
|
|
59
|
-
generateLibraryTags,
|
|
60
56
|
generateMetaTags,
|
|
61
57
|
generateSSRHTML,
|
|
62
58
|
generateThemeColorVariablesCSS,
|
|
63
59
|
generateVariablesCSS,
|
|
64
60
|
getJSValidationErrors,
|
|
65
|
-
getLocaleLinks,
|
|
66
61
|
loadBreakpointConfig,
|
|
67
62
|
loadComponentDirectory,
|
|
68
63
|
loadI18nConfig,
|
|
@@ -71,7 +66,6 @@ import {
|
|
|
71
66
|
loadProjectConfig,
|
|
72
67
|
mapPageNameToPath,
|
|
73
68
|
menoFilterScript,
|
|
74
|
-
mergeLibraries,
|
|
75
69
|
migrateTemplatesDirectory,
|
|
76
70
|
needsFormHandler,
|
|
77
71
|
needsMenoFilter,
|
|
@@ -81,9 +75,8 @@ import {
|
|
|
81
75
|
renderPageSSR,
|
|
82
76
|
resetFontConfig,
|
|
83
77
|
styleToString,
|
|
84
|
-
translatePath,
|
|
85
78
|
variableService
|
|
86
|
-
} from "../../chunks/chunk-
|
|
79
|
+
} from "../../chunks/chunk-IGYR22T6.js";
|
|
87
80
|
import {
|
|
88
81
|
ConfigService,
|
|
89
82
|
configService
|
|
@@ -116,23 +109,34 @@ import {
|
|
|
116
109
|
spawnProcess,
|
|
117
110
|
writeFile
|
|
118
111
|
} from "../../chunks/chunk-WQFG7PAH.js";
|
|
119
|
-
import "../../chunks/chunk-
|
|
112
|
+
import "../../chunks/chunk-QB2LNO4W.js";
|
|
120
113
|
import {
|
|
121
|
-
|
|
122
|
-
|
|
114
|
+
CMS_DRAFT_SUFFIX,
|
|
115
|
+
buildSlugIndex,
|
|
116
|
+
collectComponentLibraries,
|
|
117
|
+
filterLibrariesByContext,
|
|
118
|
+
generateLibraryTags,
|
|
119
|
+
getLocaleLinks,
|
|
120
|
+
mergeLibraries,
|
|
121
|
+
resolvePaletteColor,
|
|
122
|
+
translatePath
|
|
123
|
+
} from "../../chunks/chunk-X754AHS5.js";
|
|
123
124
|
import {
|
|
125
|
+
deepMergeStyles,
|
|
124
126
|
hasTemplates,
|
|
125
127
|
isHtmlMapping,
|
|
126
128
|
processCodeTemplates,
|
|
127
129
|
resolveHtmlMapping
|
|
128
|
-
} from "../../chunks/chunk-
|
|
130
|
+
} from "../../chunks/chunk-O3NAGJP4.js";
|
|
129
131
|
import {
|
|
130
132
|
addItemUrl,
|
|
131
133
|
buildTemplateContext,
|
|
132
134
|
extractInteractiveStyleMappings,
|
|
135
|
+
extractUtilityClassesFromHTML,
|
|
133
136
|
generateAllInteractiveCSS,
|
|
134
137
|
generateElementClassName,
|
|
135
138
|
generateInteractiveCSS,
|
|
139
|
+
generateUtilityCSS,
|
|
136
140
|
getNestedValue,
|
|
137
141
|
hasIf,
|
|
138
142
|
hasInteractiveStyleMappings,
|
|
@@ -144,7 +148,7 @@ import {
|
|
|
144
148
|
resolvePropsFromDefinition,
|
|
145
149
|
shortHash,
|
|
146
150
|
singularize
|
|
147
|
-
} from "../../chunks/chunk-
|
|
151
|
+
} from "../../chunks/chunk-JGWFTO6P.js";
|
|
148
152
|
import {
|
|
149
153
|
DEFAULT_BREAKPOINTS,
|
|
150
154
|
DEFAULT_I18N_CONFIG,
|
|
@@ -155,7 +159,10 @@ import {
|
|
|
155
159
|
resolveVariableValueAtBreakpoint,
|
|
156
160
|
scalePropertyValue
|
|
157
161
|
} from "../../chunks/chunk-AZQYF6KE.js";
|
|
158
|
-
import
|
|
162
|
+
import {
|
|
163
|
+
isTiptapDocument,
|
|
164
|
+
tiptapToHtml
|
|
165
|
+
} from "../../chunks/chunk-UB44F4Z2.js";
|
|
159
166
|
import {
|
|
160
167
|
HMR_ROUTE,
|
|
161
168
|
MAX_PORT_ATTEMPTS,
|
|
@@ -164,9 +171,43 @@ import {
|
|
|
164
171
|
SERVER_PORT,
|
|
165
172
|
SERVE_PORT,
|
|
166
173
|
init_constants
|
|
167
|
-
} from "../../chunks/chunk-
|
|
174
|
+
} from "../../chunks/chunk-YBLHKYFF.js";
|
|
168
175
|
import "../../chunks/chunk-KSBZ2L7C.js";
|
|
169
176
|
|
|
177
|
+
// lib/server/draftPageStore.ts
|
|
178
|
+
var DraftPageStore = class {
|
|
179
|
+
drafts = /* @__PURE__ */ new Map();
|
|
180
|
+
/**
|
|
181
|
+
* Store a draft for a page path. The value is the raw JSON string the
|
|
182
|
+
* SSR pipeline expects, matching PageService.getPage()'s contract.
|
|
183
|
+
*/
|
|
184
|
+
set(path, content) {
|
|
185
|
+
this.drafts.set(path, content);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Get the current draft string for a page path, if any.
|
|
189
|
+
*/
|
|
190
|
+
get(path) {
|
|
191
|
+
return this.drafts.get(path);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Drop the draft for a specific path. Called when the page is saved to
|
|
195
|
+
* disk so subsequent renders read the persisted version.
|
|
196
|
+
*/
|
|
197
|
+
clear(path) {
|
|
198
|
+
this.drafts.delete(path);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Drop every draft. Used on shutdown / project switch.
|
|
202
|
+
*/
|
|
203
|
+
clearAll() {
|
|
204
|
+
this.drafts.clear();
|
|
205
|
+
}
|
|
206
|
+
has(path) {
|
|
207
|
+
return this.drafts.has(path);
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
|
|
170
211
|
// build-astro.ts
|
|
171
212
|
import { existsSync, readdirSync, mkdirSync, rmSync, statSync, copyFileSync, writeFileSync } from "fs";
|
|
172
213
|
import { writeFile as writeFile2, readFile } from "fs/promises";
|
|
@@ -765,6 +806,9 @@ function astroComponentName(name) {
|
|
|
765
806
|
function ind(ctx) {
|
|
766
807
|
return " ".repeat(ctx.indent);
|
|
767
808
|
}
|
|
809
|
+
function linkHrefExpr(expr) {
|
|
810
|
+
return `(typeof ${expr} === 'string' ? ${expr} : ${expr}?.href) ?? "#"`;
|
|
811
|
+
}
|
|
768
812
|
function localizeHref(href, ctx) {
|
|
769
813
|
if (!href.startsWith("/") || href.startsWith("//")) return href;
|
|
770
814
|
const { locale, i18nDefaultLocale, slugMappings } = ctx;
|
|
@@ -996,6 +1040,41 @@ function buildElementClass(ctx, label) {
|
|
|
996
1040
|
path: ctx.elementPath
|
|
997
1041
|
});
|
|
998
1042
|
}
|
|
1043
|
+
function isResponsiveStyleObject(style) {
|
|
1044
|
+
return "base" in style || "tablet" in style || "mobile" in style;
|
|
1045
|
+
}
|
|
1046
|
+
function getComponentRootStyle(def) {
|
|
1047
|
+
const root = def?.component?.structure;
|
|
1048
|
+
if (!root || typeof root !== "object") return void 0;
|
|
1049
|
+
const s = root.style ?? root.props?.style;
|
|
1050
|
+
return s && typeof s === "object" ? s : void 0;
|
|
1051
|
+
}
|
|
1052
|
+
function hasNonEmptyStyle(style) {
|
|
1053
|
+
if (isResponsiveStyleObject(style)) {
|
|
1054
|
+
return Object.values(style).some(
|
|
1055
|
+
(branch) => !!branch && typeof branch === "object" && Object.keys(branch).length > 0
|
|
1056
|
+
);
|
|
1057
|
+
}
|
|
1058
|
+
return Object.keys(style).length > 0;
|
|
1059
|
+
}
|
|
1060
|
+
function stripTemplateExpressionStyle(style) {
|
|
1061
|
+
const cleanFlat = (s) => {
|
|
1062
|
+
const out = {};
|
|
1063
|
+
for (const [k, v] of Object.entries(s)) {
|
|
1064
|
+
if (typeof v === "string" && hasTemplates2(v)) continue;
|
|
1065
|
+
out[k] = v;
|
|
1066
|
+
}
|
|
1067
|
+
return out;
|
|
1068
|
+
};
|
|
1069
|
+
if (isResponsiveStyleObject(style)) {
|
|
1070
|
+
const out = {};
|
|
1071
|
+
for (const [bp, branch] of Object.entries(style)) {
|
|
1072
|
+
out[bp] = branch && typeof branch === "object" ? cleanFlat(branch) : branch;
|
|
1073
|
+
}
|
|
1074
|
+
return out;
|
|
1075
|
+
}
|
|
1076
|
+
return cleanFlat(style);
|
|
1077
|
+
}
|
|
999
1078
|
function buildAttributesString(attributes, ctx) {
|
|
1000
1079
|
if (!attributes) return "";
|
|
1001
1080
|
const parts = [];
|
|
@@ -1012,7 +1091,7 @@ function buildAttributesString(attributes, ctx) {
|
|
|
1012
1091
|
if (ctx.listIndexVar) expr = replaceItemMetaVars(expr, ctx.listIndexVar, ctx.listSourceVar);
|
|
1013
1092
|
const propDef = ctx.componentProps[expr];
|
|
1014
1093
|
if (propDef && propDef.type === "link") {
|
|
1015
|
-
parts.push(`${key}={${expr}
|
|
1094
|
+
parts.push(`${key}={${linkHrefExpr(expr)}}`);
|
|
1016
1095
|
} else {
|
|
1017
1096
|
parts.push(`${key}={${expr} || undefined}`);
|
|
1018
1097
|
}
|
|
@@ -1022,7 +1101,7 @@ function buildAttributesString(attributes, ctx) {
|
|
|
1022
1101
|
if (ctx.listItemBinding) trimmed = rewriteItemVar(trimmed, ctx.listItemBinding);
|
|
1023
1102
|
if (ctx.listIndexVar) trimmed = replaceItemMetaVars(trimmed, ctx.listIndexVar, ctx.listSourceVar);
|
|
1024
1103
|
const pd = ctx.componentProps[trimmed];
|
|
1025
|
-
return pd?.type === "link" ? `\${${trimmed}
|
|
1104
|
+
return pd?.type === "link" ? `\${${linkHrefExpr(trimmed)}}` : `\${${trimmed}}`;
|
|
1026
1105
|
});
|
|
1027
1106
|
parts.push(`${key}={\`${resolved}\`}`);
|
|
1028
1107
|
}
|
|
@@ -1520,10 +1599,29 @@ function emitComponentInstance(node, ctx) {
|
|
|
1520
1599
|
}
|
|
1521
1600
|
}
|
|
1522
1601
|
}
|
|
1523
|
-
if (node.
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1602
|
+
if (ctx.cmsMode && ctx.cmsConsumers?.has(node.component) && !("cms" in (node.props ?? {}))) {
|
|
1603
|
+
propParts.push(`cms={${ctx.cmsEntryBinding || "entry"}}`);
|
|
1604
|
+
}
|
|
1605
|
+
{
|
|
1606
|
+
const instanceStyle = node.style;
|
|
1607
|
+
const instanceInteractive = node.interactiveStyles;
|
|
1608
|
+
const hasStyle = !!instanceStyle && hasNonEmptyStyle(instanceStyle);
|
|
1609
|
+
const hasInteractive = Array.isArray(instanceInteractive) && instanceInteractive.length > 0;
|
|
1610
|
+
if ((hasStyle || hasInteractive) && ctx.collectedInteractiveStyles) {
|
|
1611
|
+
const elementClass = buildElementClass(ctx, node.label);
|
|
1612
|
+
const rules = [];
|
|
1613
|
+
if (hasStyle) {
|
|
1614
|
+
const rootStyle = getComponentRootStyle(ctx.globalComponents[node.component]);
|
|
1615
|
+
const merged = rootStyle ? deepMergeStyles(rootStyle, instanceStyle) : instanceStyle;
|
|
1616
|
+
const cssSafe = stripTemplateExpressionStyle(merged);
|
|
1617
|
+
if (hasNonEmptyStyle(cssSafe)) rules.push({ style: cssSafe });
|
|
1618
|
+
}
|
|
1619
|
+
if (hasInteractive) rules.push(...instanceInteractive);
|
|
1620
|
+
if (rules.length > 0) {
|
|
1621
|
+
const existing = ctx.collectedInteractiveStyles.get(elementClass);
|
|
1622
|
+
ctx.collectedInteractiveStyles.set(elementClass, existing ? [...existing, ...rules] : rules);
|
|
1623
|
+
propParts.push(`class="${elementClass}"`);
|
|
1624
|
+
}
|
|
1527
1625
|
}
|
|
1528
1626
|
}
|
|
1529
1627
|
const propsStr = propParts.length > 0 ? " " + propParts.join(" ") : "";
|
|
@@ -1630,7 +1728,7 @@ function emitLinkNode(node, ctx) {
|
|
|
1630
1728
|
if (isLinkMapping(nodeHref)) {
|
|
1631
1729
|
if (ctx.isComponentDef) {
|
|
1632
1730
|
const propRef = nodeHref.prop;
|
|
1633
|
-
hrefAttr = ` href={${propRef}
|
|
1731
|
+
hrefAttr = ` href={${linkHrefExpr(propRef)}}`;
|
|
1634
1732
|
} else {
|
|
1635
1733
|
hrefAttr = ' href="#"';
|
|
1636
1734
|
}
|
|
@@ -1644,7 +1742,7 @@ function emitLinkNode(node, ctx) {
|
|
|
1644
1742
|
if (ctx.listIndexVar) expr = replaceItemMetaVars(expr, ctx.listIndexVar, ctx.listSourceVar);
|
|
1645
1743
|
const propDef = ctx.componentProps[expr];
|
|
1646
1744
|
if (propDef && propDef.type === "link") {
|
|
1647
|
-
hrefAttr = ` href={${expr}
|
|
1745
|
+
hrefAttr = ` href={${linkHrefExpr(expr)}}`;
|
|
1648
1746
|
} else {
|
|
1649
1747
|
hrefAttr = ` href={${expr}}`;
|
|
1650
1748
|
}
|
|
@@ -1654,7 +1752,7 @@ function emitLinkNode(node, ctx) {
|
|
|
1654
1752
|
if (ctx.listItemBinding) trimmed = rewriteItemVar(trimmed, ctx.listItemBinding);
|
|
1655
1753
|
if (ctx.listIndexVar) trimmed = replaceItemMetaVars(trimmed, ctx.listIndexVar, ctx.listSourceVar);
|
|
1656
1754
|
const pd = ctx.componentProps[trimmed];
|
|
1657
|
-
return pd?.type === "link" ? `\${${trimmed}
|
|
1755
|
+
return pd?.type === "link" ? `\${${linkHrefExpr(trimmed)}}` : `\${${trimmed}}`;
|
|
1658
1756
|
});
|
|
1659
1757
|
hrefAttr = ` href={\`${resolved}\`}`;
|
|
1660
1758
|
}
|
|
@@ -1837,6 +1935,8 @@ function emitCollectionListNode(node, ctx) {
|
|
|
1837
1935
|
const end = node.limit ? start + node.limit : void 0;
|
|
1838
1936
|
queryChain += `.then(items => items.slice(${start}${end !== void 0 ? `, ${end}` : ""}))`;
|
|
1839
1937
|
}
|
|
1938
|
+
const urlExpr = ctx.collectionUrlExpr?.get(source) ?? `\`/${source}/\${e.data.slug ?? e.id}\``;
|
|
1939
|
+
queryChain += `.then(items => items.map((e) => ({ ...e.data, _id: e.id, _url: ${urlExpr} })))`;
|
|
1840
1940
|
ctx.frontmatterLines.push(`const ${collectionVar} = ${queryChain};`);
|
|
1841
1941
|
const indexVar = `${itemAs}Index`;
|
|
1842
1942
|
const innerCtx = {
|
|
@@ -2127,13 +2227,14 @@ function mergeClassNameOntoRoot(template) {
|
|
|
2127
2227
|
}
|
|
2128
2228
|
return prefix + tagName + ` class={className}` + attrs + close + template.slice(fullMatch.length);
|
|
2129
2229
|
}
|
|
2130
|
-
function emitAstroComponent(name, def, allComponents, breakpoints = DEFAULT_BREAKPOINTS, defaultLocale = "en", responsiveScales, remConfig) {
|
|
2230
|
+
function emitAstroComponent(name, def, allComponents, breakpoints = DEFAULT_BREAKPOINTS, defaultLocale = "en", responsiveScales, remConfig, cmsOptions) {
|
|
2131
2231
|
const comp = def.component;
|
|
2132
2232
|
const propDefs = comp.interface || {};
|
|
2133
2233
|
const structure = comp.structure;
|
|
2134
2234
|
if (!structure) {
|
|
2135
2235
|
return buildNoStructureComponent(name, comp);
|
|
2136
2236
|
}
|
|
2237
|
+
const isCmsConsumer = cmsOptions?.cmsConsumers?.has(name) ?? false;
|
|
2137
2238
|
const ctx = {
|
|
2138
2239
|
imports: /* @__PURE__ */ new Set(),
|
|
2139
2240
|
isComponentDef: true,
|
|
@@ -2150,7 +2251,18 @@ function emitAstroComponent(name, def, allComponents, breakpoints = DEFAULT_BREA
|
|
|
2150
2251
|
imageImports: /* @__PURE__ */ new Map(),
|
|
2151
2252
|
fileDepth: 0,
|
|
2152
2253
|
// components live at src/components/
|
|
2153
|
-
collectedInteractiveStyles: /* @__PURE__ */ new Map()
|
|
2254
|
+
collectedInteractiveStyles: /* @__PURE__ */ new Map(),
|
|
2255
|
+
// Frontmatter sink for collection queries (getCollection) emitted by lists.
|
|
2256
|
+
frontmatterLines: [],
|
|
2257
|
+
astroImports: /* @__PURE__ */ new Set(),
|
|
2258
|
+
cmsConsumers: cmsOptions?.cmsConsumers,
|
|
2259
|
+
collectionUrlExpr: cmsOptions?.collectionUrlExpr,
|
|
2260
|
+
...isCmsConsumer ? {
|
|
2261
|
+
cmsMode: true,
|
|
2262
|
+
cmsEntryBinding: "cms",
|
|
2263
|
+
cmsWrapFn: "r",
|
|
2264
|
+
cmsRichTextFields: cmsOptions?.cmsRichTextFields
|
|
2265
|
+
} : {}
|
|
2154
2266
|
};
|
|
2155
2267
|
let templateBody = nodeToAstro(structure, ctx);
|
|
2156
2268
|
templateBody = mergeClassNameOntoRoot(templateBody);
|
|
@@ -2159,8 +2271,11 @@ function emitAstroComponent(name, def, allComponents, breakpoints = DEFAULT_BREA
|
|
|
2159
2271
|
propDefs,
|
|
2160
2272
|
ctx.imports,
|
|
2161
2273
|
ctx.dynamicTags,
|
|
2162
|
-
ctx.needsI18nResolver ? defaultLocale : void 0,
|
|
2163
|
-
ctx.imageImports
|
|
2274
|
+
ctx.needsI18nResolver || isCmsConsumer ? defaultLocale : void 0,
|
|
2275
|
+
ctx.imageImports,
|
|
2276
|
+
ctx.astroImports,
|
|
2277
|
+
ctx.frontmatterLines,
|
|
2278
|
+
isCmsConsumer
|
|
2164
2279
|
);
|
|
2165
2280
|
const styleSection = comp.css ? `
|
|
2166
2281
|
<style>
|
|
@@ -2177,8 +2292,11 @@ ${generateAllInteractiveCSS(ctx.collectedInteractiveStyles, breakpoints, remConf
|
|
|
2177
2292
|
${frontmatter}---
|
|
2178
2293
|
${templateBody}${styleSection}${interactiveStyleSection}${scriptSection}`;
|
|
2179
2294
|
}
|
|
2180
|
-
function buildFrontmatter(componentName, propDefs, imports, dynamicTags, i18nDefaultLocale, imageImports) {
|
|
2295
|
+
function buildFrontmatter(componentName, propDefs, imports, dynamicTags, i18nDefaultLocale, imageImports, astroImports, frontmatterLines, isCmsConsumer) {
|
|
2181
2296
|
const lines = [];
|
|
2297
|
+
if (astroImports && astroImports.size > 0) {
|
|
2298
|
+
lines.push(`import { ${Array.from(astroImports).sort().join(", ")} } from 'astro:content';`);
|
|
2299
|
+
}
|
|
2182
2300
|
for (const imp of Array.from(imports).sort()) {
|
|
2183
2301
|
lines.push(`import ${astroComponentName(imp)} from './${imp}.astro';`);
|
|
2184
2302
|
}
|
|
@@ -2199,6 +2317,9 @@ function buildFrontmatter(componentName, propDefs, imports, dynamicTags, i18nDef
|
|
|
2199
2317
|
const optional = "default" in propDef && propDef.default !== void 0;
|
|
2200
2318
|
lines.push(` ${propName}${optional ? "?" : ""}: ${tsType};`);
|
|
2201
2319
|
}
|
|
2320
|
+
if (isCmsConsumer) {
|
|
2321
|
+
lines.push(" cms?: any;");
|
|
2322
|
+
}
|
|
2202
2323
|
lines.push(" class?: string;");
|
|
2203
2324
|
lines.push("}");
|
|
2204
2325
|
lines.push("");
|
|
@@ -2214,6 +2335,9 @@ function buildFrontmatter(componentName, propDefs, imports, dynamicTags, i18nDef
|
|
|
2214
2335
|
destructParts.push(propName);
|
|
2215
2336
|
}
|
|
2216
2337
|
}
|
|
2338
|
+
if (isCmsConsumer) {
|
|
2339
|
+
destructParts.push("cms");
|
|
2340
|
+
}
|
|
2217
2341
|
destructParts.push('class: className = ""');
|
|
2218
2342
|
if (destructParts.length <= 3 && destructParts.join(", ").length < 80) {
|
|
2219
2343
|
lines.push(`const { ${destructParts.join(", ")} } = Astro.props;`);
|
|
@@ -2241,6 +2365,10 @@ function buildFrontmatter(componentName, propDefs, imports, dynamicTags, i18nDef
|
|
|
2241
2365
|
lines.push(` return v ?? '';`);
|
|
2242
2366
|
lines.push(`};`);
|
|
2243
2367
|
}
|
|
2368
|
+
if (frontmatterLines && frontmatterLines.length > 0) {
|
|
2369
|
+
lines.push("");
|
|
2370
|
+
for (const line of frontmatterLines) lines.push(line);
|
|
2371
|
+
}
|
|
2244
2372
|
if (lines.length > 0) lines.push("");
|
|
2245
2373
|
return lines.join("\n");
|
|
2246
2374
|
}
|
|
@@ -2446,7 +2574,111 @@ import BaseLayout from '${layoutImport}';
|
|
|
2446
2574
|
`;
|
|
2447
2575
|
}
|
|
2448
2576
|
|
|
2577
|
+
// lib/server/astro/normalizeOrphanTemplateProps.ts
|
|
2578
|
+
var BARE_IDENT_BODY = /^\s*([a-zA-Z_$][a-zA-Z0-9_$]*)(?:\s*\.\s*[a-zA-Z_$][a-zA-Z0-9_$]*)*\s*$/;
|
|
2579
|
+
var RESERVED_BASES = /* @__PURE__ */ new Set([
|
|
2580
|
+
"cms",
|
|
2581
|
+
"item",
|
|
2582
|
+
"page",
|
|
2583
|
+
"itemIndex",
|
|
2584
|
+
"itemFirst",
|
|
2585
|
+
"itemLast"
|
|
2586
|
+
]);
|
|
2587
|
+
function recordRef(base, declared, listVars, out) {
|
|
2588
|
+
if (RESERVED_BASES.has(base)) return;
|
|
2589
|
+
if (listVars.has(base)) return;
|
|
2590
|
+
if (declared.has(base)) return;
|
|
2591
|
+
out.add(base);
|
|
2592
|
+
}
|
|
2593
|
+
function findOrphanRefs(value, declared, listVars, out) {
|
|
2594
|
+
if (value == null) return;
|
|
2595
|
+
if (typeof value === "string") {
|
|
2596
|
+
const re = /\{\{([^}]+)\}\}/g;
|
|
2597
|
+
let m;
|
|
2598
|
+
while ((m = re.exec(value)) !== null) {
|
|
2599
|
+
const body = m[1];
|
|
2600
|
+
const idMatch = body.match(BARE_IDENT_BODY);
|
|
2601
|
+
if (!idMatch) continue;
|
|
2602
|
+
recordRef(idMatch[1], declared, listVars, out);
|
|
2603
|
+
}
|
|
2604
|
+
return;
|
|
2605
|
+
}
|
|
2606
|
+
if (Array.isArray(value)) {
|
|
2607
|
+
for (const v of value) findOrphanRefs(v, declared, listVars, out);
|
|
2608
|
+
return;
|
|
2609
|
+
}
|
|
2610
|
+
if (typeof value !== "object") return;
|
|
2611
|
+
const obj = value;
|
|
2612
|
+
if (obj._mapping === true && typeof obj.prop === "string") {
|
|
2613
|
+
recordRef(obj.prop, declared, listVars, out);
|
|
2614
|
+
}
|
|
2615
|
+
let nextListVars = listVars;
|
|
2616
|
+
if (obj.type === "list") {
|
|
2617
|
+
const itemAs = typeof obj.itemAs === "string" && obj.itemAs ? obj.itemAs : "item";
|
|
2618
|
+
nextListVars = new Set(listVars);
|
|
2619
|
+
nextListVars.add(itemAs);
|
|
2620
|
+
}
|
|
2621
|
+
for (const key of Object.keys(obj)) {
|
|
2622
|
+
if (obj.type === "component" && key === "props") continue;
|
|
2623
|
+
findOrphanRefs(obj[key], declared, nextListVars, out);
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
function forwardLiftedPropsOnInstances(structure, hostProps, liftedByComp) {
|
|
2627
|
+
if (structure == null || typeof structure !== "object") return;
|
|
2628
|
+
if (Array.isArray(structure)) {
|
|
2629
|
+
for (const s of structure) forwardLiftedPropsOnInstances(s, hostProps, liftedByComp);
|
|
2630
|
+
return;
|
|
2631
|
+
}
|
|
2632
|
+
const obj = structure;
|
|
2633
|
+
if (obj.type === "component" && typeof obj.component === "string") {
|
|
2634
|
+
const lifted = liftedByComp.get(obj.component);
|
|
2635
|
+
if (lifted && lifted.size > 0) {
|
|
2636
|
+
const props = obj.props ?? {};
|
|
2637
|
+
let changed = false;
|
|
2638
|
+
for (const propName of lifted) {
|
|
2639
|
+
if (propName in props) continue;
|
|
2640
|
+
if (!hostProps.has(propName)) continue;
|
|
2641
|
+
props[propName] = `{{${propName}}}`;
|
|
2642
|
+
changed = true;
|
|
2643
|
+
}
|
|
2644
|
+
if (changed) obj.props = props;
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
for (const key of Object.keys(obj)) {
|
|
2648
|
+
forwardLiftedPropsOnInstances(obj[key], hostProps, liftedByComp);
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
function normalizeOrphanTemplateProps(components) {
|
|
2652
|
+
const next = {};
|
|
2653
|
+
const liftedByComp = /* @__PURE__ */ new Map();
|
|
2654
|
+
for (const [name, def] of Object.entries(components)) {
|
|
2655
|
+
const cloned = structuredClone(def);
|
|
2656
|
+
next[name] = cloned;
|
|
2657
|
+
const comp = cloned.component;
|
|
2658
|
+
if (!comp || !comp.structure) continue;
|
|
2659
|
+
const iface = comp.interface ?? {};
|
|
2660
|
+
const declared = new Set(Object.keys(iface));
|
|
2661
|
+
const orphans = /* @__PURE__ */ new Set();
|
|
2662
|
+
findOrphanRefs(comp.structure, declared, /* @__PURE__ */ new Set(), orphans);
|
|
2663
|
+
if (orphans.size === 0) continue;
|
|
2664
|
+
const augmented = { ...iface };
|
|
2665
|
+
for (const propName of orphans) {
|
|
2666
|
+
augmented[propName] = { type: "string", default: "" };
|
|
2667
|
+
}
|
|
2668
|
+
comp.interface = augmented;
|
|
2669
|
+
liftedByComp.set(name, orphans);
|
|
2670
|
+
}
|
|
2671
|
+
for (const [, hostDef] of Object.entries(next)) {
|
|
2672
|
+
const comp = hostDef.component;
|
|
2673
|
+
if (!comp || !comp.structure) continue;
|
|
2674
|
+
const hostProps = new Set(Object.keys(comp.interface ?? {}));
|
|
2675
|
+
forwardLiftedPropsOnInstances(comp.structure, hostProps, liftedByComp);
|
|
2676
|
+
}
|
|
2677
|
+
return next;
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2449
2680
|
// lib/server/astro/cmsPageEmitter.ts
|
|
2681
|
+
var CMS_SLUG_PLACEHOLDER = "__placeholder__";
|
|
2450
2682
|
function escapeTemplateLiteral3(s) {
|
|
2451
2683
|
return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
2452
2684
|
}
|
|
@@ -2622,7 +2854,12 @@ function emitCMSPage(options) {
|
|
|
2622
2854
|
processedRawHtml,
|
|
2623
2855
|
imageImports: /* @__PURE__ */ new Map(),
|
|
2624
2856
|
fileDepth,
|
|
2625
|
-
collectedInteractiveStyles: /* @__PURE__ */ new Map()
|
|
2857
|
+
collectedInteractiveStyles: /* @__PURE__ */ new Map(),
|
|
2858
|
+
// Frontmatter sink for any page-level collection-sourced lists.
|
|
2859
|
+
frontmatterLines: [],
|
|
2860
|
+
astroImports: /* @__PURE__ */ new Set(),
|
|
2861
|
+
cmsConsumers: options.cmsConsumers,
|
|
2862
|
+
collectionUrlExpr: options.collectionUrlExpr
|
|
2626
2863
|
};
|
|
2627
2864
|
const templateBody = nodeToAstro(root, ctx);
|
|
2628
2865
|
const importLines = [];
|
|
@@ -2643,9 +2880,13 @@ function emitCMSPage(options) {
|
|
|
2643
2880
|
const staticPaths = buildGetStaticPaths(cmsSchema, isMultiLocale, i18nConfig, locale);
|
|
2644
2881
|
const scriptsArrayLiteral = scriptPaths.length > 0 ? `[${scriptPaths.map((s) => `"${s}"`).join(", ")}]` : "[]";
|
|
2645
2882
|
const libraryTagsLiteral = `{ headCSS: \`${escapeTemplateLiteral3(libraryTags.headCSS || "")}\`, headJS: \`${escapeTemplateLiteral3(libraryTags.headJS || "")}\`, bodyEndJS: \`${escapeTemplateLiteral3(libraryTags.bodyEndJS || "")}\` }`;
|
|
2883
|
+
const slugField = cmsSchema.slugField || "slug";
|
|
2646
2884
|
const escapedMeta = escapeTemplateLiteral3(meta).replace(
|
|
2647
2885
|
/\{\{cms\.([^}]+)\}\}/g,
|
|
2648
2886
|
(_, fieldPath) => `\${${wrapFn}(${binding}.data.${fieldPath.trim()})}`
|
|
2887
|
+
).replace(
|
|
2888
|
+
new RegExp(CMS_SLUG_PLACEHOLDER, "g"),
|
|
2889
|
+
`\${${wrapFn}(${binding}.data.${slugField}) || ${binding}.id}`
|
|
2649
2890
|
);
|
|
2650
2891
|
const escapedFontPreloads = escapeTemplateLiteral3(fontPreloads);
|
|
2651
2892
|
const titleExpr = transformTitleExpression(title, binding, richTextFields, wrapFn);
|
|
@@ -2653,6 +2894,7 @@ function emitCMSPage(options) {
|
|
|
2653
2894
|
if (v && typeof v === 'object' && v._i18n) return v['${locale}'] ?? v['${i18nConfig.defaultLocale}'] ?? Object.values(v).find(x => x !== true && x !== undefined) ?? '';
|
|
2654
2895
|
return v ?? '';
|
|
2655
2896
|
}`;
|
|
2897
|
+
const extraFrontmatter = ctx.frontmatterLines && ctx.frontmatterLines.length > 0 ? "\n" + ctx.frontmatterLines.join("\n") : "";
|
|
2656
2898
|
const interactiveStyleSection = ctx.collectedInteractiveStyles.size > 0 ? `
|
|
2657
2899
|
<style is:global>
|
|
2658
2900
|
${generateAllInteractiveCSS(ctx.collectedInteractiveStyles, breakpoints, remConfig, responsiveScales)}
|
|
@@ -2663,7 +2905,7 @@ ${importLines.join("\n")}
|
|
|
2663
2905
|
|
|
2664
2906
|
${staticPaths}
|
|
2665
2907
|
|
|
2666
|
-
${resolverHelper}
|
|
2908
|
+
${resolverHelper}${extraFrontmatter}
|
|
2667
2909
|
---
|
|
2668
2910
|
<BaseLayout
|
|
2669
2911
|
title=${titleExpr}
|
|
@@ -2911,6 +3153,73 @@ function cmsFieldToZod(field) {
|
|
|
2911
3153
|
return "z.string()";
|
|
2912
3154
|
}
|
|
2913
3155
|
}
|
|
3156
|
+
function serializeRichTextValue(value) {
|
|
3157
|
+
const one = (v) => {
|
|
3158
|
+
if (v == null || typeof v === "string") return v ?? "";
|
|
3159
|
+
if (isTiptapDocument(v)) return tiptapToHtml(v);
|
|
3160
|
+
if (typeof v === "object" && v !== null && typeof v.html === "string") {
|
|
3161
|
+
return v.html;
|
|
3162
|
+
}
|
|
3163
|
+
return v;
|
|
3164
|
+
};
|
|
3165
|
+
if (isI18nValue(value)) {
|
|
3166
|
+
const out = { _i18n: true };
|
|
3167
|
+
for (const [k, v] of Object.entries(value)) {
|
|
3168
|
+
if (k === "_i18n") continue;
|
|
3169
|
+
out[k] = one(v);
|
|
3170
|
+
}
|
|
3171
|
+
return out;
|
|
3172
|
+
}
|
|
3173
|
+
return one(value);
|
|
3174
|
+
}
|
|
3175
|
+
function collectComponentRefs(node, acc) {
|
|
3176
|
+
if (Array.isArray(node)) {
|
|
3177
|
+
for (const child of node) collectComponentRefs(child, acc);
|
|
3178
|
+
return;
|
|
3179
|
+
}
|
|
3180
|
+
if (!node || typeof node !== "object") return;
|
|
3181
|
+
const n = node;
|
|
3182
|
+
if (n.type === "component" && typeof n.component === "string") {
|
|
3183
|
+
acc.add(n.component);
|
|
3184
|
+
}
|
|
3185
|
+
for (const value of Object.values(n)) {
|
|
3186
|
+
if (value && typeof value === "object") collectComponentRefs(value, acc);
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
function computeCmsConsumerComponents(components) {
|
|
3190
|
+
const consumers = /* @__PURE__ */ new Set();
|
|
3191
|
+
const refsByComponent = /* @__PURE__ */ new Map();
|
|
3192
|
+
for (const [name, def] of Object.entries(components)) {
|
|
3193
|
+
const structure = def.component?.structure;
|
|
3194
|
+
if (structure && JSON.stringify(structure).includes("{{cms.")) {
|
|
3195
|
+
consumers.add(name);
|
|
3196
|
+
}
|
|
3197
|
+
const refs = /* @__PURE__ */ new Set();
|
|
3198
|
+
collectComponentRefs(structure, refs);
|
|
3199
|
+
refsByComponent.set(name, refs);
|
|
3200
|
+
}
|
|
3201
|
+
let changed = true;
|
|
3202
|
+
while (changed) {
|
|
3203
|
+
changed = false;
|
|
3204
|
+
for (const [name, refs] of refsByComponent) {
|
|
3205
|
+
if (consumers.has(name)) continue;
|
|
3206
|
+
for (const ref of refs) {
|
|
3207
|
+
if (consumers.has(ref)) {
|
|
3208
|
+
consumers.add(name);
|
|
3209
|
+
changed = true;
|
|
3210
|
+
break;
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3215
|
+
return consumers;
|
|
3216
|
+
}
|
|
3217
|
+
function buildCollectionUrlExpr(schema) {
|
|
3218
|
+
const slugField = schema.slugField || "slug";
|
|
3219
|
+
const pattern = schema.urlPattern || `/${schema.id}/{{slug}}`;
|
|
3220
|
+
const body = pattern.replace(/\{\{[^}]+\}\}/, "${e.data." + slugField + " ?? e.id}");
|
|
3221
|
+
return "`" + body + "`";
|
|
3222
|
+
}
|
|
2914
3223
|
function buildSSRFallbackPage(result, importPath, fontPreloads, libraryTags, defaultTheme, scriptPaths) {
|
|
2915
3224
|
const escapedMeta = escapeTemplateLiteral4(result.meta);
|
|
2916
3225
|
const escapedHTML = escapeTemplateLiteral4(result.html);
|
|
@@ -3010,7 +3319,7 @@ async function buildAstroProject(projectRoot, outputDir) {
|
|
|
3010
3319
|
}
|
|
3011
3320
|
}
|
|
3012
3321
|
}
|
|
3013
|
-
function processRenderResult(result, urlPath, astroFilePath, fileDepth, pageData, pageName,
|
|
3322
|
+
function processRenderResult(result, urlPath, astroFilePath, fileDepth, pageData, pageName, isCMSPage4) {
|
|
3014
3323
|
mergeInteractiveStyles(result.interactiveStylesMap);
|
|
3015
3324
|
if (result.componentCSS) {
|
|
3016
3325
|
allComponentCSS.add(result.componentCSS);
|
|
@@ -3037,7 +3346,7 @@ async function buildAstroProject(projectRoot, outputDir) {
|
|
|
3037
3346
|
astroFilePath,
|
|
3038
3347
|
pageData,
|
|
3039
3348
|
pageName,
|
|
3040
|
-
isCMSPage:
|
|
3349
|
+
isCMSPage: isCMSPage4,
|
|
3041
3350
|
ssrFallbackCollector: result.ssrFallbackCollector,
|
|
3042
3351
|
processedRawHtmlCollector: result.processedRawHtmlCollector
|
|
3043
3352
|
});
|
|
@@ -3063,7 +3372,7 @@ async function buildAstroProject(projectRoot, outputDir) {
|
|
|
3063
3372
|
const isDefault = locale === i18nConfig.defaultLocale;
|
|
3064
3373
|
let slug;
|
|
3065
3374
|
if (slugs && slugs[locale]) {
|
|
3066
|
-
slug = slugs[locale];
|
|
3375
|
+
slug = slugs[locale].replace(/^\/+/, "");
|
|
3067
3376
|
} else if (basePath === "/") {
|
|
3068
3377
|
slug = "";
|
|
3069
3378
|
} else {
|
|
@@ -3136,6 +3445,25 @@ async function buildAstroProject(projectRoot, outputDir) {
|
|
|
3136
3445
|
const templatesDir = projectPaths.templates();
|
|
3137
3446
|
const templateSchemas = [];
|
|
3138
3447
|
let cmsPageCount = 0;
|
|
3448
|
+
const cmsConsumerComponents = computeCmsConsumerComponents(globalComponents);
|
|
3449
|
+
const collectionUrlExpr = /* @__PURE__ */ new Map();
|
|
3450
|
+
const mergedRichTextFields = /* @__PURE__ */ new Set();
|
|
3451
|
+
if (existsSync(templatesDir)) {
|
|
3452
|
+
for (const file of readdirSync(templatesDir).filter((f) => f.endsWith(".json"))) {
|
|
3453
|
+
const tc = await loadJSONFile(join(templatesDir, file));
|
|
3454
|
+
if (!tc) continue;
|
|
3455
|
+
try {
|
|
3456
|
+
const pd = parseJSON(tc);
|
|
3457
|
+
const schema = pd.meta?.cms;
|
|
3458
|
+
if (!schema?.id) continue;
|
|
3459
|
+
collectionUrlExpr.set(schema.id, buildCollectionUrlExpr(schema));
|
|
3460
|
+
for (const [fn, fd] of Object.entries(schema.fields || {})) {
|
|
3461
|
+
if (fd.type === "rich-text") mergedRichTextFields.add(fn);
|
|
3462
|
+
}
|
|
3463
|
+
} catch {
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3139
3467
|
if (existsSync(templatesDir)) {
|
|
3140
3468
|
const templateFiles = readdirSync(templatesDir).filter((f) => f.endsWith(".json"));
|
|
3141
3469
|
for (const file of templateFiles) {
|
|
@@ -3156,7 +3484,7 @@ async function buildAstroProject(projectRoot, outputDir) {
|
|
|
3156
3484
|
const items = await cmsService.queryItems({ collection: cmsSchema.id });
|
|
3157
3485
|
const itemCount = items.length;
|
|
3158
3486
|
const defaultLocale = i18nConfig.defaultLocale;
|
|
3159
|
-
const dummyPath = cmsSchema.urlPattern.replace("{{slug}}",
|
|
3487
|
+
const dummyPath = cmsSchema.urlPattern.replace("{{slug}}", CMS_SLUG_PLACEHOLDER);
|
|
3160
3488
|
const metaResult = await renderPageSSR(
|
|
3161
3489
|
pageData,
|
|
3162
3490
|
globalComponents,
|
|
@@ -3223,7 +3551,9 @@ async function buildAstroProject(projectRoot, outputDir) {
|
|
|
3223
3551
|
slugMappings,
|
|
3224
3552
|
imageFormat: configService.getImageFormat(),
|
|
3225
3553
|
processedRawHtml: metaResult.processedRawHtmlCollector,
|
|
3226
|
-
remConfig: remConversionConfig
|
|
3554
|
+
remConfig: remConversionConfig,
|
|
3555
|
+
cmsConsumers: cmsConsumerComponents,
|
|
3556
|
+
collectionUrlExpr
|
|
3227
3557
|
});
|
|
3228
3558
|
const astroFileFull = join(pagesOutDir, astroFilePath);
|
|
3229
3559
|
const astroFileDir = astroFileFull.substring(0, astroFileFull.lastIndexOf("/"));
|
|
@@ -3307,10 +3637,15 @@ const { title, meta = '', scripts = [], locale = 'en', theme = '${themeConfig.de
|
|
|
3307
3637
|
</html>
|
|
3308
3638
|
`;
|
|
3309
3639
|
await writeFile2(join(layoutsDir, "BaseLayout.astro"), baseLayoutContent, "utf-8");
|
|
3640
|
+
const emittableComponents = normalizeOrphanTemplateProps(globalComponents);
|
|
3310
3641
|
let componentFileCount = 0;
|
|
3311
|
-
for (const [compName, compDef] of Object.entries(
|
|
3642
|
+
for (const [compName, compDef] of Object.entries(emittableComponents)) {
|
|
3312
3643
|
try {
|
|
3313
|
-
const astroContent = emitAstroComponent(compName, compDef,
|
|
3644
|
+
const astroContent = emitAstroComponent(compName, compDef, emittableComponents, breakpoints, i18nConfig.defaultLocale, responsiveScales, remConversionConfig, {
|
|
3645
|
+
cmsConsumers: cmsConsumerComponents,
|
|
3646
|
+
cmsRichTextFields: mergedRichTextFields,
|
|
3647
|
+
collectionUrlExpr
|
|
3648
|
+
});
|
|
3314
3649
|
await writeFile2(join(componentsOutDir, `${compName}.astro`), astroContent, "utf-8");
|
|
3315
3650
|
componentFileCount++;
|
|
3316
3651
|
} catch (error) {
|
|
@@ -3327,7 +3662,7 @@ const { title, meta = '', scripts = [], locale = 'en', theme = '${themeConfig.de
|
|
|
3327
3662
|
const pageSlugMap = result.pageData.meta?.slugs ? computePageSlugMap(result.pageData.meta.slugs, i18nConfig) : void 0;
|
|
3328
3663
|
astroContent = emitAstroPage({
|
|
3329
3664
|
pageData: result.pageData,
|
|
3330
|
-
globalComponents,
|
|
3665
|
+
globalComponents: emittableComponents,
|
|
3331
3666
|
title: result.title,
|
|
3332
3667
|
meta: result.meta,
|
|
3333
3668
|
locale: result.locale,
|
|
@@ -3390,14 +3725,21 @@ export const GET: APIRoute = () => {
|
|
|
3390
3725
|
for (const schema of templateSchemas) {
|
|
3391
3726
|
const collectionDir = join(contentDir, schema.id);
|
|
3392
3727
|
mkdirSync(collectionDir, { recursive: true });
|
|
3728
|
+
const richTextFieldNames = Object.entries(schema.fields || {}).filter(([, fd]) => fd.type === "rich-text").map(([fn]) => fn);
|
|
3393
3729
|
const cmsItemsDir = join(projectPaths.cms(), schema.id);
|
|
3394
3730
|
if (existsSync(cmsItemsDir)) {
|
|
3395
|
-
const
|
|
3731
|
+
const isDevBuild = process.env.MENO_DEV_BUILD === "true";
|
|
3732
|
+
const itemFiles = readdirSync(cmsItemsDir).filter(
|
|
3733
|
+
(f) => f.endsWith(".json") && (isDevBuild || !f.endsWith(`${CMS_DRAFT_SUFFIX}.json`))
|
|
3734
|
+
);
|
|
3396
3735
|
for (const itemFile of itemFiles) {
|
|
3397
3736
|
try {
|
|
3398
3737
|
const rawContent = await readFile(join(cmsItemsDir, itemFile), "utf-8");
|
|
3399
3738
|
const item = JSON.parse(rawContent);
|
|
3400
3739
|
const resolved = { ...item };
|
|
3740
|
+
for (const fieldName of richTextFieldNames) {
|
|
3741
|
+
resolved[fieldName] = serializeRichTextValue(resolved[fieldName]);
|
|
3742
|
+
}
|
|
3401
3743
|
await writeFile2(
|
|
3402
3744
|
join(collectionDir, itemFile),
|
|
3403
3745
|
JSON.stringify(resolved, null, 2),
|
|
@@ -3470,14 +3812,12 @@ export { collections };
|
|
|
3470
3812
|
preview: "astro preview"
|
|
3471
3813
|
},
|
|
3472
3814
|
dependencies: {
|
|
3473
|
-
|
|
3815
|
+
// Astro 5 (stable Vite), NOT 6 — Astro 6's rolldown-vite breaks
|
|
3816
|
+
// @tailwindcss/vite at build time ("Missing field `tsconfigPaths`").
|
|
3817
|
+
"astro": "^5.0.0",
|
|
3474
3818
|
"@astrojs/sitemap": "^3.0.0",
|
|
3475
3819
|
"@tailwindcss/vite": "^4.0.0",
|
|
3476
3820
|
"tailwindcss": "^4.0.0"
|
|
3477
|
-
},
|
|
3478
|
-
// Astro 6 expects Vite 7; pin it so npm doesn't pull Vite 8+ and warn.
|
|
3479
|
-
overrides: {
|
|
3480
|
-
"vite": "^7.0.0"
|
|
3481
3821
|
}
|
|
3482
3822
|
};
|
|
3483
3823
|
await writeFile2(join(outDir, "package.json"), JSON.stringify(packageJson, null, 2), "utf-8");
|
|
@@ -3505,6 +3845,15 @@ export default defineConfig({${siteUrl ? `
|
|
|
3505
3845
|
extends: "astro/tsconfigs/strict"
|
|
3506
3846
|
};
|
|
3507
3847
|
await writeFile2(join(outDir, "tsconfig.json"), JSON.stringify(tsConfig, null, 2), "utf-8");
|
|
3848
|
+
const netlifyToml = `# Generated by Meno's Astro build.
|
|
3849
|
+
[build]
|
|
3850
|
+
command = "npm run build"
|
|
3851
|
+
publish = "dist"
|
|
3852
|
+
|
|
3853
|
+
[build.environment]
|
|
3854
|
+
NODE_VERSION = "22"
|
|
3855
|
+
`;
|
|
3856
|
+
await writeFile2(join(outDir, "netlify.toml"), netlifyToml, "utf-8");
|
|
3508
3857
|
await writeFile2(join(outDir, "src", "env.d.ts"), '/// <reference path="../.astro/types.d.ts" />\n', "utf-8");
|
|
3509
3858
|
const totalPages = allResults.length;
|
|
3510
3859
|
return {
|
|
@@ -3515,131 +3864,1048 @@ export default defineConfig({${siteUrl ? `
|
|
|
3515
3864
|
};
|
|
3516
3865
|
}
|
|
3517
3866
|
|
|
3518
|
-
//
|
|
3519
|
-
import { existsSync as existsSync2, readdirSync as readdirSync2 } from "fs";
|
|
3520
|
-
import {
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
"flex-shrink",
|
|
3539
|
-
"flex",
|
|
3540
|
-
"order",
|
|
3541
|
-
"orphans",
|
|
3542
|
-
"widows",
|
|
3543
|
-
"column-count",
|
|
3544
|
-
"font-weight",
|
|
3545
|
-
"tab-size"
|
|
3546
|
-
]);
|
|
3547
|
-
var TIME_PROPERTIES = /* @__PURE__ */ new Set([
|
|
3548
|
-
"transition-duration",
|
|
3549
|
-
"transition-delay",
|
|
3550
|
-
"animation-duration",
|
|
3551
|
-
"animation-delay"
|
|
3552
|
-
]);
|
|
3553
|
-
function normalizeZero(cssProp, cssValue) {
|
|
3554
|
-
if (cssValue !== "0") return cssValue;
|
|
3555
|
-
if (UNITLESS_PROPERTIES.has(cssProp)) return cssValue;
|
|
3556
|
-
if (TIME_PROPERTIES.has(cssProp)) return cssValue;
|
|
3557
|
-
return "0px";
|
|
3558
|
-
}
|
|
3559
|
-
var COLOR_PROPS_CAMEL = /* @__PURE__ */ new Set(["color", "backgroundColor", "borderColor"]);
|
|
3560
|
-
function maybeWrapColorVar(camelProp, value) {
|
|
3561
|
-
if (!COLOR_PROPS_CAMEL.has(camelProp)) return value;
|
|
3562
|
-
if (!value) return value;
|
|
3563
|
-
if (value.startsWith("#")) return value;
|
|
3564
|
-
if (value.startsWith("var(")) return value;
|
|
3565
|
-
if (value.includes("(")) return value;
|
|
3566
|
-
if (isCssNamedColor(value)) return value;
|
|
3567
|
-
return `var(--${value})`;
|
|
3867
|
+
// build-next.ts
|
|
3868
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, mkdirSync as mkdirSync2, rmSync as rmSync2, statSync as statSync2, copyFileSync as copyFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
3869
|
+
import { writeFile as writeFile3, readFile as readFile2 } from "fs/promises";
|
|
3870
|
+
import { join as join2 } from "path";
|
|
3871
|
+
import { createHash as createHash2 } from "crypto";
|
|
3872
|
+
function hashContent3(content) {
|
|
3873
|
+
return createHash2("sha256").update(content).digest("hex").slice(0, 8);
|
|
3874
|
+
}
|
|
3875
|
+
function writePageScript2(javascript, scriptsDir) {
|
|
3876
|
+
if (!javascript) return [];
|
|
3877
|
+
const hash = hashContent3(javascript);
|
|
3878
|
+
const scriptFile = `${hash}.js`;
|
|
3879
|
+
if (!existsSync2(scriptsDir)) {
|
|
3880
|
+
mkdirSync2(scriptsDir, { recursive: true });
|
|
3881
|
+
}
|
|
3882
|
+
const fullScriptPath = join2(scriptsDir, scriptFile);
|
|
3883
|
+
if (!existsSync2(fullScriptPath)) {
|
|
3884
|
+
writeFileSync2(fullScriptPath, javascript, "utf-8");
|
|
3885
|
+
}
|
|
3886
|
+
return [`/_scripts/${scriptFile}`];
|
|
3568
3887
|
}
|
|
3569
|
-
function
|
|
3570
|
-
|
|
3888
|
+
function copyDirectory2(src, dest, filter) {
|
|
3889
|
+
if (!existsSync2(src)) return;
|
|
3890
|
+
if (!existsSync2(dest)) mkdirSync2(dest, { recursive: true });
|
|
3891
|
+
const files = readdirSync2(src);
|
|
3892
|
+
for (const file of files) {
|
|
3893
|
+
if (filter && !filter(file)) continue;
|
|
3894
|
+
const srcPath = join2(src, file);
|
|
3895
|
+
const destPath = join2(dest, file);
|
|
3896
|
+
const stat = statSync2(srcPath);
|
|
3897
|
+
if (stat.isDirectory()) copyDirectory2(srcPath, destPath, filter);
|
|
3898
|
+
else copyFileSync2(srcPath, destPath);
|
|
3899
|
+
}
|
|
3571
3900
|
}
|
|
3572
|
-
function
|
|
3573
|
-
return
|
|
3901
|
+
function isCMSPage2(pageData) {
|
|
3902
|
+
return pageData.meta?.source === "cms" && !!pageData.meta?.cms;
|
|
3574
3903
|
}
|
|
3575
|
-
function
|
|
3576
|
-
|
|
3904
|
+
function buildCMSItemPath(urlPattern, item, slugField, locale, i18nConfig) {
|
|
3905
|
+
let slug = item[slugField] ?? item._slug ?? item._id;
|
|
3906
|
+
if (isI18nValue(slug)) {
|
|
3907
|
+
slug = resolveI18nValue(slug, locale, i18nConfig);
|
|
3908
|
+
}
|
|
3909
|
+
return urlPattern.replace("{{slug}}", String(slug));
|
|
3577
3910
|
}
|
|
3578
|
-
function
|
|
3579
|
-
const
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
for (const
|
|
3583
|
-
if (
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
out.push(buf);
|
|
3588
|
-
buf = "";
|
|
3589
|
-
}
|
|
3590
|
-
continue;
|
|
3911
|
+
function scanJSONFiles2(dir, prefix = "") {
|
|
3912
|
+
const results = [];
|
|
3913
|
+
if (!existsSync2(dir)) return results;
|
|
3914
|
+
const entries = readdirSync2(dir, { withFileTypes: true });
|
|
3915
|
+
for (const entry of entries) {
|
|
3916
|
+
if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
3917
|
+
results.push(prefix ? `${prefix}/${entry.name}` : entry.name);
|
|
3918
|
+
} else if (entry.isDirectory()) {
|
|
3919
|
+
results.push(...scanJSONFiles2(join2(dir, entry.name), prefix ? `${prefix}/${entry.name}` : entry.name));
|
|
3591
3920
|
}
|
|
3592
|
-
buf += ch;
|
|
3593
3921
|
}
|
|
3594
|
-
|
|
3595
|
-
return out;
|
|
3922
|
+
return results;
|
|
3596
3923
|
}
|
|
3597
|
-
function
|
|
3598
|
-
|
|
3599
|
-
|
|
3924
|
+
function escapeTemplateLiteral5(s) {
|
|
3925
|
+
return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
3926
|
+
}
|
|
3927
|
+
function escapeSingleQuoted(s) {
|
|
3928
|
+
return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
3929
|
+
}
|
|
3930
|
+
function urlPathToAppRoute(urlPath) {
|
|
3931
|
+
if (urlPath === "/" || urlPath === "") {
|
|
3932
|
+
return { dir: "", isRoot: true };
|
|
3600
3933
|
}
|
|
3601
|
-
const
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3934
|
+
const trimmed = urlPath.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
3935
|
+
return { dir: trimmed, isRoot: false };
|
|
3936
|
+
}
|
|
3937
|
+
function metaHtmlToJSX(metaHtml) {
|
|
3938
|
+
if (!metaHtml || !metaHtml.trim()) return "";
|
|
3939
|
+
const lines = metaHtml.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
3940
|
+
const elements = [];
|
|
3941
|
+
for (const line of lines) {
|
|
3942
|
+
const titleMatch = line.match(/^<title>([\s\S]*?)<\/title>$/);
|
|
3943
|
+
if (titleMatch) {
|
|
3944
|
+
elements.push(`<title>{${JSON.stringify(decodeBasicEntities(titleMatch[1]))}}</title>`);
|
|
3945
|
+
continue;
|
|
3606
3946
|
}
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3947
|
+
const selfMatch = line.match(/^<(meta|link)\s+([\s\S]*?)\s*\/?>$/);
|
|
3948
|
+
if (selfMatch) {
|
|
3949
|
+
const tagName = selfMatch[1];
|
|
3950
|
+
const attrsSrc = selfMatch[2];
|
|
3951
|
+
const attrs = parseAttrs(attrsSrc);
|
|
3952
|
+
const attrParts = Object.entries(attrs).map(([k, v]) => `${jsxAttrName(k)}=${JSON.stringify(v)}`).join(" ");
|
|
3953
|
+
elements.push(`<${tagName} ${attrParts} />`);
|
|
3954
|
+
continue;
|
|
3612
3955
|
}
|
|
3613
|
-
return null;
|
|
3614
3956
|
}
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3957
|
+
return elements.join("\n ");
|
|
3958
|
+
}
|
|
3959
|
+
var JSX_ATTR_NAMES = {
|
|
3960
|
+
"http-equiv": "httpEquiv",
|
|
3961
|
+
"hreflang": "hrefLang",
|
|
3962
|
+
"crossorigin": "crossOrigin",
|
|
3963
|
+
"referrerpolicy": "referrerPolicy",
|
|
3964
|
+
"imagesrcset": "imageSrcSet",
|
|
3965
|
+
"imagesizes": "imageSizes",
|
|
3966
|
+
"fetchpriority": "fetchPriority",
|
|
3967
|
+
"class": "className",
|
|
3968
|
+
"for": "htmlFor",
|
|
3969
|
+
"charset": "charSet"
|
|
3970
|
+
};
|
|
3971
|
+
function jsxAttrName(name) {
|
|
3972
|
+
return JSX_ATTR_NAMES[name] ?? name;
|
|
3973
|
+
}
|
|
3974
|
+
function parseAttrs(src) {
|
|
3975
|
+
const out = {};
|
|
3976
|
+
const re = /([a-zA-Z_:][\w:.-]*)\s*=\s*"([^"]*)"/g;
|
|
3977
|
+
let match;
|
|
3978
|
+
while ((match = re.exec(src)) !== null) {
|
|
3979
|
+
out[match[1]] = decodeBasicEntities(match[2]);
|
|
3629
3980
|
}
|
|
3630
|
-
return
|
|
3631
|
-
[`${cssProp}-top`]: normalizeZero(`${cssProp}-top`, top),
|
|
3632
|
-
[`${cssProp}-right`]: normalizeZero(`${cssProp}-right`, right),
|
|
3633
|
-
[`${cssProp}-bottom`]: normalizeZero(`${cssProp}-bottom`, bottom),
|
|
3634
|
-
[`${cssProp}-left`]: normalizeZero(`${cssProp}-left`, left)
|
|
3635
|
-
};
|
|
3981
|
+
return out;
|
|
3636
3982
|
}
|
|
3637
|
-
function
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3983
|
+
function decodeBasicEntities(s) {
|
|
3984
|
+
return s.replace(/"/g, '"').replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
3985
|
+
}
|
|
3986
|
+
function emitNextPage(options) {
|
|
3987
|
+
const {
|
|
3988
|
+
html,
|
|
3989
|
+
meta,
|
|
3990
|
+
title,
|
|
3991
|
+
locale,
|
|
3992
|
+
theme,
|
|
3993
|
+
fontPreloads,
|
|
3994
|
+
libraryTags,
|
|
3995
|
+
scriptPaths,
|
|
3996
|
+
customCode,
|
|
3997
|
+
iconTagsHtml,
|
|
3998
|
+
formHandlerNeeded
|
|
3999
|
+
} = options;
|
|
4000
|
+
const metaJSX = metaHtmlToJSX(meta);
|
|
4001
|
+
const headHtmlBlocks = [];
|
|
4002
|
+
if (iconTagsHtml) headHtmlBlocks.push(iconTagsHtml);
|
|
4003
|
+
if (fontPreloads) headHtmlBlocks.push(fontPreloads);
|
|
4004
|
+
if (libraryTags.headCSS) headHtmlBlocks.push(libraryTags.headCSS);
|
|
4005
|
+
if (libraryTags.headJS) headHtmlBlocks.push(libraryTags.headJS);
|
|
4006
|
+
if (customCode.head) headHtmlBlocks.push(customCode.head);
|
|
4007
|
+
const headHtml = headHtmlBlocks.join("\n");
|
|
4008
|
+
const scriptTags = scriptPaths.map((s) => `<script src="${s}" defer></script>`).join("\n");
|
|
4009
|
+
const bodyEndBlocks = [];
|
|
4010
|
+
if (scriptTags) bodyEndBlocks.push(scriptTags);
|
|
4011
|
+
if (libraryTags.bodyEndJS) bodyEndBlocks.push(libraryTags.bodyEndJS);
|
|
4012
|
+
if (customCode.bodyEnd) bodyEndBlocks.push(customCode.bodyEnd);
|
|
4013
|
+
if (formHandlerNeeded) bodyEndBlocks.push(`<script>${formHandlerScript}</script>`);
|
|
4014
|
+
const bodyEndHtml = bodyEndBlocks.join("\n");
|
|
4015
|
+
const bodyStartHtml = customCode.bodyStart || "";
|
|
4016
|
+
return `// Auto-generated by meno-core/build-next. Do not edit.
|
|
4017
|
+
import RawHead from '../components/RawHead';
|
|
4018
|
+
|
|
4019
|
+
const TITLE = ${JSON.stringify(title)};
|
|
4020
|
+
const LOCALE = ${JSON.stringify(locale)};
|
|
4021
|
+
const THEME = ${JSON.stringify(theme)};
|
|
4022
|
+
const HEAD_HTML = \`${escapeTemplateLiteral5(headHtml)}\`;
|
|
4023
|
+
const BODY_START_HTML = \`${escapeTemplateLiteral5(bodyStartHtml)}\`;
|
|
4024
|
+
const PAGE_HTML = \`${escapeTemplateLiteral5(html)}\`;
|
|
4025
|
+
const BODY_END_HTML = \`${escapeTemplateLiteral5(bodyEndHtml)}\`;
|
|
4026
|
+
|
|
4027
|
+
export const metadata = {
|
|
4028
|
+
title: TITLE,
|
|
4029
|
+
};
|
|
4030
|
+
|
|
4031
|
+
export default function Page() {
|
|
4032
|
+
return (
|
|
4033
|
+
<>
|
|
4034
|
+
${metaJSX || ""}
|
|
4035
|
+
<RawHead html={HEAD_HTML} locale={LOCALE} theme={THEME} />
|
|
4036
|
+
{BODY_START_HTML ? <div data-meno-body-start dangerouslySetInnerHTML={{ __html: BODY_START_HTML }} /> : null}
|
|
4037
|
+
<div id="root" dangerouslySetInnerHTML={{ __html: PAGE_HTML }} />
|
|
4038
|
+
{BODY_END_HTML ? <div data-meno-body-end dangerouslySetInnerHTML={{ __html: BODY_END_HTML }} /> : null}
|
|
4039
|
+
</>
|
|
4040
|
+
);
|
|
4041
|
+
}
|
|
4042
|
+
`;
|
|
4043
|
+
}
|
|
4044
|
+
function emitNextCMSPage(options) {
|
|
4045
|
+
const {
|
|
4046
|
+
slugs,
|
|
4047
|
+
perSlugData,
|
|
4048
|
+
locale,
|
|
4049
|
+
theme,
|
|
4050
|
+
fontPreloads,
|
|
4051
|
+
libraryTags,
|
|
4052
|
+
customCode,
|
|
4053
|
+
iconTagsHtml,
|
|
4054
|
+
formHandlerNeeded
|
|
4055
|
+
} = options;
|
|
4056
|
+
const headHtmlBlocks = [];
|
|
4057
|
+
if (iconTagsHtml) headHtmlBlocks.push(iconTagsHtml);
|
|
4058
|
+
if (fontPreloads) headHtmlBlocks.push(fontPreloads);
|
|
4059
|
+
if (libraryTags.headCSS) headHtmlBlocks.push(libraryTags.headCSS);
|
|
4060
|
+
if (libraryTags.headJS) headHtmlBlocks.push(libraryTags.headJS);
|
|
4061
|
+
if (customCode.head) headHtmlBlocks.push(customCode.head);
|
|
4062
|
+
const headHtml = headHtmlBlocks.join("\n");
|
|
4063
|
+
const bodyStartHtml = customCode.bodyStart || "";
|
|
4064
|
+
const trailingFormHandler = formHandlerNeeded ? `<script>${formHandlerScript}</script>` : "";
|
|
4065
|
+
const entries = [];
|
|
4066
|
+
for (const slug of slugs) {
|
|
4067
|
+
const data = perSlugData[slug];
|
|
4068
|
+
if (!data) continue;
|
|
4069
|
+
const scriptTags = data.scriptPaths.map((s) => `<script src="${s}" defer></script>`).join("\n");
|
|
4070
|
+
const bodyEndBlocks = [];
|
|
4071
|
+
if (scriptTags) bodyEndBlocks.push(scriptTags);
|
|
4072
|
+
if (libraryTags.bodyEndJS) bodyEndBlocks.push(libraryTags.bodyEndJS);
|
|
4073
|
+
if (customCode.bodyEnd) bodyEndBlocks.push(customCode.bodyEnd);
|
|
4074
|
+
if (trailingFormHandler) bodyEndBlocks.push(trailingFormHandler);
|
|
4075
|
+
const bodyEndHtml = bodyEndBlocks.join("\n");
|
|
4076
|
+
entries.push(
|
|
4077
|
+
` ${JSON.stringify(slug)}: {
|
|
4078
|
+
title: ${JSON.stringify(data.title)},
|
|
4079
|
+
metaHtml: \`${escapeTemplateLiteral5(data.meta)}\`,
|
|
4080
|
+
html: \`${escapeTemplateLiteral5(data.html)}\`,
|
|
4081
|
+
bodyEndHtml: \`${escapeTemplateLiteral5(bodyEndHtml)}\`,
|
|
4082
|
+
}`
|
|
4083
|
+
);
|
|
4084
|
+
}
|
|
4085
|
+
return `// Auto-generated by meno-core/build-next. Do not edit.
|
|
4086
|
+
import RawHead from '${cmsRawHeadImport(options)}';
|
|
4087
|
+
import MetaTags from '${cmsMetaTagsImport(options)}';
|
|
4088
|
+
|
|
4089
|
+
const LOCALE = ${JSON.stringify(locale)};
|
|
4090
|
+
const THEME = ${JSON.stringify(theme)};
|
|
4091
|
+
const HEAD_HTML = \`${escapeTemplateLiteral5(headHtml)}\`;
|
|
4092
|
+
const BODY_START_HTML = \`${escapeTemplateLiteral5(bodyStartHtml)}\`;
|
|
4093
|
+
|
|
4094
|
+
type Entry = {
|
|
4095
|
+
title: string;
|
|
4096
|
+
metaHtml: string;
|
|
4097
|
+
html: string;
|
|
4098
|
+
bodyEndHtml: string;
|
|
4099
|
+
};
|
|
4100
|
+
|
|
4101
|
+
const ENTRIES: Record<string, Entry> = {
|
|
4102
|
+
${entries.join(",\n")}
|
|
4103
|
+
};
|
|
4104
|
+
|
|
4105
|
+
export function generateStaticParams() {
|
|
4106
|
+
return Object.keys(ENTRIES).map((slug) => ({ slug }));
|
|
4107
|
+
}
|
|
4108
|
+
|
|
4109
|
+
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) {
|
|
4110
|
+
const { slug } = await params;
|
|
4111
|
+
const entry = ENTRIES[slug];
|
|
4112
|
+
return entry ? { title: entry.title } : {};
|
|
4113
|
+
}
|
|
4114
|
+
|
|
4115
|
+
export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
|
|
4116
|
+
const { slug } = await params;
|
|
4117
|
+
const entry = ENTRIES[slug];
|
|
4118
|
+
if (!entry) return null;
|
|
4119
|
+
return (
|
|
4120
|
+
<>
|
|
4121
|
+
<MetaTags html={entry.metaHtml} />
|
|
4122
|
+
<RawHead html={HEAD_HTML} locale={LOCALE} theme={THEME} />
|
|
4123
|
+
{BODY_START_HTML ? <div data-meno-body-start dangerouslySetInnerHTML={{ __html: BODY_START_HTML }} /> : null}
|
|
4124
|
+
<div id="root" dangerouslySetInnerHTML={{ __html: entry.html }} />
|
|
4125
|
+
{entry.bodyEndHtml ? <div data-meno-body-end dangerouslySetInnerHTML={{ __html: entry.bodyEndHtml }} /> : null}
|
|
4126
|
+
</>
|
|
4127
|
+
);
|
|
4128
|
+
}
|
|
4129
|
+
`;
|
|
4130
|
+
}
|
|
4131
|
+
function cmsRawHeadImport(_options) {
|
|
4132
|
+
return "../../components/RawHead";
|
|
4133
|
+
}
|
|
4134
|
+
function cmsMetaTagsImport(_options) {
|
|
4135
|
+
return "../../components/MetaTags";
|
|
4136
|
+
}
|
|
4137
|
+
async function buildNextProject(projectRoot, outputDir) {
|
|
4138
|
+
configService.reset();
|
|
4139
|
+
const projectConfig = await loadProjectConfig();
|
|
4140
|
+
const siteUrl = projectConfig.siteUrl?.replace(/\/$/, "") || "";
|
|
4141
|
+
const i18nConfig = await loadI18nConfig();
|
|
4142
|
+
await migrateTemplatesDirectory();
|
|
4143
|
+
const { components, warnings, errors: compErrors } = await loadComponentDirectory(projectPaths.components());
|
|
4144
|
+
const globalComponents = {};
|
|
4145
|
+
components.forEach((value, key) => {
|
|
4146
|
+
globalComponents[key] = value;
|
|
4147
|
+
});
|
|
4148
|
+
for (const w of warnings) console.warn(` Warning: ${w}`);
|
|
4149
|
+
for (const e of compErrors) console.error(` Error: ${e}`);
|
|
4150
|
+
const cmsProvider = new FileSystemCMSProvider(projectPaths.templates(), projectPaths.cms());
|
|
4151
|
+
const cmsService = new CMSService(cmsProvider);
|
|
4152
|
+
await cmsService.initialize();
|
|
4153
|
+
const themeConfig = await colorService.loadThemeConfig();
|
|
4154
|
+
const variablesConfig = await variableService.loadConfig();
|
|
4155
|
+
const breakpoints = await loadBreakpointConfig();
|
|
4156
|
+
await configService.load();
|
|
4157
|
+
const responsiveScales = configService.getResponsiveScales();
|
|
4158
|
+
const globalLibraries = configService.getLibraries();
|
|
4159
|
+
const componentLibraries = collectComponentLibraries(globalComponents);
|
|
4160
|
+
const outDir = outputDir || join2(projectPaths.project, "next-export");
|
|
4161
|
+
if (existsSync2(outDir)) {
|
|
4162
|
+
rmSync2(outDir, { recursive: true, force: true });
|
|
4163
|
+
}
|
|
4164
|
+
mkdirSync2(outDir, { recursive: true });
|
|
4165
|
+
const appDir = join2(outDir, "app");
|
|
4166
|
+
const componentsDir = join2(appDir, "components");
|
|
4167
|
+
const publicDir = join2(outDir, "public");
|
|
4168
|
+
const scriptsDir = join2(publicDir, "_scripts");
|
|
4169
|
+
for (const d of [appDir, componentsDir, publicDir]) {
|
|
4170
|
+
mkdirSync2(d, { recursive: true });
|
|
4171
|
+
}
|
|
4172
|
+
const pagesDir = projectPaths.pages();
|
|
4173
|
+
if (!existsSync2(pagesDir)) {
|
|
4174
|
+
console.error("Pages directory not found!");
|
|
4175
|
+
return { pages: 0, cmsPages: 0, collections: 0, errors: 1 };
|
|
4176
|
+
}
|
|
4177
|
+
const pageFiles = scanJSONFiles2(pagesDir);
|
|
4178
|
+
if (pageFiles.length === 0) {
|
|
4179
|
+
console.warn("No pages found in ./pages directory");
|
|
4180
|
+
return { pages: 0, cmsPages: 0, collections: 0, errors: 0 };
|
|
4181
|
+
}
|
|
4182
|
+
const slugMappings = [];
|
|
4183
|
+
for (const file of pageFiles) {
|
|
4184
|
+
const pageName = file.replace(".json", "");
|
|
4185
|
+
const basePath = mapPageNameToPath(pageName);
|
|
4186
|
+
const pageContent = await loadJSONFile(join2(pagesDir, file));
|
|
4187
|
+
if (!pageContent) continue;
|
|
4188
|
+
try {
|
|
4189
|
+
const pageData = parseJSON(pageContent);
|
|
4190
|
+
if (pageData.meta?.slugs) {
|
|
4191
|
+
const pageId = basePath === "/" ? "index" : basePath.substring(1);
|
|
4192
|
+
slugMappings.push({ pageId, slugs: pageData.meta.slugs });
|
|
4193
|
+
}
|
|
4194
|
+
} catch {
|
|
4195
|
+
}
|
|
4196
|
+
}
|
|
4197
|
+
const allResults = [];
|
|
4198
|
+
const allInteractiveStyles = /* @__PURE__ */ new Map();
|
|
4199
|
+
const allComponentCSS = /* @__PURE__ */ new Set();
|
|
4200
|
+
const allUtilityClasses = /* @__PURE__ */ new Set();
|
|
4201
|
+
const jsContents = /* @__PURE__ */ new Map();
|
|
4202
|
+
let errorCount = 0;
|
|
4203
|
+
let projectNeedsFormHandler = false;
|
|
4204
|
+
function mergeInteractiveStyles(source) {
|
|
4205
|
+
for (const [key, value] of source) {
|
|
4206
|
+
if (!allInteractiveStyles.has(key)) {
|
|
4207
|
+
allInteractiveStyles.set(key, value);
|
|
4208
|
+
}
|
|
4209
|
+
}
|
|
4210
|
+
}
|
|
4211
|
+
function recordRender(result, urlPath, pageData, pageName) {
|
|
4212
|
+
mergeInteractiveStyles(result.interactiveStylesMap);
|
|
4213
|
+
if (result.componentCSS) allComponentCSS.add(result.componentCSS);
|
|
4214
|
+
for (const c of extractUtilityClassesFromHTML(result.html)) {
|
|
4215
|
+
allUtilityClasses.add(c);
|
|
4216
|
+
}
|
|
4217
|
+
if (result.javascript) {
|
|
4218
|
+
const hash = hashContent3(result.javascript);
|
|
4219
|
+
if (!jsContents.has(hash)) {
|
|
4220
|
+
jsContents.set(hash, result.javascript);
|
|
4221
|
+
}
|
|
4222
|
+
}
|
|
4223
|
+
if (!projectNeedsFormHandler && needsFormHandler(result.html)) {
|
|
4224
|
+
projectNeedsFormHandler = true;
|
|
4225
|
+
}
|
|
4226
|
+
allResults.push({
|
|
4227
|
+
html: result.html,
|
|
4228
|
+
meta: result.meta,
|
|
4229
|
+
title: result.title,
|
|
4230
|
+
javascript: result.javascript,
|
|
4231
|
+
componentCSS: result.componentCSS,
|
|
4232
|
+
locale: result.locale,
|
|
4233
|
+
interactiveStylesMap: result.interactiveStylesMap,
|
|
4234
|
+
urlPath,
|
|
4235
|
+
pageData,
|
|
4236
|
+
pageName
|
|
4237
|
+
});
|
|
4238
|
+
}
|
|
4239
|
+
for (const file of pageFiles) {
|
|
4240
|
+
const pageName = file.replace(".json", "");
|
|
4241
|
+
const basePath = mapPageNameToPath(pageName);
|
|
4242
|
+
const pageContent = await loadJSONFile(join2(pagesDir, file));
|
|
4243
|
+
if (!pageContent) {
|
|
4244
|
+
console.warn(` Skipping ${basePath} (empty file)`);
|
|
4245
|
+
errorCount++;
|
|
4246
|
+
continue;
|
|
4247
|
+
}
|
|
4248
|
+
try {
|
|
4249
|
+
const pageData = parseJSON(pageContent);
|
|
4250
|
+
const isDevBuild = process.env.MENO_DEV_BUILD === "true";
|
|
4251
|
+
if (pageData.meta?.draft === true && !isDevBuild) {
|
|
4252
|
+
continue;
|
|
4253
|
+
}
|
|
4254
|
+
const slugs = pageData.meta?.slugs;
|
|
4255
|
+
for (const localeConfig of i18nConfig.locales) {
|
|
4256
|
+
const locale = localeConfig.code;
|
|
4257
|
+
const isDefault = locale === i18nConfig.defaultLocale;
|
|
4258
|
+
let slug;
|
|
4259
|
+
if (slugs && slugs[locale]) {
|
|
4260
|
+
slug = slugs[locale];
|
|
4261
|
+
} else if (basePath === "/") {
|
|
4262
|
+
slug = "";
|
|
4263
|
+
} else {
|
|
4264
|
+
slug = basePath.substring(1);
|
|
4265
|
+
}
|
|
4266
|
+
const urlPath = isDefault ? slug === "" ? "/" : `/${slug}` : slug === "" ? `/${locale}` : `/${locale}/${slug}`;
|
|
4267
|
+
const result = await renderPageSSR(
|
|
4268
|
+
pageData,
|
|
4269
|
+
globalComponents,
|
|
4270
|
+
urlPath,
|
|
4271
|
+
siteUrl,
|
|
4272
|
+
locale,
|
|
4273
|
+
i18nConfig,
|
|
4274
|
+
slugMappings,
|
|
4275
|
+
void 0,
|
|
4276
|
+
cmsService,
|
|
4277
|
+
true
|
|
4278
|
+
);
|
|
4279
|
+
recordRender(result, urlPath, pageData, pageName);
|
|
4280
|
+
}
|
|
4281
|
+
} catch (error) {
|
|
4282
|
+
const err = error;
|
|
4283
|
+
console.error(` Error rendering ${basePath}:`, err?.message || error);
|
|
4284
|
+
errorCount++;
|
|
4285
|
+
}
|
|
4286
|
+
}
|
|
4287
|
+
const fontPreloads = generateFontPreloadTags();
|
|
4288
|
+
const mergedLibraries = mergeLibraries(globalLibraries, componentLibraries);
|
|
4289
|
+
const buildLibraries = filterLibrariesByContext(mergedLibraries, "build");
|
|
4290
|
+
const inlineContents = /* @__PURE__ */ new Map();
|
|
4291
|
+
const localLibsToCopy = [];
|
|
4292
|
+
for (const css of buildLibraries.css || []) {
|
|
4293
|
+
if (!css.url.startsWith("/")) continue;
|
|
4294
|
+
const shouldInline = css.inline !== false;
|
|
4295
|
+
const relPath = css.url.slice(1);
|
|
4296
|
+
const srcPath = join2(projectPaths.project, relPath);
|
|
4297
|
+
if (!existsSync2(srcPath)) continue;
|
|
4298
|
+
if (shouldInline) {
|
|
4299
|
+
try {
|
|
4300
|
+
inlineContents.set(css.url, await readFile2(srcPath, "utf-8"));
|
|
4301
|
+
} catch {
|
|
4302
|
+
localLibsToCopy.push(relPath);
|
|
4303
|
+
}
|
|
4304
|
+
} else {
|
|
4305
|
+
localLibsToCopy.push(relPath);
|
|
4306
|
+
}
|
|
4307
|
+
}
|
|
4308
|
+
for (const js of buildLibraries.js || []) {
|
|
4309
|
+
if (js.url.startsWith("/")) {
|
|
4310
|
+
const relPath = js.url.slice(1);
|
|
4311
|
+
if (existsSync2(join2(projectPaths.project, relPath))) {
|
|
4312
|
+
localLibsToCopy.push(relPath);
|
|
4313
|
+
}
|
|
4314
|
+
}
|
|
4315
|
+
}
|
|
4316
|
+
const libraryTags = generateLibraryTags(buildLibraries, inlineContents);
|
|
4317
|
+
const defaultTheme = themeConfig.default || "light";
|
|
4318
|
+
const customCode = configService.getCustomCode();
|
|
4319
|
+
const iconsConfig = await loadIconsConfig();
|
|
4320
|
+
const hasDarkFavicon = !!(iconsConfig.favicon && iconsConfig.faviconDark);
|
|
4321
|
+
const faviconTag = iconsConfig.favicon ? `<link rel="icon" href="${iconsConfig.favicon.replace(/"/g, """)}"${hasDarkFavicon ? ' media="(prefers-color-scheme: light)"' : ""} />` : "";
|
|
4322
|
+
const faviconDarkTag = iconsConfig.faviconDark ? `<link rel="icon" href="${iconsConfig.faviconDark.replace(/"/g, """)}" media="(prefers-color-scheme: dark)" />` : "";
|
|
4323
|
+
const appleTouchIconTag = iconsConfig.appleTouchIcon ? `<link rel="apple-touch-icon" href="${iconsConfig.appleTouchIcon.replace(/"/g, """)}" />` : "";
|
|
4324
|
+
const iconTagsHtml = [faviconTag, faviconDarkTag, appleTouchIconTag].filter(Boolean).join("\n ");
|
|
4325
|
+
const remConversionConfig = configService.getRemConversion();
|
|
4326
|
+
const templatesDir = projectPaths.templates();
|
|
4327
|
+
const templateSchemas = [];
|
|
4328
|
+
let cmsPageCount = 0;
|
|
4329
|
+
const cmsEmissions = [];
|
|
4330
|
+
if (existsSync2(templatesDir)) {
|
|
4331
|
+
const templateFiles = readdirSync2(templatesDir).filter((f) => f.endsWith(".json"));
|
|
4332
|
+
for (const file of templateFiles) {
|
|
4333
|
+
const templateContent = await loadJSONFile(join2(templatesDir, file));
|
|
4334
|
+
if (!templateContent) continue;
|
|
4335
|
+
try {
|
|
4336
|
+
const pageData = parseJSON(templateContent);
|
|
4337
|
+
const isDevBuild = process.env.MENO_DEV_BUILD === "true";
|
|
4338
|
+
if (pageData.meta?.draft === true && !isDevBuild) {
|
|
4339
|
+
continue;
|
|
4340
|
+
}
|
|
4341
|
+
if (!isCMSPage2(pageData)) {
|
|
4342
|
+
console.warn(` ${file} is in templates/ but missing meta.source: "cms"`);
|
|
4343
|
+
continue;
|
|
4344
|
+
}
|
|
4345
|
+
const cmsSchema = pageData.meta.cms;
|
|
4346
|
+
templateSchemas.push(cmsSchema);
|
|
4347
|
+
const slugField = cmsSchema.slugField || "slug";
|
|
4348
|
+
const items = await cmsService.queryItems({ collection: cmsSchema.id });
|
|
4349
|
+
const urlPatternWithoutSlash = cmsSchema.urlPattern.replace(/^\//, "");
|
|
4350
|
+
const slugPlaceholderIdx = urlPatternWithoutSlash.indexOf("{{");
|
|
4351
|
+
const pathPrefix = slugPlaceholderIdx > 0 ? urlPatternWithoutSlash.substring(0, slugPlaceholderIdx).replace(/\/$/, "") : "";
|
|
4352
|
+
for (const localeEntry of i18nConfig.locales) {
|
|
4353
|
+
const localeCode = localeEntry.code;
|
|
4354
|
+
const isDefault = localeCode === i18nConfig.defaultLocale;
|
|
4355
|
+
const perSlugData = {};
|
|
4356
|
+
for (const item of items) {
|
|
4357
|
+
if (!isDevBuild && isItemDraftForLocale(item, localeCode)) continue;
|
|
4358
|
+
const itemPath = buildCMSItemPath(cmsSchema.urlPattern, item, slugField, localeCode, i18nConfig);
|
|
4359
|
+
const itemWithUrl = { ...item, _url: itemPath };
|
|
4360
|
+
const fullPath = isDefault ? itemPath : `/${localeCode}${itemPath}`;
|
|
4361
|
+
const result = await renderPageSSR(
|
|
4362
|
+
pageData,
|
|
4363
|
+
globalComponents,
|
|
4364
|
+
fullPath,
|
|
4365
|
+
siteUrl,
|
|
4366
|
+
localeCode,
|
|
4367
|
+
i18nConfig,
|
|
4368
|
+
slugMappings,
|
|
4369
|
+
{ cms: itemWithUrl },
|
|
4370
|
+
cmsService,
|
|
4371
|
+
true
|
|
4372
|
+
);
|
|
4373
|
+
mergeInteractiveStyles(result.interactiveStylesMap);
|
|
4374
|
+
if (result.componentCSS) allComponentCSS.add(result.componentCSS);
|
|
4375
|
+
for (const c of extractUtilityClassesFromHTML(result.html)) {
|
|
4376
|
+
allUtilityClasses.add(c);
|
|
4377
|
+
}
|
|
4378
|
+
if (!projectNeedsFormHandler && needsFormHandler(result.html)) {
|
|
4379
|
+
projectNeedsFormHandler = true;
|
|
4380
|
+
}
|
|
4381
|
+
const scriptPaths = [];
|
|
4382
|
+
if (result.javascript) {
|
|
4383
|
+
const hash = hashContent3(result.javascript);
|
|
4384
|
+
if (!jsContents.has(hash)) jsContents.set(hash, result.javascript);
|
|
4385
|
+
scriptPaths.push(`/_scripts/${hash}.js`);
|
|
4386
|
+
}
|
|
4387
|
+
let rawSlug = item[slugField] ?? item._slug ?? item._id;
|
|
4388
|
+
if (isI18nValue(rawSlug)) {
|
|
4389
|
+
rawSlug = resolveI18nValue(rawSlug, localeCode, i18nConfig);
|
|
4390
|
+
}
|
|
4391
|
+
const slugKey = String(rawSlug);
|
|
4392
|
+
perSlugData[slugKey] = {
|
|
4393
|
+
html: result.html,
|
|
4394
|
+
meta: result.meta,
|
|
4395
|
+
title: result.title,
|
|
4396
|
+
scriptPaths
|
|
4397
|
+
};
|
|
4398
|
+
cmsPageCount++;
|
|
4399
|
+
}
|
|
4400
|
+
if (Object.keys(perSlugData).length > 0) {
|
|
4401
|
+
cmsEmissions.push({
|
|
4402
|
+
schema: cmsSchema,
|
|
4403
|
+
locale: localeCode,
|
|
4404
|
+
pathPrefix,
|
|
4405
|
+
isDefaultLocale: isDefault,
|
|
4406
|
+
perSlugData
|
|
4407
|
+
});
|
|
4408
|
+
}
|
|
4409
|
+
}
|
|
4410
|
+
} catch (error) {
|
|
4411
|
+
const err = error;
|
|
4412
|
+
console.error(` Error processing template ${file}:`, err?.message || error);
|
|
4413
|
+
errorCount++;
|
|
4414
|
+
}
|
|
4415
|
+
}
|
|
4416
|
+
}
|
|
4417
|
+
for (const [hash, js] of jsContents) {
|
|
4418
|
+
writePageScript2(js, scriptsDir);
|
|
4419
|
+
void hash;
|
|
4420
|
+
}
|
|
4421
|
+
const mappingClasses = collectAllMappingClasses(globalComponents, breakpoints, responsiveScales);
|
|
4422
|
+
const fontCSS = generateFontCSS();
|
|
4423
|
+
const themeColorCSS = generateThemeColorVariablesCSS(themeConfig);
|
|
4424
|
+
const variablesCSS = generateVariablesCSS(variablesConfig, breakpoints, responsiveScales);
|
|
4425
|
+
const componentCSSCombined = Array.from(allComponentCSS).join("\n");
|
|
4426
|
+
const utilityCSS = allUtilityClasses.size > 0 ? generateUtilityCSS(allUtilityClasses, breakpoints, responsiveScales, remConversionConfig) : "";
|
|
4427
|
+
const interactiveStylesCSS = allInteractiveStyles.size > 0 ? generateAllInteractiveCSS(allInteractiveStyles, breakpoints, remConversionConfig, responsiveScales) : "";
|
|
4428
|
+
const baseCSS = `@layer base {
|
|
4429
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
4430
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif; }
|
|
4431
|
+
button { background: none; border: none; padding: 0; font: inherit; cursor: pointer; outline: inherit; }
|
|
4432
|
+
img { max-width: 100%; height: auto; }
|
|
4433
|
+
picture { display: block; }
|
|
4434
|
+
.olink { text-decoration: none; display: block; color: inherit; }
|
|
4435
|
+
.oem { display: inline-block; }
|
|
4436
|
+
}`;
|
|
4437
|
+
const safelistDirectives = Array.from(mappingClasses).map((c) => `@source inline("${c}");`).join("\n");
|
|
4438
|
+
const tailwindDirectives = safelistDirectives ? `@import "tailwindcss";
|
|
4439
|
+
|
|
4440
|
+
${safelistDirectives}` : `@import "tailwindcss";`;
|
|
4441
|
+
const globalCSS = [
|
|
4442
|
+
tailwindDirectives,
|
|
4443
|
+
fontCSS,
|
|
4444
|
+
themeColorCSS,
|
|
4445
|
+
variablesCSS,
|
|
4446
|
+
baseCSS,
|
|
4447
|
+
utilityCSS,
|
|
4448
|
+
componentCSSCombined,
|
|
4449
|
+
interactiveStylesCSS
|
|
4450
|
+
].filter(Boolean).join("\n\n");
|
|
4451
|
+
await writeFile3(join2(appDir, "globals.css"), globalCSS, "utf-8");
|
|
4452
|
+
const projectName = projectConfig?.name || "Site";
|
|
4453
|
+
const rootLayoutContent = `// Auto-generated by meno-core/build-next. Do not edit.
|
|
4454
|
+
import './globals.css';
|
|
4455
|
+
|
|
4456
|
+
export const metadata = {
|
|
4457
|
+
title: ${JSON.stringify(projectName)},
|
|
4458
|
+
};
|
|
4459
|
+
|
|
4460
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
4461
|
+
return (
|
|
4462
|
+
<html lang=${JSON.stringify(i18nConfig.defaultLocale)} data-theme=${JSON.stringify(defaultTheme)} suppressHydrationWarning>
|
|
4463
|
+
<head>
|
|
4464
|
+
<meta charSet="UTF-8" />
|
|
4465
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
4466
|
+
</head>
|
|
4467
|
+
<body>{children}</body>
|
|
4468
|
+
</html>
|
|
4469
|
+
);
|
|
4470
|
+
}
|
|
4471
|
+
`;
|
|
4472
|
+
await writeFile3(join2(appDir, "layout.tsx"), rootLayoutContent, "utf-8");
|
|
4473
|
+
const rawHeadContent = `// Auto-generated by meno-core/build-next. Do not edit.
|
|
4474
|
+
// Renders an arbitrary HTML string. Used for head fragments emitted by the
|
|
4475
|
+
// SSR pipeline (font preloads, icon links, customCode.head, library tags)
|
|
4476
|
+
// and per-locale data-theme/lang updates. Because this is a server component,
|
|
4477
|
+
// the HTML appears in the statically exported file as-is.
|
|
4478
|
+
|
|
4479
|
+
export default function RawHead({ html, locale, theme }: { html: string; locale: string; theme: string }) {
|
|
4480
|
+
return (
|
|
4481
|
+
<>
|
|
4482
|
+
{/* Force locale/theme on <html> via a lightweight inline script. */}
|
|
4483
|
+
<script
|
|
4484
|
+
dangerouslySetInnerHTML={{
|
|
4485
|
+
__html: \`document.documentElement.lang=\${JSON.stringify(locale)};document.documentElement.dataset.theme=\${JSON.stringify(theme)};\`,
|
|
4486
|
+
}}
|
|
4487
|
+
/>
|
|
4488
|
+
{html ? <span data-meno-head dangerouslySetInnerHTML={{ __html: html }} style={{ display: 'none' }} /> : null}
|
|
4489
|
+
</>
|
|
4490
|
+
);
|
|
4491
|
+
}
|
|
4492
|
+
`;
|
|
4493
|
+
await writeFile3(join2(componentsDir, "RawHead.tsx"), rawHeadContent, "utf-8");
|
|
4494
|
+
const metaTagsContent = `// Auto-generated by meno-core/build-next. Do not edit.
|
|
4495
|
+
// Parses a string of self-closing <meta>/<link>/<title> tags emitted by
|
|
4496
|
+
// meno-core's generateMetaTags() into React elements. React 19 hoists these
|
|
4497
|
+
// to <head> automatically.
|
|
4498
|
+
|
|
4499
|
+
type MetaEl =
|
|
4500
|
+
| { kind: 'title'; text: string }
|
|
4501
|
+
| { kind: 'meta' | 'link'; attrs: Record<string, string> };
|
|
4502
|
+
|
|
4503
|
+
function decodeEntities(s: string): string {
|
|
4504
|
+
return s
|
|
4505
|
+
.replace(/"/g, '"')
|
|
4506
|
+
.replace(/'/g, "'")
|
|
4507
|
+
.replace(/</g, '<')
|
|
4508
|
+
.replace(/>/g, '>')
|
|
4509
|
+
.replace(/&/g, '&');
|
|
4510
|
+
}
|
|
4511
|
+
|
|
4512
|
+
function parseAttrs(src: string): Record<string, string> {
|
|
4513
|
+
const out: Record<string, string> = {};
|
|
4514
|
+
const re = /([a-zA-Z_:][\\w:.-]*)\\s*=\\s*"([^"]*)"/g;
|
|
4515
|
+
let match: RegExpExecArray | null;
|
|
4516
|
+
while ((match = re.exec(src)) !== null) {
|
|
4517
|
+
out[match[1]] = decodeEntities(match[2]);
|
|
4518
|
+
}
|
|
4519
|
+
return out;
|
|
4520
|
+
}
|
|
4521
|
+
|
|
4522
|
+
const JSX_ATTR_NAMES: Record<string, string> = {
|
|
4523
|
+
'http-equiv': 'httpEquiv',
|
|
4524
|
+
'hreflang': 'hrefLang',
|
|
4525
|
+
'crossorigin': 'crossOrigin',
|
|
4526
|
+
'referrerpolicy': 'referrerPolicy',
|
|
4527
|
+
'imagesrcset': 'imageSrcSet',
|
|
4528
|
+
'imagesizes': 'imageSizes',
|
|
4529
|
+
'fetchpriority': 'fetchPriority',
|
|
4530
|
+
'class': 'className',
|
|
4531
|
+
'for': 'htmlFor',
|
|
4532
|
+
'charset': 'charSet',
|
|
4533
|
+
};
|
|
4534
|
+
|
|
4535
|
+
function jsxAttrName(name: string): string {
|
|
4536
|
+
return JSX_ATTR_NAMES[name] ?? name;
|
|
4537
|
+
}
|
|
4538
|
+
|
|
4539
|
+
function parseMeta(html: string): MetaEl[] {
|
|
4540
|
+
if (!html) return [];
|
|
4541
|
+
const lines = html.split(/\\r?\\n/).map((l) => l.trim()).filter(Boolean);
|
|
4542
|
+
const out: MetaEl[] = [];
|
|
4543
|
+
for (const line of lines) {
|
|
4544
|
+
const titleMatch = line.match(/^<title>([\\s\\S]*?)<\\/title>$/);
|
|
4545
|
+
if (titleMatch) {
|
|
4546
|
+
out.push({ kind: 'title', text: decodeEntities(titleMatch[1]) });
|
|
4547
|
+
continue;
|
|
4548
|
+
}
|
|
4549
|
+
const selfMatch = line.match(/^<(meta|link)\\s+([\\s\\S]*?)\\s*\\/?>$/);
|
|
4550
|
+
if (selfMatch) {
|
|
4551
|
+
out.push({ kind: selfMatch[1] as 'meta' | 'link', attrs: parseAttrs(selfMatch[2]) });
|
|
4552
|
+
}
|
|
4553
|
+
}
|
|
4554
|
+
return out;
|
|
4555
|
+
}
|
|
4556
|
+
|
|
4557
|
+
export default function MetaTags({ html }: { html: string }) {
|
|
4558
|
+
const els = parseMeta(html);
|
|
4559
|
+
return (
|
|
4560
|
+
<>
|
|
4561
|
+
{els.map((el, i) => {
|
|
4562
|
+
if (el.kind === 'title') return <title key={i}>{el.text}</title>;
|
|
4563
|
+
const attrs: Record<string, string> = {};
|
|
4564
|
+
for (const [k, v] of Object.entries(el.attrs)) attrs[jsxAttrName(k)] = v;
|
|
4565
|
+
if (el.kind === 'meta') return <meta key={i} {...attrs} />;
|
|
4566
|
+
return <link key={i} {...attrs} />;
|
|
4567
|
+
})}
|
|
4568
|
+
</>
|
|
4569
|
+
);
|
|
4570
|
+
}
|
|
4571
|
+
`;
|
|
4572
|
+
await writeFile3(join2(componentsDir, "MetaTags.tsx"), metaTagsContent, "utf-8");
|
|
4573
|
+
for (const result of allResults) {
|
|
4574
|
+
const scriptPaths = result.javascript ? [`/_scripts/${hashContent3(result.javascript)}.js`] : [];
|
|
4575
|
+
const route = urlPathToAppRoute(result.urlPath);
|
|
4576
|
+
const targetDir = route.isRoot ? appDir : join2(appDir, route.dir);
|
|
4577
|
+
if (!existsSync2(targetDir)) {
|
|
4578
|
+
mkdirSync2(targetDir, { recursive: true });
|
|
4579
|
+
}
|
|
4580
|
+
const pageFilePath = join2(targetDir, "page.tsx");
|
|
4581
|
+
const depth = route.isRoot ? 0 : route.dir.split("/").length;
|
|
4582
|
+
const rawHeadImportPath = depth === 0 ? "./components/RawHead" : "../".repeat(depth) + "components/RawHead";
|
|
4583
|
+
let content = emitNextPage({
|
|
4584
|
+
html: result.html,
|
|
4585
|
+
meta: result.meta,
|
|
4586
|
+
title: result.title,
|
|
4587
|
+
locale: result.locale,
|
|
4588
|
+
theme: defaultTheme,
|
|
4589
|
+
fontPreloads,
|
|
4590
|
+
libraryTags,
|
|
4591
|
+
scriptPaths,
|
|
4592
|
+
customCode,
|
|
4593
|
+
iconTagsHtml,
|
|
4594
|
+
formHandlerNeeded: projectNeedsFormHandler
|
|
4595
|
+
});
|
|
4596
|
+
content = content.replace(
|
|
4597
|
+
"import RawHead from '../components/RawHead';",
|
|
4598
|
+
`import RawHead from '${rawHeadImportPath}';`
|
|
4599
|
+
);
|
|
4600
|
+
await writeFile3(pageFilePath, content, "utf-8");
|
|
4601
|
+
}
|
|
4602
|
+
for (const emission of cmsEmissions) {
|
|
4603
|
+
const { locale, pathPrefix, isDefaultLocale, perSlugData } = emission;
|
|
4604
|
+
const segments = [];
|
|
4605
|
+
if (!isDefaultLocale) segments.push(locale);
|
|
4606
|
+
if (pathPrefix) {
|
|
4607
|
+
for (const part of pathPrefix.split("/").filter(Boolean)) {
|
|
4608
|
+
segments.push(part);
|
|
4609
|
+
}
|
|
4610
|
+
}
|
|
4611
|
+
segments.push("[slug]");
|
|
4612
|
+
const routeDir = join2(appDir, ...segments);
|
|
4613
|
+
mkdirSync2(routeDir, { recursive: true });
|
|
4614
|
+
const depth = segments.length;
|
|
4615
|
+
const upToApp = "../".repeat(depth);
|
|
4616
|
+
let content = emitNextCMSPage({
|
|
4617
|
+
slugs: Object.keys(perSlugData),
|
|
4618
|
+
perSlugData,
|
|
4619
|
+
locale,
|
|
4620
|
+
theme: defaultTheme,
|
|
4621
|
+
fontPreloads,
|
|
4622
|
+
libraryTags,
|
|
4623
|
+
customCode,
|
|
4624
|
+
iconTagsHtml,
|
|
4625
|
+
formHandlerNeeded: projectNeedsFormHandler
|
|
4626
|
+
});
|
|
4627
|
+
content = content.replace(
|
|
4628
|
+
"import RawHead from '../../components/RawHead';",
|
|
4629
|
+
`import RawHead from '${upToApp}components/RawHead';`
|
|
4630
|
+
);
|
|
4631
|
+
content = content.replace(
|
|
4632
|
+
"import MetaTags from '../../components/MetaTags';",
|
|
4633
|
+
`import MetaTags from '${upToApp}components/MetaTags';`
|
|
4634
|
+
);
|
|
4635
|
+
const pageFile = join2(routeDir, "page.tsx");
|
|
4636
|
+
await writeFile3(pageFile, content, "utf-8");
|
|
4637
|
+
}
|
|
4638
|
+
const imagesSrcDir = join2(projectPaths.project, "images");
|
|
4639
|
+
if (existsSync2(imagesSrcDir)) {
|
|
4640
|
+
copyDirectory2(imagesSrcDir, join2(publicDir, "images"));
|
|
4641
|
+
}
|
|
4642
|
+
const publicAssetDirs = ["fonts", "icons", "videos", "assets"];
|
|
4643
|
+
for (const dir of publicAssetDirs) {
|
|
4644
|
+
const srcAssetDir = join2(projectPaths.project, dir);
|
|
4645
|
+
if (existsSync2(srcAssetDir)) {
|
|
4646
|
+
copyDirectory2(srcAssetDir, join2(publicDir, dir));
|
|
4647
|
+
}
|
|
4648
|
+
}
|
|
4649
|
+
const librariesDir = join2(projectPaths.project, "libraries");
|
|
4650
|
+
if (existsSync2(librariesDir)) {
|
|
4651
|
+
copyDirectory2(librariesDir, join2(publicDir, "libraries"));
|
|
4652
|
+
}
|
|
4653
|
+
for (const relPath of localLibsToCopy) {
|
|
4654
|
+
const srcPath = join2(projectPaths.project, relPath);
|
|
4655
|
+
const destPath = join2(publicDir, relPath);
|
|
4656
|
+
const destDir = destPath.substring(0, destPath.lastIndexOf("/"));
|
|
4657
|
+
if (destDir && !existsSync2(destDir)) mkdirSync2(destDir, { recursive: true });
|
|
4658
|
+
copyFileSync2(srcPath, destPath);
|
|
4659
|
+
}
|
|
4660
|
+
const packageJson = {
|
|
4661
|
+
name: "next-export",
|
|
4662
|
+
type: "module",
|
|
4663
|
+
version: "0.0.1",
|
|
4664
|
+
private: true,
|
|
4665
|
+
scripts: {
|
|
4666
|
+
dev: "next dev",
|
|
4667
|
+
build: "next build",
|
|
4668
|
+
start: "next start"
|
|
4669
|
+
},
|
|
4670
|
+
dependencies: {
|
|
4671
|
+
next: "^15.0.0",
|
|
4672
|
+
react: "^19.0.0",
|
|
4673
|
+
"react-dom": "^19.0.0",
|
|
4674
|
+
"@tailwindcss/postcss": "^4.0.0",
|
|
4675
|
+
tailwindcss: "^4.0.0"
|
|
4676
|
+
},
|
|
4677
|
+
devDependencies: {
|
|
4678
|
+
"@types/node": "^22.0.0",
|
|
4679
|
+
"@types/react": "^19.0.0",
|
|
4680
|
+
"@types/react-dom": "^19.0.0",
|
|
4681
|
+
typescript: "^5.6.0"
|
|
4682
|
+
}
|
|
4683
|
+
};
|
|
4684
|
+
await writeFile3(join2(outDir, "package.json"), JSON.stringify(packageJson, null, 2), "utf-8");
|
|
4685
|
+
const nextConfig = `/** @type {import('next').NextConfig} */
|
|
4686
|
+
const nextConfig = {
|
|
4687
|
+
output: 'export',${siteUrl ? `
|
|
4688
|
+
// Set NEXT_PUBLIC_SITE_URL=${siteUrl} for absolute URLs in metadata.` : ""}
|
|
4689
|
+
images: { unoptimized: true },
|
|
4690
|
+
trailingSlash: false,
|
|
4691
|
+
// The SSR HTML contains arbitrary inline scripts and styles; disable
|
|
4692
|
+
// automatic font/image transforms so they survive unchanged.
|
|
4693
|
+
experimental: {
|
|
4694
|
+
optimizePackageImports: [],
|
|
4695
|
+
},
|
|
4696
|
+
};
|
|
4697
|
+
|
|
4698
|
+
export default nextConfig;
|
|
4699
|
+
`;
|
|
4700
|
+
await writeFile3(join2(outDir, "next.config.mjs"), nextConfig, "utf-8");
|
|
4701
|
+
const postcssConfig = `export default {
|
|
4702
|
+
plugins: {
|
|
4703
|
+
'@tailwindcss/postcss': {},
|
|
4704
|
+
},
|
|
4705
|
+
};
|
|
4706
|
+
`;
|
|
4707
|
+
await writeFile3(join2(outDir, "postcss.config.mjs"), postcssConfig, "utf-8");
|
|
4708
|
+
const tsConfig = {
|
|
4709
|
+
compilerOptions: {
|
|
4710
|
+
target: "ES2022",
|
|
4711
|
+
lib: ["dom", "dom.iterable", "esnext"],
|
|
4712
|
+
allowJs: true,
|
|
4713
|
+
skipLibCheck: true,
|
|
4714
|
+
strict: true,
|
|
4715
|
+
noEmit: true,
|
|
4716
|
+
esModuleInterop: true,
|
|
4717
|
+
module: "esnext",
|
|
4718
|
+
moduleResolution: "bundler",
|
|
4719
|
+
resolveJsonModule: true,
|
|
4720
|
+
isolatedModules: true,
|
|
4721
|
+
jsx: "preserve",
|
|
4722
|
+
incremental: true,
|
|
4723
|
+
plugins: [{ name: "next" }],
|
|
4724
|
+
paths: { "@/*": ["./*"] }
|
|
4725
|
+
},
|
|
4726
|
+
include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
4727
|
+
exclude: ["node_modules"]
|
|
4728
|
+
};
|
|
4729
|
+
await writeFile3(join2(outDir, "tsconfig.json"), JSON.stringify(tsConfig, null, 2), "utf-8");
|
|
4730
|
+
await writeFile3(
|
|
4731
|
+
join2(outDir, "next-env.d.ts"),
|
|
4732
|
+
`/// <reference types="next" />
|
|
4733
|
+
/// <reference types="next/image-types/global" />
|
|
4734
|
+
`,
|
|
4735
|
+
"utf-8"
|
|
4736
|
+
);
|
|
4737
|
+
const gitignore = [
|
|
4738
|
+
"node_modules",
|
|
4739
|
+
".next",
|
|
4740
|
+
"out",
|
|
4741
|
+
".DS_Store",
|
|
4742
|
+
"*.log"
|
|
4743
|
+
].join("\n") + "\n";
|
|
4744
|
+
await writeFile3(join2(outDir, ".gitignore"), gitignore, "utf-8");
|
|
4745
|
+
const readme = `# Next.js export
|
|
4746
|
+
|
|
4747
|
+
This project was generated by Meno from the SSR-rendered HTML of your pages.
|
|
4748
|
+
|
|
4749
|
+
## Run locally
|
|
4750
|
+
|
|
4751
|
+
\`\`\`bash
|
|
4752
|
+
npm install
|
|
4753
|
+
npm run dev
|
|
4754
|
+
\`\`\`
|
|
4755
|
+
|
|
4756
|
+
Open http://localhost:3000.
|
|
4757
|
+
|
|
4758
|
+
## Build a static site
|
|
4759
|
+
|
|
4760
|
+
\`\`\`bash
|
|
4761
|
+
npm run build
|
|
4762
|
+
\`\`\`
|
|
4763
|
+
|
|
4764
|
+
Output lands in \`./out\` (configured via \`output: 'export'\` in \`next.config.mjs\`).
|
|
4765
|
+
|
|
4766
|
+
## How this differs from a hand-written Next.js app
|
|
4767
|
+
|
|
4768
|
+
- Each page is a server component that embeds the SSR HTML via \`dangerouslySetInnerHTML\`.
|
|
4769
|
+
- Interactive behavior lives in the inline scripts inside that HTML \u2014 they run when the browser parses the static file.
|
|
4770
|
+
- Tailwind v4 is used for utility classes; safelisted classes referenced only by runtime mappings live in \`app/globals.css\`.
|
|
4771
|
+
- Routing is plain App Router: one \`page.tsx\` per URL, with \`[slug]\` dynamic segments for CMS collections.
|
|
4772
|
+
`;
|
|
4773
|
+
await writeFile3(join2(outDir, "README.md"), readme, "utf-8");
|
|
4774
|
+
const collectionCount = templateSchemas.length;
|
|
4775
|
+
void escapeSingleQuoted;
|
|
4776
|
+
return {
|
|
4777
|
+
pages: allResults.length,
|
|
4778
|
+
cmsPages: cmsPageCount,
|
|
4779
|
+
collections: collectionCount,
|
|
4780
|
+
errors: errorCount
|
|
4781
|
+
};
|
|
4782
|
+
}
|
|
4783
|
+
|
|
4784
|
+
// lib/server/webflow/buildWebflow.ts
|
|
4785
|
+
import { existsSync as existsSync3, readdirSync as readdirSync3 } from "fs";
|
|
4786
|
+
import { join as join4 } from "path";
|
|
4787
|
+
init_constants();
|
|
4788
|
+
|
|
4789
|
+
// lib/server/webflow/nodeToWebflow.ts
|
|
4790
|
+
init_constants();
|
|
4791
|
+
|
|
4792
|
+
// lib/server/webflow/types.ts
|
|
4793
|
+
var MENO_BIND_SENTINEL_PREFIX = "__MENO_BIND__:";
|
|
4794
|
+
var MENO_BIND_SENTINEL_SUFFIX = ":__";
|
|
4795
|
+
var MENO_BIND_SENTINEL_RE = /__MENO_BIND__:([^:]+):__/g;
|
|
4796
|
+
var MENO_BIND_SENTINEL_EXACT_RE = /^__MENO_BIND__:([^:]+):__$/;
|
|
4797
|
+
var COLLECTION_LIST_TAG = "__collection_list__";
|
|
4798
|
+
|
|
4799
|
+
// lib/server/webflow/styleMapper.ts
|
|
4800
|
+
var UNITLESS_PROPERTIES = /* @__PURE__ */ new Set([
|
|
4801
|
+
"opacity",
|
|
4802
|
+
"z-index",
|
|
4803
|
+
"flex-grow",
|
|
4804
|
+
"flex-shrink",
|
|
4805
|
+
"flex",
|
|
4806
|
+
"order",
|
|
4807
|
+
"orphans",
|
|
4808
|
+
"widows",
|
|
4809
|
+
"column-count",
|
|
4810
|
+
"font-weight",
|
|
4811
|
+
"tab-size"
|
|
4812
|
+
]);
|
|
4813
|
+
var TIME_PROPERTIES = /* @__PURE__ */ new Set([
|
|
4814
|
+
"transition-duration",
|
|
4815
|
+
"transition-delay",
|
|
4816
|
+
"animation-duration",
|
|
4817
|
+
"animation-delay"
|
|
4818
|
+
]);
|
|
4819
|
+
function normalizeZero(cssProp, cssValue) {
|
|
4820
|
+
if (cssValue !== "0") return cssValue;
|
|
4821
|
+
if (UNITLESS_PROPERTIES.has(cssProp)) return cssValue;
|
|
4822
|
+
if (TIME_PROPERTIES.has(cssProp)) return cssValue;
|
|
4823
|
+
return "0px";
|
|
4824
|
+
}
|
|
4825
|
+
var COLOR_PROPS_CAMEL = /* @__PURE__ */ new Set(["color", "backgroundColor", "borderColor"]);
|
|
4826
|
+
function maybeWrapColorVar(camelProp, value) {
|
|
4827
|
+
if (!COLOR_PROPS_CAMEL.has(camelProp)) return value;
|
|
4828
|
+
if (!value) return value;
|
|
4829
|
+
if (value.startsWith("#")) return value;
|
|
4830
|
+
if (value.startsWith("var(")) return value;
|
|
4831
|
+
if (value.includes("(")) return value;
|
|
4832
|
+
if (isCssNamedColor(value)) return value;
|
|
4833
|
+
return `var(--${value})`;
|
|
4834
|
+
}
|
|
4835
|
+
function isStyleMapping4(value) {
|
|
4836
|
+
return typeof value === "object" && value !== null && "_mapping" in value && value._mapping === true;
|
|
4837
|
+
}
|
|
4838
|
+
function isResponsiveStyle4(style) {
|
|
4839
|
+
return "base" in style || "tablet" in style || "mobile" in style;
|
|
4840
|
+
}
|
|
4841
|
+
function toKebabCase(prop) {
|
|
4842
|
+
return prop.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
4843
|
+
}
|
|
4844
|
+
function splitTopLevel(value) {
|
|
4845
|
+
const out = [];
|
|
4846
|
+
let depth = 0;
|
|
4847
|
+
let buf = "";
|
|
4848
|
+
for (const ch of value.trim()) {
|
|
4849
|
+
if (ch === "(") depth++;
|
|
4850
|
+
else if (ch === ")") depth--;
|
|
4851
|
+
if (depth === 0 && /\s/.test(ch)) {
|
|
4852
|
+
if (buf) {
|
|
4853
|
+
out.push(buf);
|
|
4854
|
+
buf = "";
|
|
4855
|
+
}
|
|
4856
|
+
continue;
|
|
4857
|
+
}
|
|
4858
|
+
buf += ch;
|
|
4859
|
+
}
|
|
4860
|
+
if (buf) out.push(buf);
|
|
4861
|
+
return out;
|
|
4862
|
+
}
|
|
4863
|
+
function expandShorthand(cssProp, cssValue) {
|
|
4864
|
+
if (cssProp !== "margin" && cssProp !== "padding" && cssProp !== "gap") {
|
|
4865
|
+
return null;
|
|
4866
|
+
}
|
|
4867
|
+
const parts = splitTopLevel(cssValue);
|
|
4868
|
+
if (cssProp === "gap") {
|
|
4869
|
+
if (parts.length === 1) {
|
|
4870
|
+
const v = normalizeZero("row-gap", parts[0]);
|
|
4871
|
+
return { "row-gap": v, "column-gap": v };
|
|
4872
|
+
}
|
|
4873
|
+
if (parts.length === 2) {
|
|
4874
|
+
return {
|
|
4875
|
+
"row-gap": normalizeZero("row-gap", parts[0]),
|
|
4876
|
+
"column-gap": normalizeZero("column-gap", parts[1])
|
|
4877
|
+
};
|
|
4878
|
+
}
|
|
4879
|
+
return null;
|
|
4880
|
+
}
|
|
4881
|
+
let top, right, bottom, left;
|
|
4882
|
+
if (parts.length === 1) {
|
|
4883
|
+
top = right = bottom = left = parts[0];
|
|
4884
|
+
} else if (parts.length === 2) {
|
|
4885
|
+
top = bottom = parts[0];
|
|
4886
|
+
right = left = parts[1];
|
|
4887
|
+
} else if (parts.length === 3) {
|
|
4888
|
+
top = parts[0];
|
|
4889
|
+
right = left = parts[1];
|
|
4890
|
+
bottom = parts[2];
|
|
4891
|
+
} else if (parts.length === 4) {
|
|
4892
|
+
[top, right, bottom, left] = parts;
|
|
4893
|
+
} else {
|
|
4894
|
+
return null;
|
|
4895
|
+
}
|
|
4896
|
+
return {
|
|
4897
|
+
[`${cssProp}-top`]: normalizeZero(`${cssProp}-top`, top),
|
|
4898
|
+
[`${cssProp}-right`]: normalizeZero(`${cssProp}-right`, right),
|
|
4899
|
+
[`${cssProp}-bottom`]: normalizeZero(`${cssProp}-bottom`, bottom),
|
|
4900
|
+
[`${cssProp}-left`]: normalizeZero(`${cssProp}-left`, left)
|
|
4901
|
+
};
|
|
4902
|
+
}
|
|
4903
|
+
function styleObjectToCSS(style) {
|
|
4904
|
+
const css = {};
|
|
4905
|
+
for (const [prop, value] of Object.entries(style)) {
|
|
4906
|
+
if (isStyleMapping4(value)) continue;
|
|
4907
|
+
if (value === "" || value === void 0 || value === null) continue;
|
|
4908
|
+
if (typeof value === "boolean" || typeof value === "object") continue;
|
|
3643
4909
|
const cssProp = toKebabCase(prop);
|
|
3644
4910
|
let cssValue;
|
|
3645
4911
|
if (typeof value === "number") {
|
|
@@ -3887,8 +5153,8 @@ function buildInstanceStyleCombo(comboName, rootClassName, style, interactiveSty
|
|
|
3887
5153
|
}
|
|
3888
5154
|
|
|
3889
5155
|
// lib/server/webflow/nodeToWebflow.ts
|
|
3890
|
-
import { readFile as
|
|
3891
|
-
import { join as
|
|
5156
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
5157
|
+
import { join as join3, basename, extname } from "path";
|
|
3892
5158
|
var PROMOTED_TO_WEBFLOW_COMPONENT = /* @__PURE__ */ new Set(["Navigation", "Footer"]);
|
|
3893
5159
|
function buildElementClass2(ctx, label) {
|
|
3894
5160
|
const generated = generateElementClassName({
|
|
@@ -4030,10 +5296,10 @@ async function maybeInlineLocalImage(element, src) {
|
|
|
4030
5296
|
if (!src || isAbsoluteUrl(src)) return;
|
|
4031
5297
|
const projectRoot = getProjectRoot();
|
|
4032
5298
|
const rel = src.replace(/^\/+/, "");
|
|
4033
|
-
const abs =
|
|
5299
|
+
const abs = join3(projectRoot, rel);
|
|
4034
5300
|
if (!abs.startsWith(projectRoot)) return;
|
|
4035
5301
|
try {
|
|
4036
|
-
const buf = await
|
|
5302
|
+
const buf = await readFile3(abs);
|
|
4037
5303
|
const ext = extname(abs).toLowerCase();
|
|
4038
5304
|
const mime = IMAGE_EXT_MIME[ext] || "application/octet-stream";
|
|
4039
5305
|
element.imageDataBase64 = buf.toString("base64");
|
|
@@ -4112,7 +5378,7 @@ function expandResponsiveVarsInto(baseStyle, responsive, ctx) {
|
|
|
4112
5378
|
}
|
|
4113
5379
|
function substituteVarsInStyle(style, ctx) {
|
|
4114
5380
|
if (!style) return style;
|
|
4115
|
-
if (
|
|
5381
|
+
if (isResponsiveStyleObject2(style)) {
|
|
4116
5382
|
const out = {};
|
|
4117
5383
|
for (const [bp, obj] of Object.entries(style)) {
|
|
4118
5384
|
if (!obj || typeof obj !== "object") continue;
|
|
@@ -4188,12 +5454,12 @@ function menoBreakpointToWebflowTier(bpName, breakpoints) {
|
|
|
4188
5454
|
if (w < 1920) return "xl";
|
|
4189
5455
|
return "xxl";
|
|
4190
5456
|
}
|
|
4191
|
-
function
|
|
5457
|
+
function isResponsiveStyleObject2(style) {
|
|
4192
5458
|
return "base" in style || "tablet" in style || "mobile" in style;
|
|
4193
5459
|
}
|
|
4194
5460
|
function extractBaseColor(style, ctx, instanceProps) {
|
|
4195
5461
|
if (!style) return void 0;
|
|
4196
|
-
const flat =
|
|
5462
|
+
const flat = isResponsiveStyleObject2(style) ? style.base : style;
|
|
4197
5463
|
const c = flat?.color;
|
|
4198
5464
|
if (typeof c === "string") return c;
|
|
4199
5465
|
if (c && typeof c === "object" && c._mapping === true) {
|
|
@@ -4231,7 +5497,7 @@ function resolveTemplatesInStyleObject(style, props) {
|
|
|
4231
5497
|
}
|
|
4232
5498
|
function resolveStyleTemplates(style, props) {
|
|
4233
5499
|
if (!style || !props) return style;
|
|
4234
|
-
if (
|
|
5500
|
+
if (isResponsiveStyleObject2(style)) {
|
|
4235
5501
|
const result = {};
|
|
4236
5502
|
for (const [bp, styleObj] of Object.entries(style)) {
|
|
4237
5503
|
if (styleObj && typeof styleObj === "object") {
|
|
@@ -4282,7 +5548,7 @@ function templatesToSyntheticMappings(style, componentDefaults, instanceProps) {
|
|
|
4282
5548
|
}
|
|
4283
5549
|
function convertStyleTemplatesToMappings(style, componentDefaults, instanceProps) {
|
|
4284
5550
|
if (!style || !componentDefaults || !instanceProps) return style;
|
|
4285
|
-
if (
|
|
5551
|
+
if (isResponsiveStyleObject2(style)) {
|
|
4286
5552
|
const result = {};
|
|
4287
5553
|
for (const [bp, styleObj] of Object.entries(style)) {
|
|
4288
5554
|
if (styleObj && typeof styleObj === "object") {
|
|
@@ -5239,20 +6505,20 @@ async function convertChildren(children, ctx, instanceProps) {
|
|
|
5239
6505
|
}
|
|
5240
6506
|
|
|
5241
6507
|
// lib/server/webflow/buildWebflow.ts
|
|
5242
|
-
function
|
|
6508
|
+
function scanJSONFiles3(dir, prefix = "") {
|
|
5243
6509
|
const results = [];
|
|
5244
|
-
if (!
|
|
5245
|
-
const entries =
|
|
6510
|
+
if (!existsSync3(dir)) return results;
|
|
6511
|
+
const entries = readdirSync3(dir, { withFileTypes: true });
|
|
5246
6512
|
for (const entry of entries) {
|
|
5247
6513
|
if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
5248
6514
|
results.push(prefix ? `${prefix}/${entry.name}` : entry.name);
|
|
5249
6515
|
} else if (entry.isDirectory()) {
|
|
5250
|
-
results.push(...
|
|
6516
|
+
results.push(...scanJSONFiles3(join4(dir, entry.name), prefix ? `${prefix}/${entry.name}` : entry.name));
|
|
5251
6517
|
}
|
|
5252
6518
|
}
|
|
5253
6519
|
return results;
|
|
5254
6520
|
}
|
|
5255
|
-
function
|
|
6521
|
+
function isCMSPage3(pageData) {
|
|
5256
6522
|
return pageData.meta?.source === "cms" && !!pageData.meta?.cms;
|
|
5257
6523
|
}
|
|
5258
6524
|
function flattenCMSItemForLocale(item, locale, i18nConfig) {
|
|
@@ -5297,9 +6563,9 @@ function scanAssets(projectRoot) {
|
|
|
5297
6563
|
{ dir: "assets", type: "file" }
|
|
5298
6564
|
];
|
|
5299
6565
|
for (const { dir, type } of assetDirs) {
|
|
5300
|
-
const fullDir =
|
|
5301
|
-
if (!
|
|
5302
|
-
const files =
|
|
6566
|
+
const fullDir = join4(projectRoot, dir);
|
|
6567
|
+
if (!existsSync3(fullDir)) continue;
|
|
6568
|
+
const files = scanJSONFiles3(fullDir).map((f) => f.replace(".json", ""));
|
|
5303
6569
|
const allFiles = scanAllFiles(fullDir);
|
|
5304
6570
|
for (const file of allFiles) {
|
|
5305
6571
|
assets.push({
|
|
@@ -5313,14 +6579,14 @@ function scanAssets(projectRoot) {
|
|
|
5313
6579
|
}
|
|
5314
6580
|
function scanAllFiles(dir, prefix = "") {
|
|
5315
6581
|
const results = [];
|
|
5316
|
-
if (!
|
|
5317
|
-
const entries =
|
|
6582
|
+
if (!existsSync3(dir)) return results;
|
|
6583
|
+
const entries = readdirSync3(dir, { withFileTypes: true });
|
|
5318
6584
|
for (const entry of entries) {
|
|
5319
6585
|
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
5320
6586
|
if (entry.isFile()) {
|
|
5321
6587
|
results.push(relativePath);
|
|
5322
6588
|
} else if (entry.isDirectory()) {
|
|
5323
|
-
results.push(...scanAllFiles(
|
|
6589
|
+
results.push(...scanAllFiles(join4(dir, entry.name), relativePath));
|
|
5324
6590
|
}
|
|
5325
6591
|
}
|
|
5326
6592
|
return results;
|
|
@@ -5379,10 +6645,10 @@ async function buildWebflowPayload(options) {
|
|
|
5379
6645
|
await configService.load();
|
|
5380
6646
|
const responsiveScales = configService.getResponsiveScales();
|
|
5381
6647
|
const pagesDir = projectPaths.pages();
|
|
5382
|
-
if (!
|
|
6648
|
+
if (!existsSync3(pagesDir)) {
|
|
5383
6649
|
return emptyPayload();
|
|
5384
6650
|
}
|
|
5385
|
-
const pageFiles =
|
|
6651
|
+
const pageFiles = scanJSONFiles3(pagesDir);
|
|
5386
6652
|
if (pageFiles.length === 0) {
|
|
5387
6653
|
return emptyPayload();
|
|
5388
6654
|
}
|
|
@@ -5390,7 +6656,7 @@ async function buildWebflowPayload(options) {
|
|
|
5390
6656
|
for (const file of pageFiles) {
|
|
5391
6657
|
const pageName = file.replace(".json", "");
|
|
5392
6658
|
const basePath = mapPageNameToPath(pageName);
|
|
5393
|
-
const pageContent = await loadJSONFile(
|
|
6659
|
+
const pageContent = await loadJSONFile(join4(pagesDir, file));
|
|
5394
6660
|
if (!pageContent) continue;
|
|
5395
6661
|
try {
|
|
5396
6662
|
const pageData = parseJSON(pageContent);
|
|
@@ -5412,7 +6678,7 @@ async function buildWebflowPayload(options) {
|
|
|
5412
6678
|
for (const file of pageFiles) {
|
|
5413
6679
|
const pageName = file.replace(".json", "");
|
|
5414
6680
|
const basePath = mapPageNameToPath(pageName);
|
|
5415
|
-
const pageContent = await loadJSONFile(
|
|
6681
|
+
const pageContent = await loadJSONFile(join4(pagesDir, file));
|
|
5416
6682
|
if (!pageContent) continue;
|
|
5417
6683
|
try {
|
|
5418
6684
|
const pageData = parseJSON(pageContent);
|
|
@@ -5481,15 +6747,15 @@ async function buildWebflowPayload(options) {
|
|
|
5481
6747
|
}
|
|
5482
6748
|
}
|
|
5483
6749
|
const templatesDir = projectPaths.templates();
|
|
5484
|
-
if (
|
|
5485
|
-
const templateFiles =
|
|
6750
|
+
if (existsSync3(templatesDir)) {
|
|
6751
|
+
const templateFiles = readdirSync3(templatesDir).filter((f) => f.endsWith(".json"));
|
|
5486
6752
|
for (const file of templateFiles) {
|
|
5487
|
-
const templateContent = await loadJSONFile(
|
|
6753
|
+
const templateContent = await loadJSONFile(join4(templatesDir, file));
|
|
5488
6754
|
if (!templateContent) continue;
|
|
5489
6755
|
try {
|
|
5490
6756
|
const pageData = parseJSON(templateContent);
|
|
5491
6757
|
if (pageData.meta?.draft === true) continue;
|
|
5492
|
-
if (!
|
|
6758
|
+
if (!isCMSPage3(pageData)) continue;
|
|
5493
6759
|
const cmsSchema = pageData.meta.cms;
|
|
5494
6760
|
const items = await cmsService.queryItems({ collection: cmsSchema.id });
|
|
5495
6761
|
if (items.length === 0) continue;
|
|
@@ -5637,7 +6903,7 @@ function emptyPayload() {
|
|
|
5637
6903
|
}
|
|
5638
6904
|
|
|
5639
6905
|
// lib/server/webflow/templateWrapper.ts
|
|
5640
|
-
import { readFile as
|
|
6906
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
5641
6907
|
var cachedTemplate = null;
|
|
5642
6908
|
async function getWebflowTemplate(appName) {
|
|
5643
6909
|
if (cachedTemplate) return cachedTemplate;
|
|
@@ -5649,7 +6915,7 @@ async function getWebflowTemplate(appName) {
|
|
|
5649
6915
|
}
|
|
5650
6916
|
async function wrapInWebflowTemplate(html, manifestPath) {
|
|
5651
6917
|
try {
|
|
5652
|
-
const manifest = JSON.parse(await
|
|
6918
|
+
const manifest = JSON.parse(await readFile4(manifestPath, "utf-8"));
|
|
5653
6919
|
const template = await getWebflowTemplate(manifest.name || "Meno Import");
|
|
5654
6920
|
const headMatch = html.match(/<head[^>]*>([\s\S]*?)<\/head>/i);
|
|
5655
6921
|
const bodyMatch = html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
|
|
@@ -5670,6 +6936,7 @@ export {
|
|
|
5670
6936
|
ComponentService,
|
|
5671
6937
|
ConfigService,
|
|
5672
6938
|
DEFAULT_SIZES,
|
|
6939
|
+
DraftPageStore,
|
|
5673
6940
|
EnumService,
|
|
5674
6941
|
FileSystemCMSProvider,
|
|
5675
6942
|
FileSystemPageProvider,
|
|
@@ -5688,6 +6955,7 @@ export {
|
|
|
5688
6955
|
buildComponentHTML,
|
|
5689
6956
|
buildImageMetadataMap,
|
|
5690
6957
|
buildLineMap,
|
|
6958
|
+
buildNextProject,
|
|
5691
6959
|
buildStaticPages,
|
|
5692
6960
|
buildWebflowPayload,
|
|
5693
6961
|
bundleFile,
|