meno-core 1.0.53 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +1 -3
- package/bin/cli.ts +66 -261
- package/build-astro.ts +150 -128
- package/dist/bin/cli.js +42 -185
- package/dist/bin/cli.js.map +2 -2
- package/dist/chunks/chunk-2AR55GYH.js +42 -0
- package/dist/chunks/chunk-2AR55GYH.js.map +7 -0
- package/dist/chunks/{chunk-JGWFTO6P.js → chunk-2IIQK7T3.js} +2624 -1389
- package/dist/chunks/chunk-2IIQK7T3.js.map +7 -0
- package/dist/chunks/chunk-4ZRU52J2.js +169 -0
- package/dist/chunks/chunk-4ZRU52J2.js.map +7 -0
- package/dist/chunks/{chunk-UB44F4Z2.js → chunk-5X4HCN7J.js} +5 -7
- package/dist/chunks/chunk-5X4HCN7J.js.map +7 -0
- package/dist/chunks/{chunk-WQFG7PAH.js → chunk-7CCVOL5M.js} +15 -19
- package/dist/chunks/chunk-7CCVOL5M.js.map +7 -0
- package/dist/chunks/{chunk-YBLHKYFF.js → chunk-AR7LHIFY.js} +8 -2
- package/dist/chunks/chunk-AR7LHIFY.js.map +7 -0
- package/dist/chunks/{chunk-O3NAGJP4.js → chunk-NUP7H7D3.js} +135 -41
- package/dist/chunks/chunk-NUP7H7D3.js.map +7 -0
- package/dist/chunks/{chunk-X754AHS5.js → chunk-QWTQZHG3.js} +194 -90
- package/dist/chunks/chunk-QWTQZHG3.js.map +7 -0
- package/dist/chunks/{chunk-H3GJ4H2U.js → chunk-XTKNX4FW.js} +216 -87
- package/dist/chunks/chunk-XTKNX4FW.js.map +7 -0
- package/dist/chunks/{fs-JGINUXGL.js → fs-G3ANWEFP.js} +2 -2
- package/dist/lib/client/index.js +1003 -475
- package/dist/lib/client/index.js.map +3 -3
- package/dist/lib/server/index.js +14393 -6371
- package/dist/lib/server/index.js.map +4 -4
- package/dist/lib/shared/index.js +342 -101
- package/dist/lib/shared/index.js.map +4 -4
- package/dist/lib/shared/richtext/index.js +1 -1
- package/dist/lib/test-utils/index.js +64 -80
- package/dist/lib/test-utils/index.js.map +2 -2
- package/entries/client-router.tsx +10 -8
- package/lib/client/ClientInitializer.ts +8 -8
- package/lib/client/ErrorBoundary.test.tsx +246 -186
- package/lib/client/ErrorBoundary.tsx +183 -132
- package/lib/client/componentRegistry.test.ts +96 -108
- package/lib/client/componentRegistry.ts +1 -3
- package/lib/client/contexts/ThemeContext.tsx +3 -2
- package/lib/client/core/ComponentBuilder.test.ts +663 -560
- package/lib/client/core/ComponentBuilder.ts +427 -155
- package/lib/client/core/ComponentRenderer.test.tsx +2 -4
- package/lib/client/core/ComponentRenderer.tsx +46 -33
- package/lib/client/core/builders/embedBuilder.ts +242 -54
- package/lib/client/core/builders/linkBuilder.ts +72 -45
- package/lib/client/core/builders/linkNodeBuilder.ts +79 -54
- package/lib/client/core/builders/listBuilder.ts +155 -89
- package/lib/client/core/builders/localeListBuilder.ts +97 -62
- package/lib/client/core/builders/types.ts +5 -5
- package/lib/client/core/cmsTemplateProcessor.ts +8 -9
- package/lib/client/elementRegistry.ts +3 -3
- package/lib/client/fontFamiliesService.test.ts +2 -10
- package/lib/client/fontFamiliesService.ts +3 -3
- package/lib/client/hmr/HMRManager.tsx +47 -30
- package/lib/client/hmrCssReload.ts +49 -14
- package/lib/client/hmrWebSocket.ts +14 -19
- package/lib/client/hooks/useColorVariables.test.ts +21 -21
- package/lib/client/hooks/useColorVariables.ts +23 -18
- package/lib/client/hooks/usePropertyAutocomplete.ts +4 -6
- package/lib/client/hooks/useVariables.ts +6 -4
- package/lib/client/hydration/HydrationUtils.test.ts +24 -25
- package/lib/client/hydration/HydrationUtils.ts +3 -4
- package/lib/client/i18nConfigService.test.ts +2 -7
- package/lib/client/i18nConfigService.ts +2 -2
- package/lib/client/meno-filter/MenoFilter.test.ts +47 -49
- package/lib/client/meno-filter/MenoFilter.ts +14 -15
- package/lib/client/meno-filter/bindings.ts +18 -43
- package/lib/client/meno-filter/init.ts +4 -4
- package/lib/client/meno-filter/renderer.ts +30 -32
- package/lib/client/meno-filter/script.generated.ts +1 -3
- package/lib/client/meno-filter/ui.ts +4 -6
- package/lib/client/meno-filter/updates.ts +18 -23
- package/lib/client/meno-filter/utils.ts +1 -1
- package/lib/client/navigation.test.ts +174 -178
- package/lib/client/navigation.ts +0 -1
- package/lib/client/responsiveStyleResolver.knownTokens.test.ts +71 -0
- package/lib/client/responsiveStyleResolver.test.ts +234 -232
- package/lib/client/responsiveStyleResolver.ts +74 -20
- package/lib/client/routing/RouteLoader.test.ts +24 -25
- package/lib/client/routing/RouteLoader.ts +27 -35
- package/lib/client/routing/Router.tsx +210 -153
- package/lib/client/scripts/ScriptExecutor.test.ts +157 -158
- package/lib/client/scripts/ScriptExecutor.ts +18 -36
- package/lib/client/services/PrefetchService.test.ts +3 -3
- package/lib/client/services/PrefetchService.ts +23 -29
- package/lib/client/styleProcessor.test.ts +9 -9
- package/lib/client/styleProcessor.ts +24 -15
- package/lib/client/styles/StyleInjector.test.ts +122 -115
- package/lib/client/styles/StyleInjector.ts +10 -7
- package/lib/client/styles/UtilityClassCollector.ts +62 -28
- package/lib/client/styles/cspNonce.test.ts +2 -5
- package/lib/client/templateEngine.test.ts +554 -488
- package/lib/client/templateEngine.ts +203 -73
- package/lib/client/theme.test.ts +1 -1
- package/lib/client/theme.ts +0 -1
- package/lib/client/utils/toast.ts +0 -1
- package/lib/server/__integration__/api-routes.test.ts +8 -4
- package/lib/server/__integration__/cms-integration.test.ts +1 -4
- package/lib/server/__integration__/server-lifecycle.test.ts +4 -5
- package/lib/server/__integration__/ssr-rendering.test.ts +47 -37
- package/lib/server/__integration__/static-assets.test.ts +3 -3
- package/lib/server/__integration__/test-helpers.ts +87 -74
- package/lib/server/ab/generateFunctions.ts +12 -10
- package/lib/server/astro/cmsPageEmitter.ts +26 -42
- package/lib/server/astro/componentEmitter.ts +35 -43
- package/lib/server/astro/cssCollector.ts +10 -26
- package/lib/server/astro/nodeToAstro.test.ts +1754 -34
- package/lib/server/astro/nodeToAstro.ts +232 -216
- package/lib/server/astro/normalizeOrphanTemplateProps.test.ts +115 -119
- package/lib/server/astro/normalizeOrphanTemplateProps.ts +11 -18
- package/lib/server/astro/pageEmitter.ts +9 -13
- package/lib/server/astro/tailwindMapper.test.ts +10 -37
- package/lib/server/astro/tailwindMapper.ts +33 -40
- package/lib/server/astro/templateTransformer.ts +17 -20
- package/lib/server/createServer.ts +7 -19
- package/lib/server/cssGenerator.test.ts +35 -44
- package/lib/server/cssGenerator.ts +8 -19
- package/lib/server/fileWatcher.test.ts +164 -13
- package/lib/server/fileWatcher.ts +209 -124
- package/lib/server/index.ts +14 -17
- package/lib/server/jsonLoader.test.ts +39 -2
- package/lib/server/jsonLoader.ts +39 -34
- package/lib/server/middleware/cors.test.ts +21 -21
- package/lib/server/middleware/cors.ts +28 -4
- package/lib/server/middleware/errorHandler.test.ts +7 -5
- package/lib/server/middleware/errorHandler.ts +3 -8
- package/lib/server/middleware/index.ts +0 -1
- package/lib/server/middleware/logger.test.ts +10 -8
- package/lib/server/middleware/logger.ts +17 -29
- package/lib/server/migrateTemplates.ts +3 -3
- package/lib/server/pageCache.test.ts +76 -77
- package/lib/server/pageCache.ts +0 -1
- package/lib/server/projectContext.ts +5 -4
- package/lib/server/providers/fileSystemCMSProvider.test.ts +142 -99
- package/lib/server/providers/fileSystemCMSProvider.ts +62 -38
- package/lib/server/routes/api/cms.test.ts +26 -14
- package/lib/server/routes/api/cms.ts +9 -14
- package/lib/server/routes/api/colors.test.ts +2 -2
- package/lib/server/routes/api/components.ts +31 -35
- package/lib/server/routes/api/config.ts +3 -4
- package/lib/server/routes/api/core-routes.ts +49 -76
- package/lib/server/routes/api/enums.test.ts +2 -2
- package/lib/server/routes/api/functions.ts +9 -17
- package/lib/server/routes/api/index.ts +3 -6
- package/lib/server/routes/api/pages.ts +8 -11
- package/lib/server/routes/api/shared.test.ts +1 -1
- package/lib/server/routes/api/shared.ts +12 -9
- package/lib/server/routes/api/variables.test.ts +3 -5
- package/lib/server/routes/api/variables.ts +1 -1
- package/lib/server/routes/index.ts +25 -28
- package/lib/server/routes/pages.ts +35 -31
- package/lib/server/routes/static.ts +17 -6
- package/lib/server/runtime/bundler.ts +98 -61
- package/lib/server/runtime/fs.ts +16 -26
- package/lib/server/runtime/httpServer.ts +8 -12
- package/lib/server/services/ColorService.test.ts +3 -3
- package/lib/server/services/ColorService.ts +37 -29
- package/lib/server/services/EnumService.test.ts +4 -8
- package/lib/server/services/EnumService.ts +11 -3
- package/lib/server/services/VariableService.test.ts +8 -12
- package/lib/server/services/VariableService.ts +11 -3
- package/lib/server/services/cmsService.test.ts +158 -120
- package/lib/server/services/cmsService.ts +36 -146
- package/lib/server/services/componentService.test.ts +125 -45
- package/lib/server/services/componentService.ts +70 -46
- package/lib/server/services/configService.test.ts +22 -45
- package/lib/server/services/configService.ts +50 -27
- package/lib/server/services/fileWatcherService.ts +42 -8
- package/lib/server/services/index.ts +0 -1
- package/lib/server/services/pageService.test.ts +8 -10
- package/lib/server/services/pageService.ts +32 -18
- package/lib/server/ssr/attributeBuilder.ts +18 -8
- package/lib/server/ssr/buildErrorOverlay.ts +11 -12
- package/lib/server/ssr/clientDataInjector.ts +7 -21
- package/lib/server/ssr/cmsSSRProcessor.ts +4 -8
- package/lib/server/ssr/cssCollector.ts +1 -1
- package/lib/server/ssr/errorOverlay.test.ts +1 -1
- package/lib/server/ssr/errorOverlay.ts +3 -9
- package/lib/server/ssr/htmlGenerator.nonce.test.ts +9 -14
- package/lib/server/ssr/htmlGenerator.test.ts +120 -44
- package/lib/server/ssr/htmlGenerator.ts +126 -91
- package/lib/server/ssr/imageMetadata.test.ts +3 -1
- package/lib/server/ssr/imageMetadata.ts +27 -21
- package/lib/server/ssr/jsCollector.test.ts +8 -18
- package/lib/server/ssr/jsCollector.ts +3 -8
- package/lib/server/ssr/liveReloadIntegration.test.ts +76 -36
- package/lib/server/ssr/metaTagGenerator.ts +2 -2
- package/lib/server/ssr/ssrRenderer.branches.test.ts +1103 -0
- package/lib/server/ssr/ssrRenderer.test.ts +316 -256
- package/lib/server/ssr/ssrRenderer.ts +795 -243
- package/lib/server/ssrRenderer.test.ts +1048 -954
- package/lib/server/utils/jsonLineMapper.test.ts +28 -28
- package/lib/server/utils/jsonLineMapper.ts +7 -7
- package/lib/server/validateStyleCoverage.ts +18 -21
- package/lib/server/websocketManager.ts +28 -19
- package/lib/shared/attributeNodeUtils.test.ts +15 -15
- package/lib/shared/attributeNodeUtils.ts +5 -12
- package/lib/shared/breakpoints.test.ts +5 -5
- package/lib/shared/breakpoints.ts +4 -11
- package/lib/shared/cmsQuery.test.ts +81 -0
- package/lib/shared/cmsQuery.ts +92 -0
- package/lib/shared/cmsQueryParser.test.ts +50 -42
- package/lib/shared/cmsQueryParser.ts +60 -38
- package/lib/shared/colorConversions.ts +9 -9
- package/lib/shared/colorVariableUtils.test.ts +27 -5
- package/lib/shared/colorVariableUtils.ts +21 -12
- package/lib/shared/componentRefs.ts +1 -5
- package/lib/shared/constants.test.ts +3 -3
- package/lib/shared/constants.ts +8 -8
- package/lib/shared/cssGeneration.test.ts +285 -144
- package/lib/shared/cssGeneration.ts +217 -531
- package/lib/shared/cssNamedColors.ts +152 -30
- package/lib/shared/cssProperties.test.ts +69 -70
- package/lib/shared/cssProperties.ts +298 -114
- package/lib/shared/elementClassName.test.ts +109 -109
- package/lib/shared/elementClassName.ts +3 -3
- package/lib/shared/elementUtils.ts +12 -16
- package/lib/shared/errorLogger.test.ts +12 -12
- package/lib/shared/errorLogger.ts +2 -10
- package/lib/shared/errors.test.ts +2 -13
- package/lib/shared/errors.ts +2 -8
- package/lib/shared/expressionEvaluator.test.ts +145 -0
- package/lib/shared/expressionEvaluator.ts +52 -24
- package/lib/shared/fontLoader.test.ts +32 -18
- package/lib/shared/fontLoader.ts +2 -2
- package/lib/shared/friendlyError.ts +2 -3
- package/lib/shared/gradientUtils.test.ts +3 -7
- package/lib/shared/gradientUtils.ts +17 -14
- package/lib/shared/hrefRefs.test.ts +2 -2
- package/lib/shared/hrefRefs.ts +2 -10
- package/lib/shared/i18n.test.ts +4 -4
- package/lib/shared/i18n.ts +18 -38
- package/lib/shared/index.ts +27 -0
- package/lib/shared/inlineSvgStyleRules.ts +14 -13
- package/lib/shared/interactiveStyleMappings.test.ts +32 -54
- package/lib/shared/interactiveStyleMappings.ts +11 -24
- package/lib/shared/interactiveStyles.test.ts +165 -188
- package/lib/shared/interfaces/contentProvider.ts +1 -1
- package/lib/shared/itemTemplateUtils.test.ts +11 -17
- package/lib/shared/itemTemplateUtils.ts +27 -39
- package/lib/shared/jsonRepair.ts +16 -10
- package/lib/shared/libraryLoader.test.ts +21 -55
- package/lib/shared/libraryLoader.ts +7 -22
- package/lib/shared/linkUtils.ts +1 -1
- package/lib/shared/logger.test.ts +66 -0
- package/lib/shared/logger.ts +94 -0
- package/lib/shared/markdown.ts +25 -0
- package/lib/shared/netlifyLocale404.test.ts +179 -0
- package/lib/shared/netlifyLocale404.ts +110 -0
- package/lib/shared/nodeUtils.test.ts +28 -16
- package/lib/shared/nodeUtils.ts +76 -23
- package/lib/shared/pathArrayUtils.test.ts +1 -2
- package/lib/shared/pathArrayUtils.ts +1 -1
- package/lib/shared/pathSecurity.ts +2 -2
- package/lib/shared/pathUtils.test.ts +4 -6
- package/lib/shared/pathUtils.ts +42 -49
- package/lib/shared/paths/Path.test.ts +2 -2
- package/lib/shared/paths/Path.ts +0 -1
- package/lib/shared/paths/PathConverter.test.ts +1 -1
- package/lib/shared/paths/PathConverter.ts +17 -20
- package/lib/shared/paths/PathUtils.ts +13 -13
- package/lib/shared/paths/PathValidator.test.ts +2 -15
- package/lib/shared/paths/PathValidator.ts +12 -10
- package/lib/shared/paths/index.ts +1 -2
- package/lib/shared/permissions.test.ts +168 -0
- package/lib/shared/permissions.ts +162 -0
- package/lib/shared/propResolver.test.ts +240 -244
- package/lib/shared/propResolver.ts +15 -26
- package/lib/shared/pxToRem.test.ts +7 -6
- package/lib/shared/pxToRem.ts +2 -5
- package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +12 -6
- package/lib/shared/registry/ClientRegistry.ts +0 -2
- package/lib/shared/registry/ComponentRegistry.test.ts +46 -32
- package/lib/shared/registry/ComponentRegistry.ts +12 -9
- package/lib/shared/registry/NodeTypeDefinition.ts +16 -9
- package/lib/shared/registry/RegistryManager.ts +1 -2
- package/lib/shared/registry/SSRRegistry.ts +0 -2
- package/lib/shared/registry/createNodeType.ts +7 -9
- package/lib/shared/registry/defineNodeType.ts +2 -6
- package/lib/shared/registry/fieldPresets.ts +3 -1
- package/lib/shared/registry/index.ts +0 -1
- package/lib/shared/registry/nodeTypes/ComponentInstanceNodeType.ts +14 -15
- package/lib/shared/registry/nodeTypes/CustomNodeType.ts +103 -0
- package/lib/shared/registry/nodeTypes/EmbedNodeType.ts +18 -11
- package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +48 -19
- package/lib/shared/registry/nodeTypes/IslandNodeType.ts +129 -0
- package/lib/shared/registry/nodeTypes/LinkNodeType.ts +21 -19
- package/lib/shared/registry/nodeTypes/ListNodeType.ts +117 -77
- package/lib/shared/registry/nodeTypes/LocaleListNodeType.ts +27 -21
- package/lib/shared/registry/nodeTypes/MarkdownNodeType.ts +78 -0
- package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +12 -8
- package/lib/shared/registry/nodeTypes/index.ts +26 -3
- package/lib/shared/responsiveScaling.test.ts +16 -34
- package/lib/shared/responsiveScaling.ts +61 -42
- package/lib/shared/responsiveStyleUtils.test.ts +0 -1
- package/lib/shared/responsiveStyleUtils.ts +11 -13
- package/lib/shared/richtext/htmlToTiptap.test.ts +139 -130
- package/lib/shared/richtext/htmlToTiptap.ts +2 -4
- package/lib/shared/richtext/tiptapToHtml.test.ts +5 -6
- package/lib/shared/richtext/types.ts +1 -8
- package/lib/shared/slugTranslator.test.ts +13 -13
- package/lib/shared/slugTranslator.ts +12 -16
- package/lib/shared/slugify.ts +9 -15
- package/lib/shared/styleNodeUtils.test.ts +8 -8
- package/lib/shared/styleNodeUtils.ts +18 -20
- package/lib/shared/styleUtils.test.ts +87 -61
- package/lib/shared/styleUtils.ts +5 -6
- package/lib/shared/themeDefaults.test.ts +11 -14
- package/lib/shared/themeDefaults.ts +3 -4
- package/lib/shared/tree/PathBuilder.test.ts +63 -145
- package/lib/shared/tree/PathBuilder.ts +59 -47
- package/lib/shared/treePathUtils.test.ts +51 -12
- package/lib/shared/treePathUtils.ts +91 -60
- package/lib/shared/types/api.ts +105 -8
- package/lib/shared/types/cms.test.ts +34 -0
- package/lib/shared/types/cms.ts +90 -34
- package/lib/shared/types/comment.ts +49 -16
- package/lib/shared/types/components.ts +51 -25
- package/lib/shared/types/errors.test.ts +1 -6
- package/lib/shared/types/errors.ts +3 -7
- package/lib/shared/types/experiments.ts +28 -28
- package/lib/shared/types/index.ts +25 -2
- package/lib/shared/types/permissions.ts +139 -0
- package/lib/shared/types/styles.ts +0 -1
- package/lib/shared/types/variables.test.ts +4 -13
- package/lib/shared/types/variables.ts +51 -30
- package/lib/shared/types.ts +1 -2
- package/lib/shared/utilityClassConfig.ts +751 -321
- package/lib/shared/utilityClassMapper.durability.test.ts +117 -0
- package/lib/shared/utilityClassMapper.test.ts +433 -95
- package/lib/shared/utilityClassMapper.ts +322 -249
- package/lib/shared/utilityClassNames.test.ts +38 -0
- package/lib/shared/utilityClassNames.ts +458 -0
- package/lib/shared/utils.test.ts +2 -10
- package/lib/shared/utils.ts +19 -10
- package/lib/shared/validation/cmsValidators.ts +2 -1
- package/lib/shared/validation/commentValidators.test.ts +53 -0
- package/lib/shared/validation/commentValidators.ts +12 -1
- package/lib/shared/validation/index.ts +1 -0
- package/lib/shared/validation/permissionsValidators.test.ts +49 -0
- package/lib/shared/validation/permissionsValidators.ts +49 -0
- package/lib/shared/validation/propValidator.test.ts +19 -21
- package/lib/shared/validation/propValidator.ts +13 -18
- package/lib/shared/validation/schemas.test.ts +64 -32
- package/lib/shared/validation/schemas.ts +616 -345
- package/lib/shared/validation/validators.test.ts +3 -8
- package/lib/shared/validation/validators.ts +89 -68
- package/lib/shared/viewportUnits.integration.test.ts +14 -10
- package/lib/shared/viewportUnits.test.ts +24 -23
- package/lib/shared/viewportUnits.ts +12 -5
- package/lib/test-utils/dom-setup.ts +1 -1
- package/lib/test-utils/factories/ConsoleMockFactory.ts +10 -14
- package/lib/test-utils/factories/DomMockFactory.ts +17 -23
- package/lib/test-utils/factories/EventMockFactory.ts +7 -13
- package/lib/test-utils/factories/FetchMockFactory.ts +39 -57
- package/lib/test-utils/factories/ServerMockFactory.ts +6 -10
- package/lib/test-utils/factories/StoreMockFactory.ts +18 -29
- package/lib/test-utils/fixtures.ts +45 -45
- package/lib/test-utils/helpers/asyncHelpers.test.ts +15 -18
- package/lib/test-utils/helpers/asyncHelpers.ts +11 -20
- package/lib/test-utils/helpers.ts +1 -5
- package/lib/test-utils/index.ts +0 -4
- package/lib/test-utils/mockFactories.ts +13 -19
- package/lib/test-utils/mocks.ts +6 -4
- package/package.json +3 -5
- package/scripts/build-for-publish.mjs +2 -4
- package/scripts/build-meno-filter.ts +4 -7
- package/tsconfig.json +1 -0
- package/vite.config.ts +4 -4
- package/build-next.ts +0 -1361
- package/build-static.test.ts +0 -424
- package/build-static.ts +0 -1074
- package/dist/build-static.js +0 -36
- package/dist/chunks/chunk-2MHDV5BF.js +0 -251
- package/dist/chunks/chunk-2MHDV5BF.js.map +0 -7
- package/dist/chunks/chunk-AZQYF6KE.js +0 -559
- package/dist/chunks/chunk-AZQYF6KE.js.map +0 -7
- package/dist/chunks/chunk-GZHGVVW3.js +0 -322
- package/dist/chunks/chunk-GZHGVVW3.js.map +0 -7
- package/dist/chunks/chunk-H3GJ4H2U.js.map +0 -7
- package/dist/chunks/chunk-I7YIGZXT.js +0 -467
- package/dist/chunks/chunk-I7YIGZXT.js.map +0 -7
- package/dist/chunks/chunk-IGYR22T6.js +0 -6192
- package/dist/chunks/chunk-IGYR22T6.js.map +0 -7
- package/dist/chunks/chunk-JGP5A3Y5.js +0 -1136
- package/dist/chunks/chunk-JGP5A3Y5.js.map +0 -7
- package/dist/chunks/chunk-JGWFTO6P.js.map +0 -7
- package/dist/chunks/chunk-O3NAGJP4.js.map +0 -7
- package/dist/chunks/chunk-QB2LNO4W.js +0 -77
- package/dist/chunks/chunk-QB2LNO4W.js.map +0 -7
- package/dist/chunks/chunk-R6XHAFBF.js +0 -3821
- package/dist/chunks/chunk-R6XHAFBF.js.map +0 -7
- package/dist/chunks/chunk-UB44F4Z2.js.map +0 -7
- package/dist/chunks/chunk-WQFG7PAH.js.map +0 -7
- package/dist/chunks/chunk-X754AHS5.js.map +0 -7
- package/dist/chunks/chunk-YBLHKYFF.js.map +0 -7
- package/dist/chunks/configService-R3OGU2UD.js +0 -13
- package/dist/chunks/configService-R3OGU2UD.js.map +0 -7
- package/dist/chunks/constants-STK2YBIW.js +0 -46
- package/dist/chunks/constants-STK2YBIW.js.map +0 -7
- package/dist/chunks/fs-JGINUXGL.js.map +0 -7
- package/dist/entries/server-router.js +0 -67
- package/dist/entries/server-router.js.map +0 -7
- package/entries/server-router.tsx +0 -81
- package/lib/server/providers/fileSystemPageProvider.ts +0 -160
- package/lib/server/webflow/buildWebflow.ts +0 -635
- package/lib/server/webflow/index.ts +0 -23
- package/lib/server/webflow/nodeToWebflow.test.ts +0 -3170
- package/lib/server/webflow/nodeToWebflow.ts +0 -2435
- package/lib/server/webflow/styleMapper.test.ts +0 -389
- package/lib/server/webflow/styleMapper.ts +0 -695
- package/lib/server/webflow/templateWrapper.ts +0 -49
- package/lib/server/webflow/types.ts +0 -396
- /package/dist/{build-static.js.map → chunks/fs-G3ANWEFP.js.map} +0 -0
package/build-static.ts
DELETED
|
@@ -1,1074 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Static Site Generation Build Script
|
|
3
|
-
* Pre-generates HTML files for all pages at build time
|
|
4
|
-
* CSP-compliant: Extracts JavaScript to external files
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { existsSync, readdirSync, mkdirSync, rmSync, statSync, copyFileSync, unlinkSync, writeFileSync } from "fs";
|
|
8
|
-
import { writeFile, readFile } from "fs/promises";
|
|
9
|
-
import { join } from "path";
|
|
10
|
-
import type { BuildError, BuildErrorsData } from "./lib/server/ssr/buildErrorOverlay";
|
|
11
|
-
import { createHash } from "crypto";
|
|
12
|
-
import { inspect, minifyJS as runtimeMinifyJS } from './lib/server/runtime';
|
|
13
|
-
import {
|
|
14
|
-
loadJSONFile,
|
|
15
|
-
loadComponentDirectory,
|
|
16
|
-
mapPageNameToPath,
|
|
17
|
-
parseJSON,
|
|
18
|
-
loadI18nConfig
|
|
19
|
-
} from "./lib/server/jsonLoader";
|
|
20
|
-
import { generateSSRHTML } from "./lib/server/ssrRenderer";
|
|
21
|
-
import type { SSRHTMLResult } from "./lib/server/ssr/htmlGenerator";
|
|
22
|
-
import { prepareClientData, type ClientDataCollection } from "./lib/server/ssr/clientDataInjector";
|
|
23
|
-
import { clearJSValidationCache, getJSValidationErrors } from "./lib/server/ssr/jsCollector";
|
|
24
|
-
import { projectPaths } from "./lib/server/projectContext";
|
|
25
|
-
import { loadProjectConfig } from "./lib/shared/fontLoader";
|
|
26
|
-
import { FileSystemCMSProvider } from "./lib/server/providers/fileSystemCMSProvider";
|
|
27
|
-
import { CMSService } from "./lib/server/services/cmsService";
|
|
28
|
-
import { isI18nValue, resolveI18nValue } from "./lib/shared/i18n";
|
|
29
|
-
import type { ComponentDefinition, JSONPage, CMSSchema, CMSItem, I18nConfig, Experiment } from "./lib/shared/types";
|
|
30
|
-
import { isItemDraftForLocale, isItemFullyPublished } from "./lib/shared/types";
|
|
31
|
-
import type { SlugMap } from "./lib/shared/slugTranslator";
|
|
32
|
-
import { buildItemUrl } from "./lib/shared/itemTemplateUtils";
|
|
33
|
-
import { generateMiddleware, generateTrackFunction, generateResultsFunction } from "./lib/server/ab/generateFunctions";
|
|
34
|
-
import { generateTrackingScript } from "./lib/server/ab/trackingScript";
|
|
35
|
-
import { migrateTemplatesDirectory } from "./lib/server/migrateTemplates";
|
|
36
|
-
import { extractLibraryOrigins, collectComponentLibraries, filterLibrariesByContext } from "./lib/shared/libraryLoader";
|
|
37
|
-
import type { LibrariesConfig } from "./lib/shared/types/libraries";
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Collect build errors for error overlay
|
|
41
|
-
*/
|
|
42
|
-
const buildErrors: BuildError[] = [];
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Generate short hash from content for file naming
|
|
46
|
-
*/
|
|
47
|
-
export function hashContent(content: string): string {
|
|
48
|
-
return createHash('sha256').update(content).digest('hex').slice(0, 8);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Format a Bun build log entry to a readable string
|
|
53
|
-
*/
|
|
54
|
-
export function formatBunLog(log: any): string {
|
|
55
|
-
const parts: string[] = [];
|
|
56
|
-
|
|
57
|
-
// Try to get position info
|
|
58
|
-
if (log.position) {
|
|
59
|
-
const pos = log.position;
|
|
60
|
-
if (pos.file) parts.push(`File: ${pos.file}`);
|
|
61
|
-
if (pos.line !== undefined) parts.push(`Line ${pos.line}:${pos.column || 0}`);
|
|
62
|
-
if (pos.lineText) parts.push(` ${pos.lineText}`);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Get the message
|
|
66
|
-
if (log.message) {
|
|
67
|
-
parts.push(log.message);
|
|
68
|
-
} else if (log.text) {
|
|
69
|
-
parts.push(log.text);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// If we couldn't extract anything useful, stringify the whole thing
|
|
73
|
-
if (parts.length === 0) {
|
|
74
|
-
try {
|
|
75
|
-
return JSON.stringify(log, null, 2);
|
|
76
|
-
} catch {
|
|
77
|
-
return String(log);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return parts.join('\n');
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Minify JavaScript code using runtime bundler
|
|
86
|
-
* Throws on error instead of silently failing
|
|
87
|
-
*/
|
|
88
|
-
async function minifyJS(code: string): Promise<string> {
|
|
89
|
-
const result = await runtimeMinifyJS(code);
|
|
90
|
-
if (result.success) {
|
|
91
|
-
return result.code;
|
|
92
|
-
}
|
|
93
|
-
throw new Error(`JavaScript minification failed:\n${result.errors.join('\n\n')}`);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Track JavaScript files to avoid duplicates
|
|
98
|
-
* Maps content hash -> script path
|
|
99
|
-
*/
|
|
100
|
-
const jsFileCache = new Map<string, string>();
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Get or create script file path for given JS content
|
|
104
|
-
* Returns the path to reference in HTML
|
|
105
|
-
*/
|
|
106
|
-
async function getScriptPath(jsContent: string, distDir: string): Promise<string> {
|
|
107
|
-
// Minify JavaScript for production
|
|
108
|
-
const minified = await minifyJS(jsContent);
|
|
109
|
-
const hash = hashContent(minified);
|
|
110
|
-
|
|
111
|
-
// Check if we already wrote this content
|
|
112
|
-
if (jsFileCache.has(hash)) {
|
|
113
|
-
return jsFileCache.get(hash)!;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Create scripts directory if needed
|
|
117
|
-
const scriptsDir = join(distDir, '_scripts');
|
|
118
|
-
if (!existsSync(scriptsDir)) {
|
|
119
|
-
mkdirSync(scriptsDir, { recursive: true });
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Write minified script file
|
|
123
|
-
const scriptPath = `/_scripts/${hash}.js`;
|
|
124
|
-
const fullPath = join(distDir, '_scripts', `${hash}.js`);
|
|
125
|
-
await writeFile(fullPath, minified, 'utf-8');
|
|
126
|
-
|
|
127
|
-
// Cache for reuse
|
|
128
|
-
jsFileCache.set(hash, scriptPath);
|
|
129
|
-
|
|
130
|
-
return scriptPath;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Recursively copy directory contents
|
|
135
|
-
*/
|
|
136
|
-
function copyDirectory(src: string, dest: string): void {
|
|
137
|
-
if (!existsSync(src)) {
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (!existsSync(dest)) {
|
|
142
|
-
mkdirSync(dest, { recursive: true });
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const files = readdirSync(src);
|
|
146
|
-
|
|
147
|
-
for (const file of files) {
|
|
148
|
-
const srcPath = join(src, file);
|
|
149
|
-
const destPath = join(dest, file);
|
|
150
|
-
const stat = statSync(srcPath);
|
|
151
|
-
|
|
152
|
-
if (stat.isDirectory()) {
|
|
153
|
-
copyDirectory(srcPath, destPath);
|
|
154
|
-
} else {
|
|
155
|
-
copyFileSync(srcPath, destPath);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Get locale-specific output path for a page with translated slug support
|
|
163
|
-
* Default locale files go to root, other locales to subdirectories
|
|
164
|
-
* Uses translated slugs from meta.slugs if available
|
|
165
|
-
*/
|
|
166
|
-
export function getLocalizedOutputPath(
|
|
167
|
-
basePath: string,
|
|
168
|
-
locale: string,
|
|
169
|
-
defaultLocale: string,
|
|
170
|
-
distDir: string,
|
|
171
|
-
slugs?: Record<string, string>
|
|
172
|
-
): string {
|
|
173
|
-
// Get translated slug for this locale, or fall back to default path
|
|
174
|
-
let slug: string;
|
|
175
|
-
if (slugs && slugs[locale]) {
|
|
176
|
-
slug = slugs[locale];
|
|
177
|
-
} else if (basePath === "/") {
|
|
178
|
-
slug = "";
|
|
179
|
-
} else {
|
|
180
|
-
slug = basePath.substring(1);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Build filename from slug
|
|
184
|
-
const fileName = slug === "" ? "index.html" : `${slug}.html`;
|
|
185
|
-
|
|
186
|
-
if (locale === defaultLocale) {
|
|
187
|
-
return `${distDir}/${fileName}`;
|
|
188
|
-
}
|
|
189
|
-
return `${distDir}/${locale}/${fileName}`;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Get display path for logging (the actual URL the user will visit)
|
|
194
|
-
*/
|
|
195
|
-
export function getDisplayPath(
|
|
196
|
-
basePath: string,
|
|
197
|
-
locale: string,
|
|
198
|
-
defaultLocale: string,
|
|
199
|
-
slugs?: Record<string, string>
|
|
200
|
-
): string {
|
|
201
|
-
// Get translated slug for this locale
|
|
202
|
-
let slug: string;
|
|
203
|
-
if (slugs && slugs[locale]) {
|
|
204
|
-
slug = slugs[locale];
|
|
205
|
-
} else if (basePath === "/") {
|
|
206
|
-
slug = "";
|
|
207
|
-
} else {
|
|
208
|
-
slug = basePath.substring(1);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (locale === defaultLocale) {
|
|
212
|
-
return slug === "" ? "/" : `/${slug}`;
|
|
213
|
-
}
|
|
214
|
-
return slug === "" ? `/${locale}` : `/${locale}/${slug}`;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Generate robots.txt with sensible defaults
|
|
219
|
-
*/
|
|
220
|
-
export async function generateRobotsTxt(siteUrl: string, distDir: string): Promise<void> {
|
|
221
|
-
const content = `User-agent: *
|
|
222
|
-
Allow: /
|
|
223
|
-
|
|
224
|
-
Sitemap: ${siteUrl}/sitemap.xml
|
|
225
|
-
`;
|
|
226
|
-
await writeFile(join(distDir, 'robots.txt'), content, 'utf-8');
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Generate sitemap.xml from collected URLs
|
|
231
|
-
*/
|
|
232
|
-
export async function generateSitemap(urls: string[], siteUrl: string, distDir: string): Promise<void> {
|
|
233
|
-
// Sort URLs for deterministic output
|
|
234
|
-
const sortedUrls = [...urls].sort();
|
|
235
|
-
|
|
236
|
-
const urlEntries = sortedUrls
|
|
237
|
-
.map(url => ` <url><loc>${siteUrl}${url}</loc></url>`)
|
|
238
|
-
.join('\n');
|
|
239
|
-
|
|
240
|
-
const content = `<?xml version="1.0" encoding="UTF-8"?>
|
|
241
|
-
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
242
|
-
${urlEntries}
|
|
243
|
-
</urlset>
|
|
244
|
-
`;
|
|
245
|
-
await writeFile(join(distDir, 'sitemap.xml'), content, 'utf-8');
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Clean dist directory, keeping only production files
|
|
250
|
-
*/
|
|
251
|
-
function cleanDist(distDir: string = projectPaths.dist()): void {
|
|
252
|
-
if (!existsSync(distDir)) {
|
|
253
|
-
mkdirSync(distDir, { recursive: true });
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
const files = readdirSync(distDir);
|
|
258
|
-
let cleaned = 0;
|
|
259
|
-
|
|
260
|
-
for (const file of files) {
|
|
261
|
-
// Keep fonts, images, icons, assets, and videos
|
|
262
|
-
if (
|
|
263
|
-
file === "fonts" ||
|
|
264
|
-
file === "images" ||
|
|
265
|
-
file === "icons" ||
|
|
266
|
-
file === "assets" ||
|
|
267
|
-
file === "videos"
|
|
268
|
-
) {
|
|
269
|
-
continue;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Remove everything else (editor files, old HTML, etc.)
|
|
273
|
-
try {
|
|
274
|
-
const filePath = join(distDir, file);
|
|
275
|
-
const stat = statSync(filePath);
|
|
276
|
-
|
|
277
|
-
if (stat.isDirectory()) {
|
|
278
|
-
rmSync(filePath, { recursive: true, force: true });
|
|
279
|
-
} else {
|
|
280
|
-
rmSync(filePath, { force: true });
|
|
281
|
-
}
|
|
282
|
-
cleaned++;
|
|
283
|
-
} catch (error) {
|
|
284
|
-
console.warn(`⚠️ Could not remove ${file}:`, error);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
if (cleaned > 0) {
|
|
289
|
-
console.log(`🧹 Cleaned ${cleaned} file(s) from dist\n`);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Check if a page is a CMS template
|
|
295
|
-
*/
|
|
296
|
-
export function isCMSPage(pageData: JSONPage): boolean {
|
|
297
|
-
return pageData.meta?.source === 'cms' && !!pageData.meta?.cms;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* Build URL path for a CMS item based on the URL pattern
|
|
302
|
-
* Uses schema.slugField to get the slug value, supporting i18n slugs
|
|
303
|
-
*/
|
|
304
|
-
export function buildCMSItemPath(
|
|
305
|
-
urlPattern: string,
|
|
306
|
-
item: CMSItem,
|
|
307
|
-
slugField: string,
|
|
308
|
-
locale: string,
|
|
309
|
-
i18nConfig: I18nConfig
|
|
310
|
-
): string {
|
|
311
|
-
// Get slug from the schema-defined slugField, fall back to _slug or _id
|
|
312
|
-
let slug = item[slugField] ?? item._slug ?? item._id;
|
|
313
|
-
|
|
314
|
-
// Resolve i18n slug to locale-specific value
|
|
315
|
-
if (isI18nValue(slug)) {
|
|
316
|
-
slug = resolveI18nValue(slug, locale, i18nConfig) as string;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
return urlPattern.replace('{{slug}}', String(slug));
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* Generate static JSON data files for collections with 'static' strategy
|
|
324
|
-
* Output: /data/{collection}/index.json
|
|
325
|
-
*/
|
|
326
|
-
async function generateStaticDataFiles(
|
|
327
|
-
staticCollections: Map<string, ClientDataCollection>,
|
|
328
|
-
distDir: string
|
|
329
|
-
): Promise<void> {
|
|
330
|
-
if (staticCollections.size === 0) return;
|
|
331
|
-
|
|
332
|
-
console.log(`\n📦 Generating static data files...`);
|
|
333
|
-
|
|
334
|
-
for (const [collectionId, data] of staticCollections) {
|
|
335
|
-
const dataDir = join(distDir, 'data', collectionId);
|
|
336
|
-
if (!existsSync(dataDir)) {
|
|
337
|
-
mkdirSync(dataDir, { recursive: true });
|
|
338
|
-
}
|
|
339
|
-
const jsonPath = join(dataDir, 'index.json');
|
|
340
|
-
await writeFile(jsonPath, JSON.stringify(data.items), 'utf-8');
|
|
341
|
-
console.log(` ✅ /data/${collectionId}/index.json (${data.items.length} items)`);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* Build CMS templates from root templates/ directory
|
|
347
|
-
*/
|
|
348
|
-
async function buildCMSTemplates(
|
|
349
|
-
templatesDir: string,
|
|
350
|
-
globalComponents: Record<string, ComponentDefinition>,
|
|
351
|
-
i18nConfig: I18nConfig,
|
|
352
|
-
slugMappings: SlugMap[],
|
|
353
|
-
distDir: string,
|
|
354
|
-
cmsService: CMSService,
|
|
355
|
-
generatedUrls: Set<string>,
|
|
356
|
-
staticCollections: Map<string, ClientDataCollection>,
|
|
357
|
-
siteUrl?: string,
|
|
358
|
-
abTrackingScript?: string | null
|
|
359
|
-
): Promise<{ success: number; errors: number }> {
|
|
360
|
-
let successCount = 0;
|
|
361
|
-
let errorCount = 0;
|
|
362
|
-
|
|
363
|
-
if (!existsSync(templatesDir)) {
|
|
364
|
-
return { success: 0, errors: 0 };
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
const templateFiles = readdirSync(templatesDir).filter(f => f.endsWith('.json'));
|
|
368
|
-
|
|
369
|
-
if (templateFiles.length === 0) {
|
|
370
|
-
return { success: 0, errors: 0 };
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
console.log(`\n📝 Processing ${templateFiles.length} CMS template(s)...\n`);
|
|
374
|
-
|
|
375
|
-
for (const file of templateFiles) {
|
|
376
|
-
const templateContent = await loadJSONFile(join(templatesDir, file));
|
|
377
|
-
if (!templateContent) continue;
|
|
378
|
-
|
|
379
|
-
try {
|
|
380
|
-
const pageData = parseJSON<JSONPage>(templateContent);
|
|
381
|
-
|
|
382
|
-
// Skip draft templates in production (not in dev mode)
|
|
383
|
-
const isDevBuild = process.env.MENO_DEV_BUILD === 'true';
|
|
384
|
-
if (pageData.meta?.draft === true && !isDevBuild) {
|
|
385
|
-
console.log(`⏭️ Skipping draft template: ${file}`);
|
|
386
|
-
continue;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
if (!isCMSPage(pageData)) {
|
|
390
|
-
console.warn(`⚠️ ${file} is in templates/ but missing meta.source: "cms"`);
|
|
391
|
-
continue;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
const cmsSchema = pageData.meta!.cms as CMSSchema;
|
|
395
|
-
console.log(`📝 CMS Collection: ${cmsSchema.id}`);
|
|
396
|
-
|
|
397
|
-
// Use shared cmsService to get items
|
|
398
|
-
const items = await cmsService.queryItems({ collection: cmsSchema.id });
|
|
399
|
-
|
|
400
|
-
if (items.length === 0) {
|
|
401
|
-
console.log(` ⚠️ No items found in cms/${cmsSchema.id}/`);
|
|
402
|
-
continue;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
console.log(` Found ${items.length} item(s)`);
|
|
406
|
-
|
|
407
|
-
// Prepare client data if clientData is enabled
|
|
408
|
-
// Filter out fully-draft items from client data (client data is locale-agnostic)
|
|
409
|
-
let clientDataCollections: Map<string, ClientDataCollection> | undefined;
|
|
410
|
-
if (cmsSchema.clientData?.enabled) {
|
|
411
|
-
const publishedItems = items.filter(item => isItemFullyPublished(item));
|
|
412
|
-
const clientData = prepareClientData(cmsSchema.id, publishedItems, cmsSchema.clientData);
|
|
413
|
-
if (clientData) {
|
|
414
|
-
if (clientData.strategy === 'inline') {
|
|
415
|
-
// Inline data embedded in HTML
|
|
416
|
-
clientDataCollections = new Map([[cmsSchema.id, clientData]]);
|
|
417
|
-
console.log(` 📦 Client data (inline): ${clientData.items.length} items (${clientData.config.fields?.length || 'all'} fields)`);
|
|
418
|
-
} else if (clientData.strategy === 'static') {
|
|
419
|
-
// Static data written to separate file
|
|
420
|
-
staticCollections.set(cmsSchema.id, clientData);
|
|
421
|
-
console.log(` 📦 Client data (static): ${clientData.items.length} items → /data/${cmsSchema.id}/index.json`);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
for (const item of items) {
|
|
427
|
-
for (const localeConfig of i18nConfig.locales) {
|
|
428
|
-
const locale = localeConfig.code;
|
|
429
|
-
|
|
430
|
-
// Skip draft items per locale in production builds
|
|
431
|
-
const isDevBuild = process.env.MENO_DEV_BUILD === 'true';
|
|
432
|
-
if (!isDevBuild && isItemDraftForLocale(item, locale)) {
|
|
433
|
-
console.log(` ⏭️ Skipping draft: ${item._filename || item._id} [${locale}]`);
|
|
434
|
-
continue;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
const baseUrl = siteUrl || "";
|
|
438
|
-
const itemPath = buildCMSItemPath(cmsSchema.urlPattern, item, cmsSchema.slugField, locale, i18nConfig);
|
|
439
|
-
|
|
440
|
-
// Create CMS item with computed _url for {{cms._url}} template access
|
|
441
|
-
const itemWithUrl: CMSItem = { ...item, _url: itemPath };
|
|
442
|
-
|
|
443
|
-
// Generate HTML with JS returned separately (CSP-compliant)
|
|
444
|
-
const result = await generateSSRHTML({
|
|
445
|
-
pageData,
|
|
446
|
-
globalComponents,
|
|
447
|
-
pagePath: itemPath,
|
|
448
|
-
baseUrl,
|
|
449
|
-
useBuiltBundle: true,
|
|
450
|
-
locale,
|
|
451
|
-
slugMappings,
|
|
452
|
-
cmsContext: { cms: itemWithUrl },
|
|
453
|
-
cmsService,
|
|
454
|
-
returnSeparateJS: true,
|
|
455
|
-
pageLibraries: pageData.meta?.libraries,
|
|
456
|
-
pageCustomCode: pageData.meta?.customCode,
|
|
457
|
-
clientDataCollections,
|
|
458
|
-
isProductionBuild: true,
|
|
459
|
-
}) as SSRHTMLResult;
|
|
460
|
-
|
|
461
|
-
// If there's JavaScript, write to external file and update HTML
|
|
462
|
-
let finalHtml = result.html;
|
|
463
|
-
if (result.javascript) {
|
|
464
|
-
const scriptPath = await getScriptPath(result.javascript, distDir);
|
|
465
|
-
finalHtml = finalHtml.replace('</body>', ` <script src="${scriptPath}" defer></script>\n</body>`);
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// Inject A/B tracking script if experiments are running
|
|
469
|
-
if (abTrackingScript) {
|
|
470
|
-
finalHtml = injectTrackingScript(finalHtml, abTrackingScript);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
const outputPath = locale === i18nConfig.defaultLocale
|
|
474
|
-
? `${distDir}${itemPath}.html`
|
|
475
|
-
: `${distDir}/${locale}${itemPath}.html`;
|
|
476
|
-
|
|
477
|
-
const outputDir = outputPath.substring(0, outputPath.lastIndexOf('/'));
|
|
478
|
-
if (outputDir && !existsSync(outputDir)) {
|
|
479
|
-
mkdirSync(outputDir, { recursive: true });
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
await writeFile(outputPath, finalHtml, 'utf-8');
|
|
483
|
-
|
|
484
|
-
const displayPath = locale === i18nConfig.defaultLocale ? itemPath : `/${locale}${itemPath}`;
|
|
485
|
-
generatedUrls.add(displayPath);
|
|
486
|
-
console.log(` ✅ ${displayPath}`);
|
|
487
|
-
successCount++;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
} catch (error: any) {
|
|
491
|
-
// Capture full error with as much detail as possible
|
|
492
|
-
let errorMessage: string;
|
|
493
|
-
|
|
494
|
-
if (error instanceof Error) {
|
|
495
|
-
// Check for AggregateError (multiple errors)
|
|
496
|
-
if ('errors' in error && Array.isArray(error.errors)) {
|
|
497
|
-
errorMessage = error.errors.map((e: any) => e.stack || e.message || String(e)).join('\n\n');
|
|
498
|
-
}
|
|
499
|
-
// Check for cause chain
|
|
500
|
-
else if (error.cause) {
|
|
501
|
-
const causeMsg = error.cause instanceof Error
|
|
502
|
-
? (error.cause.stack || error.cause.message)
|
|
503
|
-
: String(error.cause);
|
|
504
|
-
errorMessage = `${error.stack || error.message}\n\nCaused by:\n${causeMsg}`;
|
|
505
|
-
}
|
|
506
|
-
// Bun's BuildMessage has logs array
|
|
507
|
-
else if ('logs' in error && Array.isArray(error.logs)) {
|
|
508
|
-
errorMessage = error.logs.map(formatBunLog).join('\n\n');
|
|
509
|
-
}
|
|
510
|
-
else {
|
|
511
|
-
errorMessage = error.stack || error.message;
|
|
512
|
-
}
|
|
513
|
-
} else if (typeof error === 'object' && error !== null) {
|
|
514
|
-
// Bun BuildOutput has logs
|
|
515
|
-
if (error.logs && Array.isArray(error.logs)) {
|
|
516
|
-
errorMessage = error.logs.map(formatBunLog).join('\n\n');
|
|
517
|
-
} else {
|
|
518
|
-
errorMessage = String(error);
|
|
519
|
-
}
|
|
520
|
-
} else {
|
|
521
|
-
errorMessage = String(error);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
// If we still just have "Bundle failed", try to get more from inspect
|
|
525
|
-
if (errorMessage === 'Bundle failed' || errorMessage.includes('Bundle failed\n')) {
|
|
526
|
-
try {
|
|
527
|
-
const inspected = inspect(error);
|
|
528
|
-
if (inspected && inspected !== '[object Object]' && inspected.length > errorMessage.length) {
|
|
529
|
-
errorMessage = inspected;
|
|
530
|
-
}
|
|
531
|
-
} catch {}
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
console.error(`❌ Error processing ${file}:`, error);
|
|
535
|
-
buildErrors.push({
|
|
536
|
-
file: `templates/${file}`,
|
|
537
|
-
message: errorMessage,
|
|
538
|
-
type: errorMessage.includes('minification') || errorMessage.includes('minify') ? 'minify' : 'cms',
|
|
539
|
-
});
|
|
540
|
-
errorCount++;
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
return { success: successCount, errors: errorCount };
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
/**
|
|
548
|
-
* Load running experiments from experiments.json
|
|
549
|
-
*/
|
|
550
|
-
async function loadRunningExperiments(): Promise<Experiment[]> {
|
|
551
|
-
const experimentsPath = join(projectPaths.project, 'experiments.json');
|
|
552
|
-
if (!existsSync(experimentsPath)) return [];
|
|
553
|
-
|
|
554
|
-
try {
|
|
555
|
-
const content = await readFile(experimentsPath, 'utf-8');
|
|
556
|
-
const data = JSON.parse(content);
|
|
557
|
-
const experiments: Experiment[] = data.experiments || [];
|
|
558
|
-
return experiments.filter(e => e.status === 'running');
|
|
559
|
-
} catch {
|
|
560
|
-
return [];
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
/**
|
|
565
|
-
* Inject A/B tracking script into HTML before </head>
|
|
566
|
-
*/
|
|
567
|
-
export function injectTrackingScript(html: string, trackingJs: string): string {
|
|
568
|
-
const scriptTag = `<script>${trackingJs}</script>`;
|
|
569
|
-
return html.replace('</head>', `${scriptTag}\n</head>`);
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
/**
|
|
573
|
-
* Generate A/B testing Cloudflare Functions into dist/functions/
|
|
574
|
-
*/
|
|
575
|
-
async function generateABFunctions(experiments: Experiment[], distDir: string): Promise<void> {
|
|
576
|
-
// Generate middleware
|
|
577
|
-
const middlewareContent = generateMiddleware(experiments);
|
|
578
|
-
if (middlewareContent) {
|
|
579
|
-
const middlewarePath = join(distDir, 'functions', '_middleware.ts');
|
|
580
|
-
const middlewareDir = join(distDir, 'functions');
|
|
581
|
-
if (!existsSync(middlewareDir)) {
|
|
582
|
-
mkdirSync(middlewareDir, { recursive: true });
|
|
583
|
-
}
|
|
584
|
-
await writeFile(middlewarePath, middlewareContent, 'utf-8');
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
// Generate tracking endpoint
|
|
588
|
-
const trackContent = generateTrackFunction();
|
|
589
|
-
const trackDir = join(distDir, 'functions', 'api');
|
|
590
|
-
if (!existsSync(trackDir)) {
|
|
591
|
-
mkdirSync(trackDir, { recursive: true });
|
|
592
|
-
}
|
|
593
|
-
await writeFile(join(trackDir, 'ab-track.ts'), trackContent, 'utf-8');
|
|
594
|
-
|
|
595
|
-
// Generate results endpoint
|
|
596
|
-
const resultsContent = generateResultsFunction();
|
|
597
|
-
await writeFile(join(trackDir, 'ab-results.ts'), resultsContent, 'utf-8');
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
/**
|
|
601
|
-
* Main build function
|
|
602
|
-
*/
|
|
603
|
-
export async function buildStaticPages(outDir?: string): Promise<void> {
|
|
604
|
-
console.log("🏗️ Building static HTML files...\n");
|
|
605
|
-
|
|
606
|
-
// Clear previous build errors and JS validation cache
|
|
607
|
-
buildErrors.length = 0;
|
|
608
|
-
clearJSValidationCache();
|
|
609
|
-
|
|
610
|
-
// Reset configService to ensure it loads from the correct project directory
|
|
611
|
-
const { configService } = await import("./lib/server/services/configService");
|
|
612
|
-
configService.reset();
|
|
613
|
-
|
|
614
|
-
// Load project config first
|
|
615
|
-
const projectConfig = await loadProjectConfig();
|
|
616
|
-
const siteUrl = (projectConfig as { siteUrl?: string }).siteUrl?.replace(/\/$/, ''); // Remove trailing slash
|
|
617
|
-
|
|
618
|
-
// Track all generated URLs for sitemap
|
|
619
|
-
const generatedUrls = new Set<string>();
|
|
620
|
-
|
|
621
|
-
// Load running A/B experiments (if any)
|
|
622
|
-
const runningExperiments = await loadRunningExperiments();
|
|
623
|
-
let trackingScript: string | null = null;
|
|
624
|
-
if (runningExperiments.length > 0) {
|
|
625
|
-
const exp = runningExperiments[0]; // Use first running experiment
|
|
626
|
-
trackingScript = generateTrackingScript(exp.id, exp.conversionGoals);
|
|
627
|
-
console.log(`🧪 A/B experiment active: "${exp.name}" (${exp.variants.length} variant(s))\n`);
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
// Load i18n config for multi-locale build
|
|
631
|
-
const i18nConfig = await loadI18nConfig();
|
|
632
|
-
console.log(`🌐 Locales: ${i18nConfig.locales.map(l => l.code).join(", ")} (default: ${i18nConfig.defaultLocale})\n`);
|
|
633
|
-
|
|
634
|
-
// Auto-migrate pages/templates/ → templates/ if needed
|
|
635
|
-
await migrateTemplatesDirectory();
|
|
636
|
-
|
|
637
|
-
// Resolve output directory. Defaults to <project>/dist; callers (e.g. the
|
|
638
|
-
// Cloudflare deploy) may pass a temp dir to avoid leaving a dist/ in the project.
|
|
639
|
-
const distDir = outDir || projectPaths.dist();
|
|
640
|
-
|
|
641
|
-
// Clean dist directory (removes editor files, old HTML)
|
|
642
|
-
cleanDist(distDir);
|
|
643
|
-
|
|
644
|
-
// Clear the JS file cache since cleanDist() removed _scripts/
|
|
645
|
-
// Without this, cached entries would skip file creation on subsequent builds
|
|
646
|
-
jsFileCache.clear();
|
|
647
|
-
|
|
648
|
-
// Copy fonts, images, icons, and functions directories to dist
|
|
649
|
-
console.log("📦 Copying assets...");
|
|
650
|
-
|
|
651
|
-
// Delete old _errors.json if it exists (start fresh)
|
|
652
|
-
const errorsPath = join(distDir, '_errors.json');
|
|
653
|
-
if (existsSync(errorsPath)) {
|
|
654
|
-
unlinkSync(errorsPath);
|
|
655
|
-
}
|
|
656
|
-
copyDirectory(projectPaths.fonts(), join(distDir, "fonts"));
|
|
657
|
-
copyDirectory(projectPaths.images(), join(distDir, "images"));
|
|
658
|
-
copyDirectory(projectPaths.icons(), join(distDir, "icons"));
|
|
659
|
-
copyDirectory(projectPaths.assets(), join(distDir, "assets"));
|
|
660
|
-
copyDirectory(projectPaths.videos(), join(distDir, "videos"));
|
|
661
|
-
|
|
662
|
-
// Copy libraries folder (downloaded external JS/CSS files)
|
|
663
|
-
const librariesDir = join(projectPaths.project, "libraries");
|
|
664
|
-
if (existsSync(librariesDir)) {
|
|
665
|
-
copyDirectory(librariesDir, join(distDir, "libraries"));
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
// Copy functions folder for Cloudflare Pages
|
|
669
|
-
const functionsDir = projectPaths.functions();
|
|
670
|
-
if (existsSync(functionsDir)) {
|
|
671
|
-
copyDirectory(functionsDir, join(distDir, "functions"));
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
// Copy user-created root files for static hosting
|
|
675
|
-
const hostingFiles: string[] = [];
|
|
676
|
-
const rootFilesToCopy = [
|
|
677
|
-
'_headers', // Netlify/Cloudflare headers
|
|
678
|
-
'_redirects', // Netlify/Cloudflare redirects
|
|
679
|
-
'llms.txt', // LLM context
|
|
680
|
-
'humans.txt', // Team credits
|
|
681
|
-
'ads.txt', // Ad verification
|
|
682
|
-
'security.txt', // Security contact
|
|
683
|
-
'CNAME', // GitHub Pages domain
|
|
684
|
-
'manifest.json', // PWA manifest
|
|
685
|
-
'site.webmanifest', // PWA manifest (alt)
|
|
686
|
-
];
|
|
687
|
-
|
|
688
|
-
for (const file of rootFilesToCopy) {
|
|
689
|
-
const filePath = join(projectPaths.project, file);
|
|
690
|
-
if (existsSync(filePath)) {
|
|
691
|
-
copyFileSync(filePath, join(distDir, file));
|
|
692
|
-
hostingFiles.push(file);
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
// Copy .well-known directory if exists
|
|
697
|
-
const wellKnownDir = join(projectPaths.project, '.well-known');
|
|
698
|
-
if (existsSync(wellKnownDir)) {
|
|
699
|
-
copyDirectory(wellKnownDir, join(distDir, '.well-known'));
|
|
700
|
-
hostingFiles.push('.well-known/');
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
const parts = ['Assets'];
|
|
704
|
-
if (existsSync(functionsDir)) parts.push('functions');
|
|
705
|
-
if (hostingFiles.length > 0) parts.push(hostingFiles.join(', '));
|
|
706
|
-
console.log(`✅ ${parts.join(', ')} copied\n`);
|
|
707
|
-
|
|
708
|
-
// Load all global components
|
|
709
|
-
const { components, warnings, errors } = await loadComponentDirectory(projectPaths.components());
|
|
710
|
-
const globalComponents: Record<string, ComponentDefinition> = {};
|
|
711
|
-
components.forEach((value, key) => {
|
|
712
|
-
globalComponents[key] = value;
|
|
713
|
-
});
|
|
714
|
-
|
|
715
|
-
for (const warning of warnings) console.warn(`⚠️ ${warning}`);
|
|
716
|
-
for (const error of errors) console.error(`❌ ${error}`);
|
|
717
|
-
console.log(`✅ Loaded ${components.size} global component(s)\n`);
|
|
718
|
-
|
|
719
|
-
// Initialize CMS service for CMSList rendering
|
|
720
|
-
const cmsProvider = new FileSystemCMSProvider(projectPaths.templates(), projectPaths.cms());
|
|
721
|
-
const cmsService = new CMSService(cmsProvider);
|
|
722
|
-
await cmsService.initialize();
|
|
723
|
-
console.log(`✅ CMS service initialized\n`);
|
|
724
|
-
|
|
725
|
-
// Load all pages
|
|
726
|
-
const pagesDir = projectPaths.pages();
|
|
727
|
-
if (!existsSync(pagesDir)) {
|
|
728
|
-
console.error("❌ Pages directory not found!");
|
|
729
|
-
process.exit(1);
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
// Recursively collect all .json page files (supports nested folders like pages/a/b.json)
|
|
733
|
-
const pageFiles: string[] = [];
|
|
734
|
-
function scanPagesDir(dir: string, prefix: string): void {
|
|
735
|
-
const entries = readdirSync(dir, { withFileTypes: true });
|
|
736
|
-
for (const entry of entries) {
|
|
737
|
-
if (entry.isFile() && entry.name.endsWith('.json')) {
|
|
738
|
-
pageFiles.push(prefix ? `${prefix}/${entry.name}` : entry.name);
|
|
739
|
-
} else if (entry.isDirectory()) {
|
|
740
|
-
scanPagesDir(join(dir, entry.name), prefix ? `${prefix}/${entry.name}` : entry.name);
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
scanPagesDir(pagesDir, '');
|
|
745
|
-
|
|
746
|
-
if (pageFiles.length === 0) {
|
|
747
|
-
console.warn("⚠️ No pages found in ./pages directory");
|
|
748
|
-
return;
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
console.log(`📄 Found ${pageFiles.length} page(s) to build\n`);
|
|
752
|
-
|
|
753
|
-
// First pass: collect all slug mappings and page-level libraries
|
|
754
|
-
const slugMappings: SlugMap[] = [];
|
|
755
|
-
const allPageLibraries: LibrariesConfig[] = [];
|
|
756
|
-
for (const file of pageFiles) {
|
|
757
|
-
const pageName = file.replace(".json", "");
|
|
758
|
-
const basePath = mapPageNameToPath(pageName);
|
|
759
|
-
const pageContent = await loadJSONFile(join(pagesDir, file));
|
|
760
|
-
if (pageContent) {
|
|
761
|
-
try {
|
|
762
|
-
const pageData = parseJSON<JSONPage>(pageContent);
|
|
763
|
-
if (pageData.meta?.slugs) {
|
|
764
|
-
const pageId = basePath === '/' ? 'index' : basePath.substring(1);
|
|
765
|
-
slugMappings.push({ pageId, slugs: pageData.meta.slugs });
|
|
766
|
-
}
|
|
767
|
-
if (pageData.meta?.libraries) {
|
|
768
|
-
allPageLibraries.push(pageData.meta.libraries as LibrariesConfig);
|
|
769
|
-
}
|
|
770
|
-
} catch { /* ignore parse errors in first pass */ }
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
// Generate _headers from CSP config, auto-including library CDN domains
|
|
775
|
-
// Always regenerate when CSP config exists — overwrites any stale _headers file
|
|
776
|
-
// that may have been copied from the project root
|
|
777
|
-
{
|
|
778
|
-
await configService.load();
|
|
779
|
-
const cspConfig = configService.getCSP();
|
|
780
|
-
if (cspConfig && Object.keys(cspConfig).length > 0) {
|
|
781
|
-
// Collect all library sources: global + component + page-level
|
|
782
|
-
// Note: we concatenate (not mergeLibraries) because _headers applies globally —
|
|
783
|
-
// we need the union of ALL origins, regardless of per-page merge modes
|
|
784
|
-
const globalLibraries = configService.getLibraries();
|
|
785
|
-
const componentLibraries = collectComponentLibraries(globalComponents);
|
|
786
|
-
const allJs = [
|
|
787
|
-
...(globalLibraries.js || []),
|
|
788
|
-
...(componentLibraries.js || []),
|
|
789
|
-
...allPageLibraries.flatMap(p => p.js || []),
|
|
790
|
-
];
|
|
791
|
-
const allCss = [
|
|
792
|
-
...(globalLibraries.css || []),
|
|
793
|
-
...(componentLibraries.css || []),
|
|
794
|
-
...allPageLibraries.flatMap(p => p.css || []),
|
|
795
|
-
];
|
|
796
|
-
const allLibs: LibrariesConfig = { js: allJs, css: allCss };
|
|
797
|
-
|
|
798
|
-
// Filter out CSS libs disabled for build before extracting CSP origins
|
|
799
|
-
const buildLibs = filterLibrariesByContext(allLibs, 'build');
|
|
800
|
-
|
|
801
|
-
// Extract CDN origins from all library URLs
|
|
802
|
-
const { scriptOrigins, styleOrigins } = extractLibraryOrigins(buildLibs);
|
|
803
|
-
|
|
804
|
-
const extraScripts = [
|
|
805
|
-
...(cspConfig.scriptSrc || []),
|
|
806
|
-
...Array.from(scriptOrigins),
|
|
807
|
-
].join(' ');
|
|
808
|
-
const extraStyles = [
|
|
809
|
-
...(cspConfig.styleSrc || []),
|
|
810
|
-
...Array.from(styleOrigins),
|
|
811
|
-
].join(' ');
|
|
812
|
-
const extraConnect = cspConfig.connectSrc?.join(' ') || '';
|
|
813
|
-
const extraFrames = cspConfig.frameSrc?.join(' ') || '';
|
|
814
|
-
const extraFonts = cspConfig.fontSrc?.join(' ') || '';
|
|
815
|
-
const extraImgs = cspConfig.imgSrc?.join(' ') || '';
|
|
816
|
-
|
|
817
|
-
// Production-built pages have NO executable inline scripts: page config,
|
|
818
|
-
// component JS, form handler, and MenoFilter all live in external
|
|
819
|
-
// /_scripts/{hash}.js files (returnSeparateJS: true), and the Meno
|
|
820
|
-
// badge's hover effect is pure CSS. Only `<script type="application/json">`
|
|
821
|
-
// remains inline (used as a data island for MenoFilter), which CSP
|
|
822
|
-
// ignores because it has no executable code. So we can drop
|
|
823
|
-
// `'unsafe-inline'` entirely from script-src in the generated _headers.
|
|
824
|
-
const cspDirectives = [
|
|
825
|
-
"default-src 'self'",
|
|
826
|
-
`script-src 'self' https://f.vimeocdn.com https://player.vimeo.com https://www.youtube.com https://s.ytimg.com ${extraScripts}`.trim(),
|
|
827
|
-
`style-src 'self' 'unsafe-inline' https://f.vimeocdn.com ${extraStyles}`.trim(),
|
|
828
|
-
`img-src 'self' data: https: ${extraImgs}`.trim(),
|
|
829
|
-
`connect-src 'self' https://vimeo.com https://*.vimeocdn.com ${extraConnect}`.trim(),
|
|
830
|
-
`frame-src https://player.vimeo.com https://vimeo.com https://www.youtube.com https://www.youtube-nocookie.com ${extraFrames}`.trim(),
|
|
831
|
-
`font-src 'self' data: ${extraFonts}`.trim(),
|
|
832
|
-
"media-src 'self' https: blob:"
|
|
833
|
-
].join('; ');
|
|
834
|
-
|
|
835
|
-
const headersContent = `/*\n Content-Security-Policy: ${cspDirectives}\n`;
|
|
836
|
-
writeFileSync(join(distDir, '_headers'), headersContent);
|
|
837
|
-
console.log(`✅ Generated _headers with CSP (auto-included ${scriptOrigins.size + styleOrigins.size} library origin(s))\n`);
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
let successCount = 0;
|
|
842
|
-
let errorCount = 0;
|
|
843
|
-
|
|
844
|
-
// Build each page for each locale
|
|
845
|
-
for (const file of pageFiles) {
|
|
846
|
-
const pageName = file.replace(".json", "");
|
|
847
|
-
const basePath = mapPageNameToPath(pageName);
|
|
848
|
-
const pageContent = await loadJSONFile(join(pagesDir, file));
|
|
849
|
-
|
|
850
|
-
if (!pageContent) {
|
|
851
|
-
console.warn(`⚠️ Skipping ${basePath} (empty file)`);
|
|
852
|
-
errorCount++;
|
|
853
|
-
continue;
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
try {
|
|
857
|
-
const pageData = parseJSON<JSONPage>(pageContent);
|
|
858
|
-
|
|
859
|
-
// Skip draft pages in production (not in dev mode)
|
|
860
|
-
const isDevBuild = process.env.MENO_DEV_BUILD === 'true';
|
|
861
|
-
if (pageData.meta?.draft === true && !isDevBuild) {
|
|
862
|
-
console.log(`⏭️ Skipping draft: ${basePath}`);
|
|
863
|
-
continue;
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
// Get translated slugs from page meta (if available)
|
|
867
|
-
const slugs = pageData.meta?.slugs;
|
|
868
|
-
|
|
869
|
-
// Generate HTML for each locale
|
|
870
|
-
for (const localeConfig of i18nConfig.locales) {
|
|
871
|
-
const locale = localeConfig.code;
|
|
872
|
-
const baseUrl = siteUrl || "";
|
|
873
|
-
|
|
874
|
-
// Build the URL path that will be used for this locale
|
|
875
|
-
const urlPath = getDisplayPath(basePath, locale, i18nConfig.defaultLocale, slugs);
|
|
876
|
-
|
|
877
|
-
// Generate HTML with JS returned separately (CSP-compliant)
|
|
878
|
-
const result = await generateSSRHTML({
|
|
879
|
-
pageData,
|
|
880
|
-
globalComponents,
|
|
881
|
-
pagePath: urlPath,
|
|
882
|
-
baseUrl,
|
|
883
|
-
useBuiltBundle: true,
|
|
884
|
-
locale,
|
|
885
|
-
slugMappings,
|
|
886
|
-
cmsService,
|
|
887
|
-
returnSeparateJS: true,
|
|
888
|
-
pageLibraries: pageData.meta?.libraries,
|
|
889
|
-
pageCustomCode: pageData.meta?.customCode,
|
|
890
|
-
isProductionBuild: true,
|
|
891
|
-
}) as SSRHTMLResult;
|
|
892
|
-
|
|
893
|
-
// If there's JavaScript, write to external file and update HTML
|
|
894
|
-
let finalHtml = result.html;
|
|
895
|
-
if (result.javascript) {
|
|
896
|
-
const scriptPath = await getScriptPath(result.javascript, distDir);
|
|
897
|
-
// Insert script reference before </body>
|
|
898
|
-
finalHtml = finalHtml.replace('</body>', ` <script src="${scriptPath}" defer></script>\n</body>`);
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
// Inject A/B tracking script if experiments are running
|
|
902
|
-
if (trackingScript) {
|
|
903
|
-
finalHtml = injectTrackingScript(finalHtml, trackingScript);
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
// Determine locale-specific output path with translated slug
|
|
907
|
-
const outputPath = getLocalizedOutputPath(basePath, locale, i18nConfig.defaultLocale, distDir, slugs);
|
|
908
|
-
|
|
909
|
-
// Ensure directory exists
|
|
910
|
-
const outputDir = outputPath.substring(0, outputPath.lastIndexOf("/"));
|
|
911
|
-
if (outputDir && !existsSync(outputDir)) {
|
|
912
|
-
mkdirSync(outputDir, { recursive: true });
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
// Debug: show end of HTML
|
|
916
|
-
if (urlPath === '/') {
|
|
917
|
-
console.log('[DEBUG] Last 500 chars of HTML:', finalHtml.slice(-500));
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
await writeFile(outputPath, finalHtml, "utf-8");
|
|
921
|
-
|
|
922
|
-
generatedUrls.add(urlPath);
|
|
923
|
-
console.log(`✅ Built: ${urlPath} → ${outputPath}`);
|
|
924
|
-
successCount++;
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
} catch (error: any) {
|
|
928
|
-
// Capture full error with as much detail as possible
|
|
929
|
-
let errorMessage: string;
|
|
930
|
-
|
|
931
|
-
if (error instanceof Error) {
|
|
932
|
-
// Check for AggregateError (multiple errors)
|
|
933
|
-
if ('errors' in error && Array.isArray(error.errors)) {
|
|
934
|
-
errorMessage = error.errors.map((e: any) => e.stack || e.message || String(e)).join('\n\n');
|
|
935
|
-
}
|
|
936
|
-
// Check for cause chain
|
|
937
|
-
else if (error.cause) {
|
|
938
|
-
const causeMsg = error.cause instanceof Error
|
|
939
|
-
? (error.cause.stack || error.cause.message)
|
|
940
|
-
: String(error.cause);
|
|
941
|
-
errorMessage = `${error.stack || error.message}\n\nCaused by:\n${causeMsg}`;
|
|
942
|
-
}
|
|
943
|
-
// Bun's BuildMessage has logs array
|
|
944
|
-
else if ('logs' in error && Array.isArray(error.logs)) {
|
|
945
|
-
errorMessage = error.logs.map(formatBunLog).join('\n\n');
|
|
946
|
-
}
|
|
947
|
-
else {
|
|
948
|
-
errorMessage = error.stack || error.message;
|
|
949
|
-
}
|
|
950
|
-
} else if (typeof error === 'object' && error !== null) {
|
|
951
|
-
// Bun BuildOutput has logs
|
|
952
|
-
if (error.logs && Array.isArray(error.logs)) {
|
|
953
|
-
errorMessage = error.logs.map(formatBunLog).join('\n\n');
|
|
954
|
-
} else {
|
|
955
|
-
errorMessage = String(error);
|
|
956
|
-
}
|
|
957
|
-
} else {
|
|
958
|
-
errorMessage = String(error);
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
// If we still just have "Bundle failed", try to get more from inspect
|
|
962
|
-
if (errorMessage === 'Bundle failed' || errorMessage.includes('Bundle failed\n')) {
|
|
963
|
-
try {
|
|
964
|
-
const inspected = inspect(error);
|
|
965
|
-
if (inspected && inspected !== '[object Object]' && inspected.length > errorMessage.length) {
|
|
966
|
-
errorMessage = inspected;
|
|
967
|
-
}
|
|
968
|
-
} catch {}
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
console.error(`❌ Error building ${basePath}:`, error);
|
|
972
|
-
buildErrors.push({
|
|
973
|
-
file: `pages/${file}`,
|
|
974
|
-
message: errorMessage,
|
|
975
|
-
type: errorMessage.includes('minification') || errorMessage.includes('minify') ? 'minify' : 'render',
|
|
976
|
-
});
|
|
977
|
-
errorCount++;
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
// Build CMS templates from root templates/ directory
|
|
982
|
-
const templatesDir = projectPaths.templates();
|
|
983
|
-
const staticCollections = new Map<string, ClientDataCollection>();
|
|
984
|
-
const cmsResult = await buildCMSTemplates(
|
|
985
|
-
templatesDir,
|
|
986
|
-
globalComponents,
|
|
987
|
-
i18nConfig,
|
|
988
|
-
slugMappings,
|
|
989
|
-
distDir,
|
|
990
|
-
cmsService,
|
|
991
|
-
generatedUrls,
|
|
992
|
-
staticCollections,
|
|
993
|
-
siteUrl,
|
|
994
|
-
trackingScript
|
|
995
|
-
);
|
|
996
|
-
successCount += cmsResult.success;
|
|
997
|
-
errorCount += cmsResult.errors;
|
|
998
|
-
|
|
999
|
-
// Generate static data files for collections with 'static' strategy
|
|
1000
|
-
await generateStaticDataFiles(staticCollections, distDir);
|
|
1001
|
-
|
|
1002
|
-
// Generate A/B testing functions if experiments are running
|
|
1003
|
-
if (runningExperiments.length > 0) {
|
|
1004
|
-
await generateABFunctions(runningExperiments, distDir);
|
|
1005
|
-
console.log(`\n🧪 A/B testing functions generated (middleware, tracker, results API)`);
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
// Generate SEO files (robots.txt and sitemap.xml)
|
|
1009
|
-
if (siteUrl) {
|
|
1010
|
-
await generateRobotsTxt(siteUrl, distDir);
|
|
1011
|
-
await generateSitemap([...generatedUrls], siteUrl, distDir);
|
|
1012
|
-
console.log(`\n🔍 SEO files generated (robots.txt, sitemap.xml)`);
|
|
1013
|
-
} else {
|
|
1014
|
-
console.warn(`\n⚠️ Skipping SEO files: siteUrl not configured in project.config.json`);
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
console.log("\n" + "=".repeat(50));
|
|
1018
|
-
console.log(`✨ Build complete!`);
|
|
1019
|
-
console.log(` ✅ Success: ${successCount}`);
|
|
1020
|
-
if (errorCount > 0) {
|
|
1021
|
-
console.log(` ❌ Errors: ${errorCount}`);
|
|
1022
|
-
}
|
|
1023
|
-
console.log(`\n📦 Production files in /dist:`);
|
|
1024
|
-
console.log(` - *.html (Static pages with vanilla JS)`);
|
|
1025
|
-
console.log(` - fonts/ (Custom fonts)`);
|
|
1026
|
-
console.log(` - images/ (Image assets)`);
|
|
1027
|
-
console.log(` - icons/ (Favicon and icons)`);
|
|
1028
|
-
if (staticCollections.size > 0) {
|
|
1029
|
-
console.log(` - data/ (CMS collection data for client filtering)`);
|
|
1030
|
-
}
|
|
1031
|
-
if (existsSync(functionsDir)) {
|
|
1032
|
-
console.log(` - functions/ (Cloudflare Pages Functions)`);
|
|
1033
|
-
}
|
|
1034
|
-
if (runningExperiments.length > 0) {
|
|
1035
|
-
console.log(` - A/B testing (middleware + tracking injected)`);
|
|
1036
|
-
}
|
|
1037
|
-
if (siteUrl) {
|
|
1038
|
-
console.log(` - robots.txt, sitemap.xml (SEO)`);
|
|
1039
|
-
}
|
|
1040
|
-
console.log(` - No React, no client-router ✓`);
|
|
1041
|
-
|
|
1042
|
-
// Collect component JS validation errors
|
|
1043
|
-
const jsErrors = getJSValidationErrors();
|
|
1044
|
-
for (const { component, error } of jsErrors) {
|
|
1045
|
-
buildErrors.push({
|
|
1046
|
-
file: `components/${component}`,
|
|
1047
|
-
message: error,
|
|
1048
|
-
type: 'minify',
|
|
1049
|
-
});
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
// Write build errors to _errors.json for static server overlay
|
|
1053
|
-
if (buildErrors.length > 0) {
|
|
1054
|
-
// Deduplicate errors by message (same error may occur on multiple pages)
|
|
1055
|
-
const seenMessages = new Set<string>();
|
|
1056
|
-
const uniqueErrors = buildErrors.filter(err => {
|
|
1057
|
-
if (seenMessages.has(err.message)) return false;
|
|
1058
|
-
seenMessages.add(err.message);
|
|
1059
|
-
return true;
|
|
1060
|
-
});
|
|
1061
|
-
|
|
1062
|
-
const errorsData: BuildErrorsData = {
|
|
1063
|
-
errors: uniqueErrors,
|
|
1064
|
-
timestamp: Date.now(),
|
|
1065
|
-
};
|
|
1066
|
-
await writeFile(errorsPath, JSON.stringify(errorsData, null, 2), 'utf-8');
|
|
1067
|
-
const countMsg = uniqueErrors.length === buildErrors.length
|
|
1068
|
-
? `${uniqueErrors.length} error${uniqueErrors.length === 1 ? '' : 's'}`
|
|
1069
|
-
: `${uniqueErrors.length} unique error${uniqueErrors.length === 1 ? '' : 's'} (affected ${buildErrors.length} files)`;
|
|
1070
|
-
console.log(`\n⚠️ Build errors written to dist/_errors.json (${countMsg})`);
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
|