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
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Leveled logger — the single console abstraction for Meno runtime/server code.
|
|
3
|
+
*
|
|
4
|
+
* Why: the codebase had hundreds of raw `console.*` calls with no way to gate
|
|
5
|
+
* verbosity in the field (a packaged desktop app emits everything, with no level
|
|
6
|
+
* control and no telemetry path). This module adds:
|
|
7
|
+
* - levels (silent < error < warn < info < debug), gated by `MENO_LOG_LEVEL`
|
|
8
|
+
* (or `NODE_ENV`: `production` → warn, otherwise → debug);
|
|
9
|
+
* - per-scope loggers (`createLogger('AstroProvider')`) that prefix messages;
|
|
10
|
+
* - a pluggable sink (`setLogSink`) so the Electron main process / telemetry can
|
|
11
|
+
* capture logs and crashes instead of (or in addition to) the console.
|
|
12
|
+
*
|
|
13
|
+
* Isomorphic: works in Node and the browser. Error reporting still flows through
|
|
14
|
+
* `errorLogger` (`logRuntimeError`/`logNetworkError`); this is for general
|
|
15
|
+
* debug/info/warn/error console output.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export type LogLevel = 'silent' | 'error' | 'warn' | 'info' | 'debug';
|
|
19
|
+
|
|
20
|
+
const LEVEL_RANK: Record<LogLevel, number> = { silent: 0, error: 1, warn: 2, info: 3, debug: 4 };
|
|
21
|
+
|
|
22
|
+
/** A record forwarded to the sink for every emitted log line. */
|
|
23
|
+
export interface LogRecord {
|
|
24
|
+
level: Exclude<LogLevel, 'silent'>;
|
|
25
|
+
scope: string;
|
|
26
|
+
message: string;
|
|
27
|
+
args: unknown[];
|
|
28
|
+
timestamp: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type LogSink = (record: LogRecord) => void;
|
|
32
|
+
|
|
33
|
+
let sink: LogSink | null = null;
|
|
34
|
+
|
|
35
|
+
/** Install a sink (telemetry / Electron capture). Pass `null` to remove. */
|
|
36
|
+
export function setLogSink(next: LogSink | null): void {
|
|
37
|
+
sink = next;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function defaultLevel(): LogLevel {
|
|
41
|
+
const env = typeof process !== 'undefined' ? process.env : undefined;
|
|
42
|
+
const explicit = env?.MENO_LOG_LEVEL?.toLowerCase();
|
|
43
|
+
if (explicit && explicit in LEVEL_RANK) return explicit as LogLevel;
|
|
44
|
+
return env?.NODE_ENV === 'production' ? 'warn' : 'debug';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let currentLevel: LogLevel = defaultLevel();
|
|
48
|
+
|
|
49
|
+
/** Override the active level at runtime (e.g. a debug toggle in the app). */
|
|
50
|
+
export function setLogLevel(level: LogLevel): void {
|
|
51
|
+
currentLevel = level;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The active level (resolved from env at module load unless overridden). */
|
|
55
|
+
export function getLogLevel(): LogLevel {
|
|
56
|
+
return currentLevel;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const CONSOLE: Record<Exclude<LogLevel, 'silent'>, (...a: unknown[]) => void> = {
|
|
60
|
+
error: (...a) => console.error(...a),
|
|
61
|
+
warn: (...a) => console.warn(...a),
|
|
62
|
+
info: (...a) => console.info(...a),
|
|
63
|
+
debug: (...a) => console.debug(...a),
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
function emit(level: Exclude<LogLevel, 'silent'>, scope: string, message: string, args: unknown[]): void {
|
|
67
|
+
if (LEVEL_RANK[level] > LEVEL_RANK[currentLevel]) return;
|
|
68
|
+
const prefix = scope ? `[${scope}]` : '';
|
|
69
|
+
CONSOLE[level](`${prefix} ${message}`.trimStart(), ...args);
|
|
70
|
+
sink?.({ level, scope, message, args, timestamp: Date.now() });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface Logger {
|
|
74
|
+
debug(message: string, ...args: unknown[]): void;
|
|
75
|
+
info(message: string, ...args: unknown[]): void;
|
|
76
|
+
warn(message: string, ...args: unknown[]): void;
|
|
77
|
+
error(message: string, ...args: unknown[]): void;
|
|
78
|
+
/** A child logger nested under this scope (`parent:child`). */
|
|
79
|
+
child(scope: string): Logger;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Create a scoped logger. `scope` is prefixed to every line and forwarded to the sink. */
|
|
83
|
+
export function createLogger(scope = ''): Logger {
|
|
84
|
+
return {
|
|
85
|
+
debug: (message, ...args) => emit('debug', scope, message, args),
|
|
86
|
+
info: (message, ...args) => emit('info', scope, message, args),
|
|
87
|
+
warn: (message, ...args) => emit('warn', scope, message, args),
|
|
88
|
+
error: (message, ...args) => emit('error', scope, message, args),
|
|
89
|
+
child: (childScope) => createLogger(scope ? `${scope}:${childScope}` : childScope),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** A default unscoped logger for ad-hoc use. Prefer a scoped `createLogger('Area')`. */
|
|
94
|
+
export const logger = createLogger();
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown → HTML for the `markdown` node type.
|
|
3
|
+
*
|
|
4
|
+
* A Meno `markdown` node stores its body as a verbatim Markdown string (`node.source`).
|
|
5
|
+
* meno-core renders it to HTML here for both the editor canvas (client renderer) and SSR
|
|
6
|
+
* (design-canvas preview / JSON-format SSR). The meno-astro build runtime has its OWN
|
|
7
|
+
* `renderMarkdown` (packages/astro/lib/runtime/markdown.ts) — kept separate so a new
|
|
8
|
+
* meno-core export never has to reach the published Astro play runtime (see the
|
|
9
|
+
* runtime-uses-npm-meno-core constraint). Both wrap the same `markdown-it` config, so the
|
|
10
|
+
* canvas preview and the real Astro build render identically.
|
|
11
|
+
*
|
|
12
|
+
* `html: true` lets authors embed raw HTML in Markdown (parity with typical Markdown +
|
|
13
|
+
* the embed node, which also passes author HTML through). Content is author-trusted (the
|
|
14
|
+
* editor is the source of truth), like the `customCode` / `embed` paths.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import MarkdownIt from 'markdown-it';
|
|
18
|
+
|
|
19
|
+
const md = new MarkdownIt({ html: true, linkify: true, breaks: false });
|
|
20
|
+
|
|
21
|
+
/** Render a Markdown source string to an HTML string. Empty/non-string → "". */
|
|
22
|
+
export function renderMarkdown(source: unknown): string {
|
|
23
|
+
if (typeof source !== 'string' || source.trim() === '') return '';
|
|
24
|
+
return md.render(source);
|
|
25
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { describe, test, expect } from 'bun:test';
|
|
2
|
+
import {
|
|
3
|
+
netlifyLocale404Redirects,
|
|
4
|
+
syncNetlifyLocale404Block,
|
|
5
|
+
NETLIFY_LOCALE_404_BEGIN,
|
|
6
|
+
NETLIFY_LOCALE_404_END,
|
|
7
|
+
} from './netlifyLocale404';
|
|
8
|
+
import type { I18nConfig } from './types';
|
|
9
|
+
|
|
10
|
+
const MULTI: I18nConfig = {
|
|
11
|
+
defaultLocale: 'en',
|
|
12
|
+
locales: [
|
|
13
|
+
{ code: 'en', name: 'EN', nativeName: 'EN', langTag: 'en-US' },
|
|
14
|
+
{ code: 'pl', name: 'PL', nativeName: 'PL', langTag: 'pl-PL' },
|
|
15
|
+
{ code: 'de', name: 'DE', nativeName: 'DE', langTag: 'de-DE' },
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const EN_PL: I18nConfig = {
|
|
20
|
+
defaultLocale: 'en',
|
|
21
|
+
locales: [MULTI.locales[0]!, MULTI.locales[1]!],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const SINGLE: I18nConfig = { defaultLocale: 'en', locales: [MULTI.locales[0]!] };
|
|
25
|
+
|
|
26
|
+
const BASE_TOML = `# Scaffold generated by Meno.
|
|
27
|
+
[build]
|
|
28
|
+
command = "npm run build"
|
|
29
|
+
publish = "dist"
|
|
30
|
+
|
|
31
|
+
[build.environment]
|
|
32
|
+
NODE_VERSION = "22"
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
describe('netlifyLocale404Redirects (pure generator)', () => {
|
|
36
|
+
test('one non-shadowing 404 rewrite per non-default locale', () => {
|
|
37
|
+
const toml = netlifyLocale404Redirects(MULTI);
|
|
38
|
+
for (const code of ['pl', 'de']) {
|
|
39
|
+
expect(toml).toContain(`from = "/${code}/*"`);
|
|
40
|
+
expect(toml).toContain(`to = "/${code}/404/index.html"`);
|
|
41
|
+
}
|
|
42
|
+
// status 404 (a rewrite that keeps the URL and the error status — Netlify's
|
|
43
|
+
// documented localized-404 pattern), never a shadowing force rule.
|
|
44
|
+
expect(toml.match(/status = 404/g)).toHaveLength(2);
|
|
45
|
+
expect(toml).not.toContain('force');
|
|
46
|
+
// The default locale is the root catch-all 404.html — no rule.
|
|
47
|
+
expect(toml).not.toContain('from = "/en/*"');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('single-locale config → "" (no redirects block at all)', () => {
|
|
51
|
+
expect(netlifyLocale404Redirects(SINGLE)).toBe('');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('a malformed locale code is skipped (cannot corrupt the TOML)', () => {
|
|
55
|
+
const sketchy: I18nConfig = {
|
|
56
|
+
defaultLocale: 'en',
|
|
57
|
+
locales: [
|
|
58
|
+
MULTI.locales[0]!,
|
|
59
|
+
{ code: 'pl', name: 'PL', nativeName: 'PL', langTag: 'pl-PL' },
|
|
60
|
+
{ code: 'we"ird/../x', name: 'X', nativeName: 'X', langTag: 'x' },
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
const toml = netlifyLocale404Redirects(sketchy);
|
|
64
|
+
expect(toml).toContain('from = "/pl/*"');
|
|
65
|
+
expect(toml).not.toContain('we"ird');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('only-malformed non-default codes → "" (no orphan header)', () => {
|
|
69
|
+
const allBad: I18nConfig = {
|
|
70
|
+
defaultLocale: 'en',
|
|
71
|
+
locales: [MULTI.locales[0]!, { code: 'bad code', name: 'X', nativeName: 'X', langTag: 'x' }],
|
|
72
|
+
};
|
|
73
|
+
expect(netlifyLocale404Redirects(allBad)).toBe('');
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('syncNetlifyLocale404Block', () => {
|
|
78
|
+
test('inserts the managed block (multi-locale + 404 page)', () => {
|
|
79
|
+
const out = syncNetlifyLocale404Block(BASE_TOML, EN_PL, true);
|
|
80
|
+
// The original content is preserved byte-identically as a prefix.
|
|
81
|
+
expect(out.startsWith(BASE_TOML)).toBe(true);
|
|
82
|
+
expect(out).toContain(NETLIFY_LOCALE_404_BEGIN);
|
|
83
|
+
expect(out).toContain(NETLIFY_LOCALE_404_END);
|
|
84
|
+
expect(out).toContain('from = "/pl/*"');
|
|
85
|
+
expect(out).toContain('to = "/pl/404/index.html"');
|
|
86
|
+
expect(out).toContain('status = 404');
|
|
87
|
+
expect(out).not.toContain('from = "/en/*"');
|
|
88
|
+
// The block is line-shaped: BEGIN at line start, END line last, trailing newline.
|
|
89
|
+
expect(out).toContain(`\n${NETLIFY_LOCALE_404_BEGIN}\n`);
|
|
90
|
+
expect(out.endsWith(`${NETLIFY_LOCALE_404_END}\n`)).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('adds a trailing newline before appending when the content lacks one', () => {
|
|
94
|
+
const noTrailing = '[build]\n publish = "dist"';
|
|
95
|
+
const out = syncNetlifyLocale404Block(noTrailing, EN_PL, true);
|
|
96
|
+
expect(out.startsWith(`${noTrailing}\n${NETLIFY_LOCALE_404_BEGIN}\n`)).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('empty content + applicable rules → just the block', () => {
|
|
100
|
+
const out = syncNetlifyLocale404Block('', EN_PL, true);
|
|
101
|
+
expect(out.startsWith(NETLIFY_LOCALE_404_BEGIN)).toBe(true);
|
|
102
|
+
expect(out.endsWith(`${NETLIFY_LOCALE_404_END}\n`)).toBe(true);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('no markers + nothing to add → returned unchanged (single-locale)', () => {
|
|
106
|
+
expect(syncNetlifyLocale404Block(BASE_TOML, SINGLE, true)).toBe(BASE_TOML);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('no markers + nothing to add → returned unchanged (no 404 page)', () => {
|
|
110
|
+
expect(syncNetlifyLocale404Block(BASE_TOML, EN_PL, false)).toBe(BASE_TOML);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('idempotent: re-syncing the same state is a no-op', () => {
|
|
114
|
+
const once = syncNetlifyLocale404Block(BASE_TOML, EN_PL, true);
|
|
115
|
+
const twice = syncNetlifyLocale404Block(once, EN_PL, true);
|
|
116
|
+
expect(twice).toBe(once);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('replaces an existing block in place when the locale set changes', () => {
|
|
120
|
+
const withPl = syncNetlifyLocale404Block(BASE_TOML, EN_PL, true);
|
|
121
|
+
const withPlDe = syncNetlifyLocale404Block(withPl, MULTI, true);
|
|
122
|
+
expect(withPlDe).toContain('from = "/pl/*"');
|
|
123
|
+
expect(withPlDe).toContain('from = "/de/*"');
|
|
124
|
+
// Exactly one block (replaced, not appended).
|
|
125
|
+
expect(withPlDe.split(NETLIFY_LOCALE_404_BEGIN)).toHaveLength(2);
|
|
126
|
+
// Content outside the markers is byte-identical.
|
|
127
|
+
expect(withPlDe.startsWith(BASE_TOML)).toBe(true);
|
|
128
|
+
// Replacing pl+de straight away gives the same result (position-stable).
|
|
129
|
+
expect(withPlDe).toBe(syncNetlifyLocale404Block(BASE_TOML, MULTI, true));
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('removes the whole block when the project goes single-locale', () => {
|
|
133
|
+
const withBlock = syncNetlifyLocale404Block(BASE_TOML, EN_PL, true);
|
|
134
|
+
const removed = syncNetlifyLocale404Block(withBlock, SINGLE, true);
|
|
135
|
+
expect(removed).toBe(BASE_TOML); // perfect round-trip
|
|
136
|
+
expect(removed).not.toContain('[[redirects]]');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('removes the whole block when the 404 page disappears', () => {
|
|
140
|
+
const withBlock = syncNetlifyLocale404Block(BASE_TOML, EN_PL, true);
|
|
141
|
+
const removed = syncNetlifyLocale404Block(withBlock, EN_PL, false);
|
|
142
|
+
expect(removed).toBe(BASE_TOML);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('user content around the block is preserved byte-identically', () => {
|
|
146
|
+
const userAbove = '# my deploy config\n[build]\n command = "my build"\n';
|
|
147
|
+
const userBelow =
|
|
148
|
+
'\n# user redirect kept verbatim\n[[redirects]]\n from = "/old"\n to = "/new"\n status = 301\n';
|
|
149
|
+
const withBlock = syncNetlifyLocale404Block(userAbove, EN_PL, true) + userBelow;
|
|
150
|
+
|
|
151
|
+
// Replace: outside-the-markers bytes survive.
|
|
152
|
+
const replaced = syncNetlifyLocale404Block(withBlock, MULTI, true);
|
|
153
|
+
expect(replaced.startsWith(userAbove)).toBe(true);
|
|
154
|
+
expect(replaced.endsWith(userBelow)).toBe(true);
|
|
155
|
+
expect(replaced).toContain('from = "/de/*"');
|
|
156
|
+
|
|
157
|
+
// Remove: only the managed lines disappear.
|
|
158
|
+
const removed = syncNetlifyLocale404Block(withBlock, SINGLE, true);
|
|
159
|
+
expect(removed).toBe(userAbove + userBelow);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('a corrupted block (BEGIN without END) is left untouched', () => {
|
|
163
|
+
const corrupt = `${BASE_TOML}${NETLIFY_LOCALE_404_BEGIN}\n# half a block, END deleted by hand\n`;
|
|
164
|
+
expect(syncNetlifyLocale404Block(corrupt, MULTI, true)).toBe(corrupt);
|
|
165
|
+
expect(syncNetlifyLocale404Block(corrupt, SINGLE, true)).toBe(corrupt);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('weird locale codes cannot reach the managed block', () => {
|
|
169
|
+
const sketchy: I18nConfig = {
|
|
170
|
+
defaultLocale: 'en',
|
|
171
|
+
locales: [MULTI.locales[0]!, { code: '../escape', name: 'X', nativeName: 'X', langTag: 'x' }],
|
|
172
|
+
};
|
|
173
|
+
// Only-malformed codes → no rules → nothing inserted.
|
|
174
|
+
expect(syncNetlifyLocale404Block(BASE_TOML, sketchy, true)).toBe(BASE_TOML);
|
|
175
|
+
// And an existing block is removed rather than filled with garbage.
|
|
176
|
+
const withBlock = syncNetlifyLocale404Block(BASE_TOML, EN_PL, true);
|
|
177
|
+
expect(syncNetlifyLocale404Block(withBlock, sketchy, true)).toBe(BASE_TOML);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locale-scoped Netlify 404 rules + the managed netlify.toml block that carries them.
|
|
3
|
+
*
|
|
4
|
+
* Why: a multi-locale static build emits a localized 404 per non-default locale
|
|
5
|
+
* (`dist/pl/404/index.html`), but a static host serves exactly ONE catch-all 404
|
|
6
|
+
* page unless told otherwise — so `/pl/<missing>` would get the default-locale
|
|
7
|
+
* `404.html`. These rules scope the 404 per locale prefix.
|
|
8
|
+
*
|
|
9
|
+
* Shared by every netlify.toml producer — meno-astro's `convertProject` scaffold,
|
|
10
|
+
* the JSON→Astro export (`build-astro.ts`), the CLI new-project scaffold — and by
|
|
11
|
+
* the studio save-config route, which re-syncs the managed block when the locale
|
|
12
|
+
* set changes AFTER the file was first written (so a project that adds a locale
|
|
13
|
+
* later still gets its rules). It lives in meno-core because the dependency
|
|
14
|
+
* direction is astro→core: meno-core must never import from meno-astro.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { I18nConfig } from './types';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* First line of the managed block. Everything between the markers (inclusive) is
|
|
21
|
+
* generated and may be rewritten wholesale by `syncNetlifyLocale404Block`; user
|
|
22
|
+
* content outside the markers is never touched.
|
|
23
|
+
*/
|
|
24
|
+
export const NETLIFY_LOCALE_404_BEGIN = '# BEGIN meno locale-404 redirects (generated — do not edit between markers)';
|
|
25
|
+
|
|
26
|
+
/** Last line of the managed block. */
|
|
27
|
+
export const NETLIFY_LOCALE_404_END = '# END meno locale-404 redirects';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Locale-scoped 404 rules for a netlify.toml — one `[[redirects]]` block per
|
|
31
|
+
* non-default locale (`''` for single-locale configs):
|
|
32
|
+
*
|
|
33
|
+
* [[redirects]]
|
|
34
|
+
* from = "/pl/*"
|
|
35
|
+
* to = "/pl/404/index.html"
|
|
36
|
+
* status = 404
|
|
37
|
+
*
|
|
38
|
+
* Netlify semantics (verified against Netlify's "redirect options" docs, the
|
|
39
|
+
* documented "localized custom 404" pattern): a redirect with `status = 404` and no
|
|
40
|
+
* `force` is a NON-SHADOWING rewrite — a request that matches an existing file is
|
|
41
|
+
* served normally (real `/pl/...` pages always win), and only an unmatched `/pl/*`
|
|
42
|
+
* URL serves the `to` file's CONTENT with an HTTP 404 status (the URL stays put; no
|
|
43
|
+
* 3xx). Everything the rules don't cover falls through to Netlify's root convention:
|
|
44
|
+
* a top-level `404.html` (which Astro builds from `src/pages/404.astro`) is served
|
|
45
|
+
* automatically for any other unmatched URL — no explicit catch-all rule needed.
|
|
46
|
+
*
|
|
47
|
+
* Locale codes are sanity-checked (`/^[A-Za-z0-9_-]+$/`): a malformed code would
|
|
48
|
+
* corrupt the TOML / redirect syntax, and such a locale cannot produce a route anyway.
|
|
49
|
+
*
|
|
50
|
+
* Pure (config in, TOML fragment out) so it is directly unit-testable; callers own
|
|
51
|
+
* the existence gates (404 page present, netlify.toml ownership). Most callers
|
|
52
|
+
* should go through `syncNetlifyLocale404Block`, which wraps the rules in the
|
|
53
|
+
* managed marker block so later config saves can update them in place.
|
|
54
|
+
*/
|
|
55
|
+
export function netlifyLocale404Redirects(config: I18nConfig): string {
|
|
56
|
+
if (!config.locales || config.locales.length <= 1) return '';
|
|
57
|
+
const codes = config.locales
|
|
58
|
+
.map((l) => l.code)
|
|
59
|
+
.filter((code) => code !== config.defaultLocale && /^[A-Za-z0-9_-]+$/.test(code));
|
|
60
|
+
if (codes.length === 0) return '';
|
|
61
|
+
const header =
|
|
62
|
+
"\n# Locale-scoped 404s — an unmatched /<locale>/* URL serves that locale's own\n" +
|
|
63
|
+
'# prerendered 404 page WITH a 404 status (non-shadowing: real pages still win).\n' +
|
|
64
|
+
'# Unmatched URLs outside these prefixes fall through to the default /404.html.\n';
|
|
65
|
+
const rules = codes
|
|
66
|
+
.map((code) => `[[redirects]]\n from = "/${code}/*"\n to = "/${code}/404/index.html"\n status = 404\n`)
|
|
67
|
+
.join('\n');
|
|
68
|
+
return header + rules;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Insert / refresh / remove the managed locale-404 block in netlify.toml content.
|
|
73
|
+
* Pure (string in, string out) — callers own reading/writing the file.
|
|
74
|
+
*
|
|
75
|
+
* - Rules applicable (multi-locale config AND `has404`) + no block yet → the block
|
|
76
|
+
* is APPENDED (a trailing newline is added to the existing content first if it
|
|
77
|
+
* lacks one).
|
|
78
|
+
* - Rules applicable + block present → the block's contents are REPLACED in place
|
|
79
|
+
* (idempotent: re-syncing identical state is a no-op).
|
|
80
|
+
* - Rules no longer applicable (single-locale, or no 404 page) + block present →
|
|
81
|
+
* the whole block is REMOVED; everything outside the markers stays byte-identical.
|
|
82
|
+
* - Nothing applicable + no block → the content is returned unchanged.
|
|
83
|
+
*
|
|
84
|
+
* A corrupted block (BEGIN marker without its END) is left untouched — rewriting up
|
|
85
|
+
* to an unknown boundary could eat user content, and a hand-damaged block is the
|
|
86
|
+
* user taking ownership.
|
|
87
|
+
*/
|
|
88
|
+
export function syncNetlifyLocale404Block(content: string, config: I18nConfig, has404: boolean): string {
|
|
89
|
+
const rules = has404 ? netlifyLocale404Redirects(config) : '';
|
|
90
|
+
// `rules` starts with '\n' (terminating the BEGIN line) and ends with '\n'
|
|
91
|
+
// (putting END at line start), so the block is exactly BEGIN…rules…END lines.
|
|
92
|
+
const block = rules ? `${NETLIFY_LOCALE_404_BEGIN}${rules}${NETLIFY_LOCALE_404_END}\n` : '';
|
|
93
|
+
|
|
94
|
+
const beginIdx = content.indexOf(NETLIFY_LOCALE_404_BEGIN);
|
|
95
|
+
if (beginIdx === -1) {
|
|
96
|
+
if (!block) return content;
|
|
97
|
+
const base = content === '' || content.endsWith('\n') ? content : `${content}\n`;
|
|
98
|
+
return base + block;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const endMarkerIdx = content.indexOf(NETLIFY_LOCALE_404_END, beginIdx);
|
|
102
|
+
if (endMarkerIdx === -1) return content; // corrupted block — refuse to guess its extent
|
|
103
|
+
|
|
104
|
+
let endIdx = endMarkerIdx + NETLIFY_LOCALE_404_END.length;
|
|
105
|
+
if (content[endIdx] === '\n') endIdx += 1; // the END line's own newline belongs to the block
|
|
106
|
+
|
|
107
|
+
const before = content.slice(0, beginIdx);
|
|
108
|
+
const after = content.slice(endIdx);
|
|
109
|
+
return before + block + after;
|
|
110
|
+
}
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
isEmptyContainer,
|
|
25
25
|
} from './nodeUtils';
|
|
26
26
|
import { NODE_TYPE } from './constants';
|
|
27
|
-
import type {
|
|
27
|
+
import type { ComponentInstanceNode, HtmlNode, SlotMarker, EmbedNode, LinkNode, LocaleListNode } from './types';
|
|
28
28
|
import { registerBuiltInNodeTypes } from './registry';
|
|
29
29
|
|
|
30
30
|
describe('nodeUtils', () => {
|
|
@@ -126,11 +126,11 @@ describe('nodeUtils', () => {
|
|
|
126
126
|
const node: ComponentInstanceNode = {
|
|
127
127
|
type: NODE_TYPE.COMPONENT,
|
|
128
128
|
component: 'Button',
|
|
129
|
-
props: { text: 'Click me' }
|
|
129
|
+
props: { text: 'Click me' },
|
|
130
130
|
};
|
|
131
131
|
expect(extractNodeProperties(node)).toEqual({
|
|
132
132
|
componentName: 'Button',
|
|
133
|
-
props: { text: 'Click me' }
|
|
133
|
+
props: { text: 'Click me' },
|
|
134
134
|
});
|
|
135
135
|
});
|
|
136
136
|
|
|
@@ -138,7 +138,7 @@ describe('nodeUtils', () => {
|
|
|
138
138
|
const node: HtmlNode = { type: NODE_TYPE.NODE, tag: 'div' };
|
|
139
139
|
expect(extractNodeProperties(node)).toEqual({
|
|
140
140
|
tag: 'div',
|
|
141
|
-
props: {}
|
|
141
|
+
props: {},
|
|
142
142
|
});
|
|
143
143
|
});
|
|
144
144
|
|
|
@@ -227,6 +227,10 @@ describe('nodeUtils', () => {
|
|
|
227
227
|
expect(isValidNodeType(NODE_TYPE.EMBED)).toBe(true);
|
|
228
228
|
expect(isValidNodeType(NODE_TYPE.LINK)).toBe(true);
|
|
229
229
|
expect(isValidNodeType(NODE_TYPE.LOCALE_LIST)).toBe(true);
|
|
230
|
+
expect(isValidNodeType(NODE_TYPE.LIST)).toBe(true);
|
|
231
|
+
expect(isValidNodeType(NODE_TYPE.ISLAND)).toBe(true);
|
|
232
|
+
expect(isValidNodeType(NODE_TYPE.CUSTOM)).toBe(true);
|
|
233
|
+
expect(isValidNodeType(NODE_TYPE.MARKDOWN)).toBe(true);
|
|
230
234
|
});
|
|
231
235
|
|
|
232
236
|
test('returns false for invalid node types', () => {
|
|
@@ -253,6 +257,14 @@ describe('nodeUtils', () => {
|
|
|
253
257
|
expect(canHaveChildren(null)).toBe(false);
|
|
254
258
|
expect(canHaveChildren(undefined)).toBe(false);
|
|
255
259
|
});
|
|
260
|
+
|
|
261
|
+
test('returns false for a verbatim {_code} marker (the emitter ignores children → silent loss)', () => {
|
|
262
|
+
expect(
|
|
263
|
+
canHaveChildren({ _code: true, expr: 'items.map((i) => i)' } as unknown as Parameters<
|
|
264
|
+
typeof canHaveChildren
|
|
265
|
+
>[0]),
|
|
266
|
+
).toBe(false);
|
|
267
|
+
});
|
|
256
268
|
});
|
|
257
269
|
|
|
258
270
|
describe('canHaveStyle', () => {
|
|
@@ -328,7 +340,7 @@ describe('nodeUtils', () => {
|
|
|
328
340
|
const mapping = {
|
|
329
341
|
_mapping: true,
|
|
330
342
|
prop: 'visible',
|
|
331
|
-
values: {
|
|
343
|
+
values: { true: true, false: false },
|
|
332
344
|
};
|
|
333
345
|
expect(isBooleanMapping(mapping)).toBe(true);
|
|
334
346
|
});
|
|
@@ -368,8 +380,8 @@ describe('nodeUtils', () => {
|
|
|
368
380
|
if: {
|
|
369
381
|
_mapping: true,
|
|
370
382
|
prop: 'showBanner',
|
|
371
|
-
values: {
|
|
372
|
-
}
|
|
383
|
+
values: { true: true, false: false },
|
|
384
|
+
},
|
|
373
385
|
} as any;
|
|
374
386
|
|
|
375
387
|
expect(evaluateNodeIf(node, { showBanner: 'true' })).toBe(true);
|
|
@@ -383,8 +395,8 @@ describe('nodeUtils', () => {
|
|
|
383
395
|
if: {
|
|
384
396
|
_mapping: true,
|
|
385
397
|
prop: 'status',
|
|
386
|
-
values: {
|
|
387
|
-
}
|
|
398
|
+
values: { active: true, inactive: false },
|
|
399
|
+
},
|
|
388
400
|
} as any;
|
|
389
401
|
|
|
390
402
|
// 'unknown' is not in the values map
|
|
@@ -398,8 +410,8 @@ describe('nodeUtils', () => {
|
|
|
398
410
|
if: {
|
|
399
411
|
_mapping: true,
|
|
400
412
|
prop: 'visible',
|
|
401
|
-
values: {
|
|
402
|
-
}
|
|
413
|
+
values: { true: true, false: false },
|
|
414
|
+
},
|
|
403
415
|
} as any;
|
|
404
416
|
|
|
405
417
|
expect(evaluateNodeIf(node)).toBe(true);
|
|
@@ -409,7 +421,7 @@ describe('nodeUtils', () => {
|
|
|
409
421
|
const node = {
|
|
410
422
|
type: NODE_TYPE.NODE,
|
|
411
423
|
tag: 'div',
|
|
412
|
-
if: '{{showBanner}}'
|
|
424
|
+
if: '{{showBanner}}',
|
|
413
425
|
} as any;
|
|
414
426
|
|
|
415
427
|
// String templates can't be resolved without context, default to true
|
|
@@ -420,7 +432,7 @@ describe('nodeUtils', () => {
|
|
|
420
432
|
const node: ComponentInstanceNode = {
|
|
421
433
|
type: NODE_TYPE.COMPONENT,
|
|
422
434
|
component: 'Banner',
|
|
423
|
-
if: false
|
|
435
|
+
if: false,
|
|
424
436
|
} as any;
|
|
425
437
|
expect(evaluateNodeIf(node)).toBe(false);
|
|
426
438
|
});
|
|
@@ -429,7 +441,7 @@ describe('nodeUtils', () => {
|
|
|
429
441
|
const node: EmbedNode = {
|
|
430
442
|
type: NODE_TYPE.EMBED,
|
|
431
443
|
html: '<svg></svg>',
|
|
432
|
-
if: false
|
|
444
|
+
if: false,
|
|
433
445
|
} as any;
|
|
434
446
|
expect(evaluateNodeIf(node)).toBe(false);
|
|
435
447
|
});
|
|
@@ -438,7 +450,7 @@ describe('nodeUtils', () => {
|
|
|
438
450
|
const node: LinkNode = {
|
|
439
451
|
type: NODE_TYPE.LINK,
|
|
440
452
|
href: '/page',
|
|
441
|
-
if: true
|
|
453
|
+
if: true,
|
|
442
454
|
} as any;
|
|
443
455
|
expect(evaluateNodeIf(node)).toBe(true);
|
|
444
456
|
});
|
|
@@ -515,7 +527,7 @@ describe('nodeUtils', () => {
|
|
|
515
527
|
const node: HtmlNode = {
|
|
516
528
|
type: NODE_TYPE.NODE,
|
|
517
529
|
tag: 'div',
|
|
518
|
-
children: [{ type: NODE_TYPE.NODE, tag: 'span' }]
|
|
530
|
+
children: [{ type: NODE_TYPE.NODE, tag: 'span' }],
|
|
519
531
|
};
|
|
520
532
|
expect(isEmptyContainer(node)).toBe(false);
|
|
521
533
|
});
|