meno-core 1.0.53 → 1.0.54
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 +48 -57
- package/build-astro.ts +131 -113
- package/build-next.ts +109 -96
- package/build-static.test.ts +39 -10
- package/build-static.ts +120 -122
- package/dist/bin/cli.js +34 -38
- package/dist/bin/cli.js.map +2 -2
- package/dist/build-static.js +12 -11
- package/dist/chunks/chunk-2AR55GYH.js +42 -0
- package/dist/chunks/chunk-2AR55GYH.js.map +7 -0
- package/dist/chunks/{chunk-IGYR22T6.js → chunk-2FN4UOVO.js} +512 -239
- package/dist/chunks/chunk-2FN4UOVO.js.map +7 -0
- package/dist/chunks/chunk-3XER4E5W.js +168 -0
- package/dist/chunks/chunk-3XER4E5W.js.map +7 -0
- package/dist/chunks/{chunk-X754AHS5.js → chunk-5ETZFREW.js} +9 -12
- package/dist/chunks/chunk-5ETZFREW.js.map +7 -0
- package/dist/chunks/{chunk-2MHDV5BF.js → chunk-7E4IF5L7.js} +15 -21
- package/dist/chunks/chunk-7E4IF5L7.js.map +7 -0
- package/dist/chunks/{chunk-JGWFTO6P.js → chunk-7HWQUVTU.js} +1578 -1349
- package/dist/chunks/chunk-7HWQUVTU.js.map +7 -0
- package/dist/chunks/{chunk-O3NAGJP4.js → chunk-AE3QK5QW.js} +110 -21
- package/dist/chunks/chunk-AE3QK5QW.js.map +7 -0
- package/dist/chunks/{chunk-GZHGVVW3.js → chunk-F6KTJYGV.js} +7 -7
- package/dist/chunks/chunk-F6KTJYGV.js.map +7 -0
- package/dist/chunks/{chunk-WQFG7PAH.js → chunk-FZITJSSS.js} +2 -6
- package/dist/chunks/chunk-FZITJSSS.js.map +7 -0
- package/dist/chunks/{chunk-QB2LNO4W.js → chunk-GSYYA5GX.js} +2 -2
- package/dist/chunks/chunk-GSYYA5GX.js.map +7 -0
- package/dist/chunks/{chunk-YBLHKYFF.js → chunk-HIZMY3EP.js} +1 -1
- package/dist/chunks/chunk-HIZMY3EP.js.map +7 -0
- package/dist/chunks/{chunk-AZQYF6KE.js → chunk-I2WEGYA7.js} +41 -176
- package/dist/chunks/chunk-I2WEGYA7.js.map +7 -0
- package/dist/chunks/{chunk-I7YIGZXT.js → chunk-JNO3CNLJ.js} +6 -9
- package/dist/chunks/chunk-JNO3CNLJ.js.map +7 -0
- package/dist/chunks/{chunk-UB44F4Z2.js → chunk-NVRBTSQG.js} +2 -4
- package/dist/chunks/chunk-NVRBTSQG.js.map +7 -0
- package/dist/chunks/{chunk-JGP5A3Y5.js → chunk-Q4OBWKXG.js} +40 -33
- package/dist/chunks/chunk-Q4OBWKXG.js.map +7 -0
- package/dist/chunks/{chunk-R6XHAFBF.js → chunk-QTE32Y53.js} +250 -242
- package/dist/chunks/chunk-QTE32Y53.js.map +7 -0
- package/dist/chunks/{chunk-H3GJ4H2U.js → chunk-STDY3OVM.js} +214 -85
- package/dist/chunks/chunk-STDY3OVM.js.map +7 -0
- package/dist/chunks/configService-PRJZF7Y6.js +14 -0
- package/dist/chunks/{constants-STK2YBIW.js → constants-KIQEYMAM.js} +2 -2
- package/dist/chunks/{fs-JGINUXGL.js → fs-ZI5JEU7V.js} +2 -2
- package/dist/entries/server-router.js +14 -19
- package/dist/entries/server-router.js.map +2 -2
- package/dist/lib/client/index.js +638 -332
- package/dist/lib/client/index.js.map +2 -2
- package/dist/lib/server/index.js +177 -235
- package/dist/lib/server/index.js.map +2 -2
- package/dist/lib/shared/index.js +80 -44
- package/dist/lib/shared/index.js.map +2 -2
- package/dist/lib/shared/richtext/index.js +1 -1
- package/dist/lib/test-utils/index.js +38 -60
- package/dist/lib/test-utils/index.js.map +2 -2
- package/entries/client-router.tsx +10 -8
- package/entries/server-router.tsx +1 -7
- package/lib/client/ClientInitializer.ts +8 -8
- package/lib/client/ErrorBoundary.test.tsx +146 -143
- package/lib/client/ErrorBoundary.tsx +175 -127
- package/lib/client/componentRegistry.test.ts +96 -108
- package/lib/client/componentRegistry.ts +1 -2
- package/lib/client/contexts/ThemeContext.tsx +3 -2
- package/lib/client/core/ComponentBuilder.test.ts +513 -560
- package/lib/client/core/ComponentBuilder.ts +318 -146
- package/lib/client/core/ComponentRenderer.test.tsx +1 -2
- package/lib/client/core/ComponentRenderer.tsx +46 -33
- package/lib/client/core/builders/embedBuilder.ts +241 -53
- package/lib/client/core/builders/linkBuilder.ts +71 -44
- package/lib/client/core/builders/linkNodeBuilder.ts +78 -53
- package/lib/client/core/builders/listBuilder.ts +115 -86
- package/lib/client/core/builders/localeListBuilder.ts +95 -60
- package/lib/client/core/builders/types.ts +5 -5
- package/lib/client/core/cmsTemplateProcessor.ts +7 -7
- 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 +8 -0
- package/lib/client/hmrCssReload.ts +14 -8
- package/lib/client/hmrWebSocket.ts +9 -14
- package/lib/client/hooks/useColorVariables.test.ts +21 -21
- package/lib/client/hooks/useColorVariables.ts +12 -10
- package/lib/client/hooks/usePropertyAutocomplete.ts +3 -5
- package/lib/client/hooks/useVariables.ts +4 -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 +19 -21
- package/lib/client/meno-filter/MenoFilter.ts +5 -9
- package/lib/client/meno-filter/bindings.ts +15 -40
- package/lib/client/meno-filter/init.ts +1 -1
- package/lib/client/meno-filter/renderer.ts +23 -29
- package/lib/client/meno-filter/script.generated.ts +1 -3
- package/lib/client/meno-filter/ui.ts +3 -5
- package/lib/client/meno-filter/updates.ts +15 -21
- package/lib/client/navigation.test.ts +159 -159
- package/lib/client/navigation.ts +0 -1
- package/lib/client/responsiveStyleResolver.test.ts +230 -228
- package/lib/client/responsiveStyleResolver.ts +13 -16
- package/lib/client/routing/RouteLoader.test.ts +23 -24
- package/lib/client/routing/RouteLoader.ts +22 -35
- package/lib/client/routing/Router.tsx +31 -3
- package/lib/client/scripts/ScriptExecutor.test.ts +157 -158
- package/lib/client/scripts/ScriptExecutor.ts +15 -33
- package/lib/client/services/PrefetchService.test.ts +2 -2
- package/lib/client/services/PrefetchService.ts +10 -24
- package/lib/client/styleProcessor.test.ts +9 -9
- package/lib/client/styleProcessor.ts +18 -15
- package/lib/client/styles/StyleInjector.test.ts +122 -115
- package/lib/client/styles/StyleInjector.ts +9 -6
- package/lib/client/styles/UtilityClassCollector.ts +19 -26
- package/lib/client/styles/cspNonce.test.ts +2 -5
- package/lib/client/templateEngine.test.ts +554 -482
- package/lib/client/templateEngine.ts +200 -64
- 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 +2 -5
- package/lib/server/__integration__/ssr-rendering.test.ts +47 -37
- package/lib/server/__integration__/static-assets.test.ts +1 -1
- package/lib/server/__integration__/test-helpers.ts +84 -70
- package/lib/server/ab/generateFunctions.ts +12 -10
- package/lib/server/astro/cmsPageEmitter.ts +23 -38
- package/lib/server/astro/componentEmitter.ts +25 -36
- package/lib/server/astro/cssCollector.ts +10 -26
- package/lib/server/astro/nodeToAstro.test.ts +1750 -30
- package/lib/server/astro/nodeToAstro.ts +198 -187
- package/lib/server/astro/normalizeOrphanTemplateProps.test.ts +105 -109
- package/lib/server/astro/normalizeOrphanTemplateProps.ts +4 -12
- 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 +14 -17
- package/lib/server/createServer.ts +5 -17
- package/lib/server/cssGenerator.test.ts +35 -44
- package/lib/server/cssGenerator.ts +6 -17
- package/lib/server/fileWatcher.test.ts +124 -10
- package/lib/server/fileWatcher.ts +124 -118
- package/lib/server/index.ts +7 -1
- package/lib/server/jsonLoader.test.ts +39 -2
- package/lib/server/jsonLoader.ts +33 -31
- package/lib/server/middleware/cors.test.ts +20 -20
- package/lib/server/middleware/cors.ts +28 -4
- package/lib/server/middleware/errorHandler.test.ts +5 -3
- package/lib/server/middleware/errorHandler.ts +3 -8
- package/lib/server/middleware/index.ts +0 -1
- package/lib/server/middleware/logger.test.ts +7 -5
- package/lib/server/middleware/logger.ts +10 -22
- package/lib/server/pageCache.test.ts +76 -77
- package/lib/server/pageCache.ts +0 -1
- package/lib/server/projectContext.ts +4 -3
- package/lib/server/providers/fileSystemCMSProvider.test.ts +124 -95
- package/lib/server/providers/fileSystemCMSProvider.ts +35 -20
- package/lib/server/providers/fileSystemPageProvider.test.ts +84 -0
- package/lib/server/providers/fileSystemPageProvider.ts +31 -12
- package/lib/server/routes/api/cms.test.ts +26 -14
- package/lib/server/routes/api/cms.ts +9 -14
- package/lib/server/routes/api/components.ts +30 -34
- package/lib/server/routes/api/config.ts +0 -1
- package/lib/server/routes/api/core-routes.ts +47 -74
- package/lib/server/routes/api/functions.ts +8 -16
- 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 +1 -3
- package/lib/server/routes/api/variables.ts +1 -1
- package/lib/server/routes/index.ts +23 -26
- package/lib/server/routes/pages.ts +34 -29
- package/lib/server/routes/static.ts +16 -4
- package/lib/server/runtime/bundler.ts +47 -32
- package/lib/server/runtime/fs.ts +3 -13
- package/lib/server/runtime/httpServer.ts +5 -9
- package/lib/server/services/ColorService.ts +32 -27
- package/lib/server/services/EnumService.test.ts +2 -6
- package/lib/server/services/EnumService.ts +7 -2
- package/lib/server/services/VariableService.test.ts +1 -5
- package/lib/server/services/VariableService.ts +6 -1
- package/lib/server/services/cmsService.test.ts +116 -78
- package/lib/server/services/cmsService.ts +24 -54
- package/lib/server/services/componentService.test.ts +122 -36
- package/lib/server/services/componentService.ts +54 -38
- package/lib/server/services/configService.test.ts +9 -31
- package/lib/server/services/configService.ts +20 -27
- package/lib/server/services/fileWatcherService.ts +40 -6
- package/lib/server/services/index.ts +0 -1
- package/lib/server/services/pageService.test.ts +1 -3
- package/lib/server/services/pageService.ts +14 -13
- package/lib/server/ssr/attributeBuilder.ts +16 -6
- package/lib/server/ssr/buildErrorOverlay.ts +11 -12
- package/lib/server/ssr/clientDataInjector.ts +7 -21
- package/lib/server/ssr/cmsSSRProcessor.ts +3 -6
- 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 +3 -9
- package/lib/server/ssr/htmlGenerator.test.ts +120 -43
- package/lib/server/ssr/htmlGenerator.ts +120 -85
- package/lib/server/ssr/imageMetadata.test.ts +3 -1
- package/lib/server/ssr/imageMetadata.ts +25 -19
- package/lib/server/ssr/jsCollector.test.ts +3 -13
- package/lib/server/ssr/jsCollector.ts +3 -8
- package/lib/server/ssr/liveReloadIntegration.test.ts +69 -28
- 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 +196 -246
- package/lib/server/ssr/ssrRenderer.ts +609 -225
- package/lib/server/ssrRenderer.test.ts +1044 -950
- package/lib/server/utils/jsonLineMapper.test.ts +28 -28
- package/lib/server/utils/jsonLineMapper.ts +1 -1
- package/lib/server/validateStyleCoverage.ts +18 -20
- package/lib/server/webflow/buildWebflow.ts +41 -53
- package/lib/server/webflow/nodeToWebflow.test.ts +150 -218
- package/lib/server/webflow/nodeToWebflow.ts +195 -258
- package/lib/server/webflow/styleMapper.test.ts +15 -56
- package/lib/server/webflow/styleMapper.ts +33 -41
- package/lib/server/webflow/types.ts +1 -8
- package/lib/server/websocketManager.ts +16 -19
- package/lib/shared/attributeNodeUtils.test.ts +15 -15
- package/lib/shared/attributeNodeUtils.ts +5 -12
- package/lib/shared/breakpoints.ts +4 -11
- package/lib/shared/cmsQueryParser.test.ts +50 -42
- package/lib/shared/cmsQueryParser.ts +49 -31
- package/lib/shared/colorVariableUtils.test.ts +5 -5
- package/lib/shared/colorVariableUtils.ts +3 -8
- package/lib/shared/componentRefs.ts +1 -5
- package/lib/shared/constants.test.ts +3 -3
- package/lib/shared/constants.ts +4 -8
- package/lib/shared/cssGeneration.test.ts +262 -144
- package/lib/shared/cssGeneration.ts +189 -516
- package/lib/shared/cssNamedColors.ts +152 -30
- package/lib/shared/cssProperties.test.ts +5 -6
- package/lib/shared/cssProperties.ts +296 -112
- package/lib/shared/elementClassName.test.ts +109 -109
- package/lib/shared/elementClassName.ts +1 -1
- package/lib/shared/elementUtils.ts +12 -16
- 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 +119 -0
- package/lib/shared/expressionEvaluator.ts +43 -24
- package/lib/shared/fontLoader.test.ts +19 -5
- package/lib/shared/friendlyError.ts +2 -3
- package/lib/shared/gradientUtils.test.ts +1 -5
- package/lib/shared/gradientUtils.ts +2 -6
- package/lib/shared/hrefRefs.ts +2 -10
- package/lib/shared/i18n.test.ts +1 -1
- package/lib/shared/i18n.ts +13 -34
- package/lib/shared/index.ts +4 -0
- package/lib/shared/inlineSvgStyleRules.ts +2 -4
- package/lib/shared/interactiveStyleMappings.test.ts +11 -33
- package/lib/shared/interactiveStyleMappings.ts +9 -16
- package/lib/shared/interactiveStyles.test.ts +165 -188
- package/lib/shared/interfaces/contentProvider.ts +1 -1
- package/lib/shared/itemTemplateUtils.test.ts +6 -12
- package/lib/shared/itemTemplateUtils.ts +19 -35
- package/lib/shared/jsonRepair.ts +8 -2
- package/lib/shared/libraryLoader.test.ts +15 -49
- package/lib/shared/libraryLoader.ts +7 -22
- package/lib/shared/netlifyLocale404.test.ts +179 -0
- package/lib/shared/netlifyLocale404.ts +110 -0
- package/lib/shared/nodeUtils.test.ts +24 -16
- package/lib/shared/nodeUtils.ts +49 -19
- package/lib/shared/pathArrayUtils.test.ts +1 -2
- package/lib/shared/pathArrayUtils.ts +1 -1
- package/lib/shared/pathSecurity.ts +1 -1
- package/lib/shared/pathUtils.test.ts +4 -6
- package/lib/shared/pathUtils.ts +42 -48
- 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 +14 -17
- package/lib/shared/paths/PathUtils.ts +9 -10
- package/lib/shared/paths/PathValidator.test.ts +2 -15
- package/lib/shared/paths/PathValidator.ts +11 -9
- package/lib/shared/paths/index.ts +1 -2
- package/lib/shared/propResolver.test.ts +240 -244
- package/lib/shared/propResolver.ts +14 -25
- package/lib/shared/pxToRem.test.ts +7 -6
- package/lib/shared/pxToRem.ts +2 -5
- package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +9 -5
- package/lib/shared/registry/ClientRegistry.ts +0 -1
- package/lib/shared/registry/ComponentRegistry.test.ts +43 -29
- package/lib/shared/registry/ComponentRegistry.ts +9 -11
- package/lib/shared/registry/NodeTypeDefinition.ts +15 -8
- package/lib/shared/registry/RegistryManager.ts +1 -2
- package/lib/shared/registry/SSRRegistry.ts +0 -1
- package/lib/shared/registry/createNodeType.ts +7 -9
- package/lib/shared/registry/defineNodeType.ts +2 -6
- package/lib/shared/registry/index.ts +0 -1
- package/lib/shared/registry/nodeTypes/ComponentInstanceNodeType.ts +14 -15
- package/lib/shared/registry/nodeTypes/EmbedNodeType.ts +18 -11
- package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +47 -18
- package/lib/shared/registry/nodeTypes/LinkNodeType.ts +21 -19
- package/lib/shared/registry/nodeTypes/ListNodeType.ts +78 -74
- package/lib/shared/registry/nodeTypes/LocaleListNodeType.ts +27 -21
- package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +6 -7
- package/lib/shared/registry/nodeTypes/index.ts +10 -2
- package/lib/shared/responsiveScaling.test.ts +15 -31
- package/lib/shared/responsiveScaling.ts +55 -37
- package/lib/shared/responsiveStyleUtils.ts +11 -13
- package/lib/shared/richtext/htmlToTiptap.test.ts +23 -14
- package/lib/shared/richtext/htmlToTiptap.ts +1 -3
- 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 +5 -10
- package/lib/shared/styleUtils.test.ts +87 -61
- package/lib/shared/styleUtils.ts +5 -6
- package/lib/shared/themeDefaults.test.ts +11 -11
- package/lib/shared/themeDefaults.ts +3 -4
- package/lib/shared/tree/PathBuilder.test.ts +62 -138
- package/lib/shared/tree/PathBuilder.ts +49 -39
- package/lib/shared/treePathUtils.test.ts +2 -10
- package/lib/shared/treePathUtils.ts +54 -59
- package/lib/shared/types/api.ts +1 -2
- package/lib/shared/types/cms.ts +25 -21
- package/lib/shared/types/comment.ts +49 -16
- package/lib/shared/types/components.ts +27 -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 +2 -2
- package/lib/shared/types/styles.ts +0 -1
- package/lib/shared/types/variables.test.ts +4 -13
- package/lib/shared/types/variables.ts +48 -27
- package/lib/shared/types.ts +1 -2
- package/lib/shared/utilityClassConfig.ts +648 -321
- package/lib/shared/utilityClassMapper.test.ts +203 -92
- package/lib/shared/utilityClassMapper.ts +188 -246
- package/lib/shared/utilityClassNames.ts +326 -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/propValidator.test.ts +18 -20
- package/lib/shared/validation/propValidator.ts +12 -17
- package/lib/shared/validation/schemas.test.ts +24 -33
- package/lib/shared/validation/schemas.ts +469 -344
- package/lib/shared/validation/validators.test.ts +1 -6
- package/lib/shared/validation/validators.ts +89 -68
- package/lib/shared/viewportUnits.integration.test.ts +14 -10
- package/lib/shared/viewportUnits.test.ts +11 -23
- package/lib/test-utils/dom-setup.ts +1 -1
- package/lib/test-utils/factories/ConsoleMockFactory.ts +3 -7
- package/lib/test-utils/factories/DomMockFactory.ts +7 -19
- 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 +5 -9
- package/lib/test-utils/factories/StoreMockFactory.ts +14 -25
- 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 +12 -18
- package/lib/test-utils/mocks.ts +4 -2
- package/package.json +1 -1
- package/scripts/build-meno-filter.ts +1 -4
- package/vite.config.ts +4 -4
- package/dist/chunks/chunk-2MHDV5BF.js.map +0 -7
- package/dist/chunks/chunk-AZQYF6KE.js.map +0 -7
- package/dist/chunks/chunk-GZHGVVW3.js.map +0 -7
- package/dist/chunks/chunk-H3GJ4H2U.js.map +0 -7
- package/dist/chunks/chunk-I7YIGZXT.js.map +0 -7
- package/dist/chunks/chunk-IGYR22T6.js.map +0 -7
- 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.map +0 -7
- 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 → configService-PRJZF7Y6.js.map} +0 -0
- /package/dist/chunks/{constants-STK2YBIW.js.map → constants-KIQEYMAM.js.map} +0 -0
- /package/dist/chunks/{fs-JGINUXGL.js.map → fs-ZI5JEU7V.js.map} +0 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expression Evaluator Tests
|
|
3
|
+
*
|
|
4
|
+
* The headline concern here is the SSOT (single-source-of-truth) invariant between
|
|
5
|
+
* `evaluateNode`'s switch and `SUPPORTED_NODE_TYPES`. `isSupportedTemplateExpression`
|
|
6
|
+
* (used by the .astro codec to decide modelable {{binding}} vs verbatim _code) trusts
|
|
7
|
+
* `SUPPORTED_NODE_TYPES` to mirror exactly the node types `evaluateNode` can actually
|
|
8
|
+
* evaluate. If they drift, the failure is silent — either evaluatable code gets emitted
|
|
9
|
+
* as verbatim _code, or un-evaluatable code gets coerced into a fake {{binding}} that
|
|
10
|
+
* shows up literally in SSR output. These tests force a contributor who touches one side
|
|
11
|
+
* to touch the other.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { describe, test, expect } from 'bun:test';
|
|
15
|
+
import { safeEvaluate, isSupportedTemplateExpression, SUPPORTED_NODE_TYPES } from './expressionEvaluator';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The complete set of jsep node types `evaluateNode` has a `case` for AND returns a
|
|
19
|
+
* value from (i.e. does NOT throw). This list is intentionally hardcoded: adding a new
|
|
20
|
+
* `case` to evaluateNode without adding it here (or vice-versa) must fail the exact-set
|
|
21
|
+
* assertion below, forcing the SSOT to be kept in lockstep.
|
|
22
|
+
*
|
|
23
|
+
* Note: `LogicalExpression` is handled defensively in the same switch case as
|
|
24
|
+
* `BinaryExpression`, but jsep classifies `&&`/`||` as `BinaryExpression` by default, so
|
|
25
|
+
* it is never produced from a parsed string — hence it has no representative expression.
|
|
26
|
+
*/
|
|
27
|
+
const EXPECTED_SUPPORTED_NODE_TYPES = [
|
|
28
|
+
'Identifier',
|
|
29
|
+
'Literal',
|
|
30
|
+
'MemberExpression',
|
|
31
|
+
'BinaryExpression',
|
|
32
|
+
'LogicalExpression',
|
|
33
|
+
'ConditionalExpression',
|
|
34
|
+
'UnaryExpression',
|
|
35
|
+
'ArrayExpression',
|
|
36
|
+
].sort();
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* A representative, parseable expression for every supported node type jsep can actually
|
|
40
|
+
* emit, paired with the value `evaluateNode` should return for it given `CONTEXT`. Each
|
|
41
|
+
* entry double-proves the SSOT: the expression both (a) is reported supported by
|
|
42
|
+
* `isSupportedTemplateExpression`, and (b) is actually evaluated (not thrown) by
|
|
43
|
+
* `safeEvaluate` — so a type listed in SUPPORTED_NODE_TYPES but missing from the switch
|
|
44
|
+
* would surface as a wrong/undefined evaluation result here.
|
|
45
|
+
*/
|
|
46
|
+
const CONTEXT = { a: 2, b: 3, flag: true, name: 'Meno', obj: { x: 7 }, key: 'x' };
|
|
47
|
+
const REPRESENTATIVE: Array<{ nodeType: string; expr: string; expected: unknown }> = [
|
|
48
|
+
{ nodeType: 'Identifier', expr: 'name', expected: 'Meno' },
|
|
49
|
+
{ nodeType: 'Literal', expr: '42', expected: 42 },
|
|
50
|
+
{ nodeType: 'MemberExpression', expr: 'obj.x', expected: 7 },
|
|
51
|
+
{ nodeType: 'MemberExpression(computed)', expr: 'obj[key]', expected: 7 },
|
|
52
|
+
{ nodeType: 'BinaryExpression', expr: 'a + b', expected: 5 },
|
|
53
|
+
{ nodeType: 'BinaryExpression(&&)', expr: 'flag && name', expected: 'Meno' },
|
|
54
|
+
{ nodeType: 'BinaryExpression(||)', expr: 'a || b', expected: 2 },
|
|
55
|
+
{ nodeType: 'ConditionalExpression', expr: 'flag ? a : b', expected: 2 },
|
|
56
|
+
{ nodeType: 'UnaryExpression', expr: '!flag', expected: false },
|
|
57
|
+
{ nodeType: 'ArrayExpression', expr: '[a, b]', expected: [2, 3] },
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
/** Node types the evaluator deliberately refuses (its switch throws for these). */
|
|
61
|
+
const UNSUPPORTED_EXPRESSIONS: Array<{ nodeType: string; expr: string }> = [
|
|
62
|
+
{ nodeType: 'CallExpression', expr: 'obj.x.toFixed(2)' },
|
|
63
|
+
{ nodeType: 'Compound', expr: 'a, b' },
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
describe('expressionEvaluator SSOT invariant', () => {
|
|
67
|
+
test('SUPPORTED_NODE_TYPES exactly equals the expected hardcoded set', () => {
|
|
68
|
+
// If this fails, evaluateNode's switch and SUPPORTED_NODE_TYPES have drifted —
|
|
69
|
+
// update the switch, SUPPORTED_NODE_TYPES, AND this test's expected list together.
|
|
70
|
+
expect([...SUPPORTED_NODE_TYPES].sort()).toEqual(EXPECTED_SUPPORTED_NODE_TYPES);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('every representative supported expression is reported supported AND evaluated', () => {
|
|
74
|
+
for (const { nodeType, expr, expected } of REPRESENTATIVE) {
|
|
75
|
+
// (a) the codec predicate must accept it
|
|
76
|
+
expect(isSupportedTemplateExpression(expr)).toBe(true);
|
|
77
|
+
// (b) the evaluator must actually handle it (not throw -> undefined fallback)
|
|
78
|
+
expect(safeEvaluate(expr, CONTEXT)).toEqual(expected as never);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('every representative node type (jsep-producible) is present in SUPPORTED_NODE_TYPES', () => {
|
|
83
|
+
// Maps the labelled representatives back to their base jsep node type and asserts
|
|
84
|
+
// membership — closes the "added a case to the switch but forgot the set" gap for
|
|
85
|
+
// any type that has been given a representative expression.
|
|
86
|
+
const producibleBaseTypes = new Set(REPRESENTATIVE.map((r) => r.nodeType.replace(/\(.*\)$/, '')));
|
|
87
|
+
for (const baseType of producibleBaseTypes) {
|
|
88
|
+
expect(SUPPORTED_NODE_TYPES.has(baseType)).toBe(true);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('isSupportedTemplateExpression', () => {
|
|
94
|
+
test('returns false for function/method calls (verbatim _code territory)', () => {
|
|
95
|
+
for (const { expr } of UNSUPPORTED_EXPRESSIONS) {
|
|
96
|
+
expect(isSupportedTemplateExpression(expr)).toBe(false);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('returns false for empty / whitespace / syntactically invalid expressions', () => {
|
|
101
|
+
expect(isSupportedTemplateExpression('')).toBe(false);
|
|
102
|
+
expect(isSupportedTemplateExpression(' ')).toBe(false);
|
|
103
|
+
expect(isSupportedTemplateExpression('a +')).toBe(false);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('rejects an expression nesting an unsupported call deep inside a supported tree', () => {
|
|
107
|
+
// The recursive walk must catch unsupported nodes that are not at the root.
|
|
108
|
+
expect(isSupportedTemplateExpression('a + obj.x.toFixed(2)')).toBe(false);
|
|
109
|
+
expect(isSupportedTemplateExpression('flag ? name : obj.x.toString()')).toBe(false);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('safeEvaluate', () => {
|
|
114
|
+
test('returns undefined (no throw) for unsupported expressions', () => {
|
|
115
|
+
for (const { expr } of UNSUPPORTED_EXPRESSIONS) {
|
|
116
|
+
expect(safeEvaluate(expr, CONTEXT)).toBeUndefined();
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* Supports: ternary, binary, logical, unary operators, member access, arrays
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import jsep from 'jsep';
|
|
10
9
|
import type { Expression } from 'jsep';
|
|
10
|
+
import jsep from 'jsep';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Safely evaluates a jsep AST node against a context object
|
|
@@ -73,18 +73,32 @@ function evaluateNode(node: Expression, context: Record<string, unknown>): unkno
|
|
|
73
73
|
return String(left ?? '') + String(right ?? '');
|
|
74
74
|
}
|
|
75
75
|
return (left as number) + (right as number);
|
|
76
|
-
case '-':
|
|
77
|
-
|
|
78
|
-
case '
|
|
79
|
-
|
|
80
|
-
case '
|
|
81
|
-
|
|
82
|
-
case '
|
|
83
|
-
|
|
84
|
-
case '
|
|
85
|
-
|
|
86
|
-
case '
|
|
87
|
-
|
|
76
|
+
case '-':
|
|
77
|
+
return (left as number) - (right as number);
|
|
78
|
+
case '*':
|
|
79
|
+
return (left as number) * (right as number);
|
|
80
|
+
case '/':
|
|
81
|
+
return (left as number) / (right as number);
|
|
82
|
+
case '%':
|
|
83
|
+
return (left as number) % (right as number);
|
|
84
|
+
case '===':
|
|
85
|
+
return left === right;
|
|
86
|
+
case '!==':
|
|
87
|
+
return left !== right;
|
|
88
|
+
case '==':
|
|
89
|
+
// biome-ignore lint/suspicious/noDoubleEquals: loose equality IS the semantics of the `==` operator in template expressions
|
|
90
|
+
return left == right;
|
|
91
|
+
case '!=':
|
|
92
|
+
// biome-ignore lint/suspicious/noDoubleEquals: loose inequality IS the semantics of the `!=` operator in template expressions
|
|
93
|
+
return left != right;
|
|
94
|
+
case '<':
|
|
95
|
+
return (left as number) < (right as number);
|
|
96
|
+
case '>':
|
|
97
|
+
return (left as number) > (right as number);
|
|
98
|
+
case '<=':
|
|
99
|
+
return (left as number) <= (right as number);
|
|
100
|
+
case '>=':
|
|
101
|
+
return (left as number) >= (right as number);
|
|
88
102
|
default:
|
|
89
103
|
throw new Error(`Unsupported operator: ${binaryNode.operator}`);
|
|
90
104
|
}
|
|
@@ -94,9 +108,7 @@ function evaluateNode(node: Expression, context: Record<string, unknown>): unkno
|
|
|
94
108
|
// Ternary: condition ? consequent : alternate
|
|
95
109
|
const condNode = node as jsep.ConditionalExpression;
|
|
96
110
|
const test = evaluateNode(condNode.test, context);
|
|
97
|
-
return test
|
|
98
|
-
? evaluateNode(condNode.consequent, context)
|
|
99
|
-
: evaluateNode(condNode.alternate, context);
|
|
111
|
+
return test ? evaluateNode(condNode.consequent, context) : evaluateNode(condNode.alternate, context);
|
|
100
112
|
}
|
|
101
113
|
|
|
102
114
|
case 'UnaryExpression': {
|
|
@@ -105,9 +117,12 @@ function evaluateNode(node: Expression, context: Record<string, unknown>): unkno
|
|
|
105
117
|
const arg = evaluateNode(unaryNode.argument, context);
|
|
106
118
|
|
|
107
119
|
switch (unaryNode.operator) {
|
|
108
|
-
case '!':
|
|
109
|
-
|
|
110
|
-
case '
|
|
120
|
+
case '!':
|
|
121
|
+
return !arg;
|
|
122
|
+
case '-':
|
|
123
|
+
return -(arg as number);
|
|
124
|
+
case '+':
|
|
125
|
+
return +(arg as number);
|
|
111
126
|
default:
|
|
112
127
|
throw new Error(`Unsupported unary operator: ${unaryNode.operator}`);
|
|
113
128
|
}
|
|
@@ -116,9 +131,7 @@ function evaluateNode(node: Expression, context: Record<string, unknown>): unkno
|
|
|
116
131
|
case 'ArrayExpression': {
|
|
117
132
|
// Array literals: [1, 2, 3]
|
|
118
133
|
const arrayNode = node as jsep.ArrayExpression;
|
|
119
|
-
return arrayNode.elements.map(el =>
|
|
120
|
-
el ? evaluateNode(el, context) : undefined
|
|
121
|
-
);
|
|
134
|
+
return arrayNode.elements.map((el) => (el ? evaluateNode(el, context) : undefined));
|
|
122
135
|
}
|
|
123
136
|
|
|
124
137
|
case 'CallExpression': {
|
|
@@ -160,8 +173,14 @@ export function isComplexExpression(expression: string): boolean {
|
|
|
160
173
|
return /[?:+\-*/%<>=!&|]/.test(expression);
|
|
161
174
|
}
|
|
162
175
|
|
|
163
|
-
/**
|
|
164
|
-
|
|
176
|
+
/**
|
|
177
|
+
* jsep node types `evaluateNode` (above) can evaluate — the rest throw at runtime.
|
|
178
|
+
*
|
|
179
|
+
* SSOT: this set MUST stay in lockstep with the `case` labels in `evaluateNode`'s
|
|
180
|
+
* switch. Drift is silent (lost code or literal `{{…}}` in SSR output), so the
|
|
181
|
+
* invariant is pinned by expressionEvaluator.test.ts — update both together.
|
|
182
|
+
*/
|
|
183
|
+
export const SUPPORTED_NODE_TYPES = new Set([
|
|
165
184
|
'Identifier',
|
|
166
185
|
'Literal',
|
|
167
186
|
'MemberExpression',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, test, expect, afterAll } from 'bun:test';
|
|
1
|
+
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { mkdirSync, writeFileSync, rmSync } from 'fs';
|
|
4
4
|
import { tmpdir } from 'os';
|
|
@@ -13,10 +13,23 @@ const testDir = join(tmpdir(), `fontLoader-test-${Date.now()}`);
|
|
|
13
13
|
mkdirSync(testDir, { recursive: true });
|
|
14
14
|
setProjectRoot(testDir);
|
|
15
15
|
|
|
16
|
+
// fontLoader keeps a module-level config cache that survives across test
|
|
17
|
+
// files in a single `bun test` process. Earlier files (e.g. anything pulling
|
|
18
|
+
// in the SSR htmlGenerator) may already have populated it with `{ fonts: [] }`,
|
|
19
|
+
// which would make loadProjectConfig() return that stale cache instead of
|
|
20
|
+
// reading our test config. Reset on both ends so this file neither inherits
|
|
21
|
+
// nor leaks cached config.
|
|
22
|
+
beforeAll(() => {
|
|
23
|
+
resetFontConfig();
|
|
24
|
+
});
|
|
25
|
+
|
|
16
26
|
// Restore original project root after all tests in this file
|
|
17
27
|
afterAll(() => {
|
|
18
28
|
setProjectRoot(originalProjectRoot);
|
|
19
|
-
|
|
29
|
+
resetFontConfig();
|
|
30
|
+
try {
|
|
31
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
32
|
+
} catch {}
|
|
20
33
|
});
|
|
21
34
|
|
|
22
35
|
// Helper to write a project.config.json in the test directory
|
|
@@ -99,6 +112,7 @@ import {
|
|
|
99
112
|
generateFontCSS,
|
|
100
113
|
generateFontPreloadTags,
|
|
101
114
|
getFontFamilies,
|
|
115
|
+
resetFontConfig,
|
|
102
116
|
} from './fontLoader';
|
|
103
117
|
|
|
104
118
|
describe('fontLoader', () => {
|
|
@@ -215,7 +229,7 @@ describe('fontLoader', () => {
|
|
|
215
229
|
test('generateFontCSS omits font-display when not specified', () => {
|
|
216
230
|
const css = generateFontCSS();
|
|
217
231
|
// The Roboto bold entry has no fontDisplay
|
|
218
|
-
const robotoBlock = css.split('\n\n').find(b => b.includes('roboto-bold.ttf'))!;
|
|
232
|
+
const robotoBlock = css.split('\n\n').find((b) => b.includes('roboto-bold.ttf'))!;
|
|
219
233
|
expect(robotoBlock).toBeDefined();
|
|
220
234
|
expect(robotoBlock).not.toContain('font-display');
|
|
221
235
|
});
|
|
@@ -277,7 +291,7 @@ describe('fontLoader', () => {
|
|
|
277
291
|
|
|
278
292
|
test('generateFontPreloadTags defaults to font/ttf for unknown extension', () => {
|
|
279
293
|
const tags = generateFontPreloadTags();
|
|
280
|
-
const mysteryTag = tags.split('\n ').find(t => t.includes('mystery.xyz'))!;
|
|
294
|
+
const mysteryTag = tags.split('\n ').find((t) => t.includes('mystery.xyz'))!;
|
|
281
295
|
expect(mysteryTag).toContain('type="font/ttf"');
|
|
282
296
|
});
|
|
283
297
|
|
|
@@ -297,7 +311,7 @@ describe('fontLoader', () => {
|
|
|
297
311
|
test('generateFontPreloadTags produces correct link tag format', () => {
|
|
298
312
|
const tags = generateFontPreloadTags();
|
|
299
313
|
expect(tags).toContain(
|
|
300
|
-
'<link rel="preload" href="/fonts/inter-variable.woff2" as="font" type="font/woff2" crossorigin>'
|
|
314
|
+
'<link rel="preload" href="/fonts/inter-variable.woff2" as="font" type="font/woff2" crossorigin>',
|
|
301
315
|
);
|
|
302
316
|
});
|
|
303
317
|
|
|
@@ -83,7 +83,7 @@ const RULES: Rule[] = [
|
|
|
83
83
|
test: /Cannot find module|Failed to resolve module|Failed to load module|Error loading dynamically imported module/i,
|
|
84
84
|
build: () => ({
|
|
85
85
|
title: "A required file couldn't be loaded",
|
|
86
|
-
friendlyMessage:
|
|
86
|
+
friendlyMessage: "An import or module reference couldn't be found.",
|
|
87
87
|
hint: 'Check the import path.',
|
|
88
88
|
}),
|
|
89
89
|
},
|
|
@@ -114,8 +114,7 @@ export function toFriendlyError(input: unknown): FriendlyError {
|
|
|
114
114
|
|
|
115
115
|
return {
|
|
116
116
|
title: 'This section ran into a problem',
|
|
117
|
-
friendlyMessage:
|
|
118
|
-
'Something went wrong while rendering this part of the page. The rest of the page is fine.',
|
|
117
|
+
friendlyMessage: 'Something went wrong while rendering this part of the page. The rest of the page is fine.',
|
|
119
118
|
raw,
|
|
120
119
|
};
|
|
121
120
|
}
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import { describe, test, expect } from 'bun:test';
|
|
2
|
-
import {
|
|
3
|
-
isGradientValue,
|
|
4
|
-
parseGradient,
|
|
5
|
-
buildGradientCss,
|
|
6
|
-
} from './gradientUtils';
|
|
2
|
+
import { isGradientValue, parseGradient, buildGradientCss } from './gradientUtils';
|
|
7
3
|
|
|
8
4
|
describe('isGradientValue', () => {
|
|
9
5
|
test('identifies gradient values', () => {
|
|
@@ -22,9 +22,7 @@ const CONIC_RE = /^conic-gradient\((.+)\)$/i;
|
|
|
22
22
|
export function isGradientValue(value: string): boolean {
|
|
23
23
|
if (!value) return false;
|
|
24
24
|
const v = value.trim().toLowerCase();
|
|
25
|
-
return v.startsWith('linear-gradient(') ||
|
|
26
|
-
v.startsWith('radial-gradient(') ||
|
|
27
|
-
v.startsWith('conic-gradient(');
|
|
25
|
+
return v.startsWith('linear-gradient(') || v.startsWith('radial-gradient(') || v.startsWith('conic-gradient(');
|
|
28
26
|
}
|
|
29
27
|
|
|
30
28
|
/** Parse a CSS gradient string into a GradientConfig */
|
|
@@ -94,9 +92,7 @@ export function parseGradient(cssValue: string): GradientConfig | null {
|
|
|
94
92
|
|
|
95
93
|
/** Build a CSS gradient string from a GradientConfig */
|
|
96
94
|
export function buildGradientCss(config: GradientConfig): string {
|
|
97
|
-
const stopStrings = config.stops.map(
|
|
98
|
-
s => `${s.color} ${Math.round(s.position)}%`
|
|
99
|
-
);
|
|
95
|
+
const stopStrings = config.stops.map((s) => `${s.color} ${Math.round(s.position)}%`);
|
|
100
96
|
|
|
101
97
|
switch (config.type) {
|
|
102
98
|
case 'linear': {
|
package/lib/shared/hrefRefs.ts
CHANGED
|
@@ -16,11 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
function rewriteHref(value: string, oldHref: string, newHref: string): string | null {
|
|
18
18
|
if (value === oldHref) return newHref;
|
|
19
|
-
if (
|
|
20
|
-
value.startsWith(oldHref + '/') ||
|
|
21
|
-
value.startsWith(oldHref + '?') ||
|
|
22
|
-
value.startsWith(oldHref + '#')
|
|
23
|
-
) {
|
|
19
|
+
if (value.startsWith(oldHref + '/') || value.startsWith(oldHref + '?') || value.startsWith(oldHref + '#')) {
|
|
24
20
|
return newHref + value.substring(oldHref.length);
|
|
25
21
|
}
|
|
26
22
|
return null;
|
|
@@ -34,11 +30,7 @@ function rewriteHref(value: string, oldHref: string, newHref: string): string |
|
|
|
34
30
|
* Walks `children` recursively (array or single object) and any object-valued
|
|
35
31
|
* field on the node (covers component-prop-trees, nested structure, etc.).
|
|
36
32
|
*/
|
|
37
|
-
export function rewriteHrefRefs(
|
|
38
|
-
node: unknown,
|
|
39
|
-
oldHref: string,
|
|
40
|
-
newHref: string,
|
|
41
|
-
): boolean {
|
|
33
|
+
export function rewriteHrefRefs(node: unknown, oldHref: string, newHref: string): boolean {
|
|
42
34
|
if (!node || typeof node !== 'object') return false;
|
|
43
35
|
if (!oldHref || oldHref === newHref) return false;
|
|
44
36
|
|
package/lib/shared/i18n.test.ts
CHANGED
|
@@ -199,7 +199,7 @@ describe('i18n', () => {
|
|
|
199
199
|
test('should return empty array if no translation available for list i18n', () => {
|
|
200
200
|
const listValue = {
|
|
201
201
|
_i18n: true,
|
|
202
|
-
en: [],
|
|
202
|
+
en: [], // Add empty array to make it a list i18n
|
|
203
203
|
} as I18nValue;
|
|
204
204
|
// Remove the empty array to test empty fallback detection
|
|
205
205
|
delete (listValue as any).en;
|
package/lib/shared/i18n.ts
CHANGED
|
@@ -10,9 +10,7 @@ import type { I18nValue, I18nConfig, LocaleConfig } from './types/components';
|
|
|
10
10
|
*/
|
|
11
11
|
export const DEFAULT_I18N_CONFIG: I18nConfig = {
|
|
12
12
|
defaultLocale: 'en',
|
|
13
|
-
locales: [
|
|
14
|
-
{ code: 'en', name: 'English', nativeName: 'English', langTag: 'en-US' }
|
|
15
|
-
],
|
|
13
|
+
locales: [{ code: 'en', name: 'English', nativeName: 'English', langTag: 'en-US' }],
|
|
16
14
|
};
|
|
17
15
|
|
|
18
16
|
// ============================================
|
|
@@ -23,21 +21,21 @@ export const DEFAULT_I18N_CONFIG: I18nConfig = {
|
|
|
23
21
|
* Get array of locale codes from config
|
|
24
22
|
*/
|
|
25
23
|
export function getLocaleCodes(config: I18nConfig): string[] {
|
|
26
|
-
return config.locales.map(loc => loc.code);
|
|
24
|
+
return config.locales.map((loc) => loc.code);
|
|
27
25
|
}
|
|
28
26
|
|
|
29
27
|
/**
|
|
30
28
|
* Find a locale config by its code
|
|
31
29
|
*/
|
|
32
30
|
export function findLocaleByCode(config: I18nConfig, code: string): LocaleConfig | undefined {
|
|
33
|
-
return config.locales.find(loc => loc.code === code);
|
|
31
|
+
return config.locales.find((loc) => loc.code === code);
|
|
34
32
|
}
|
|
35
33
|
|
|
36
34
|
/**
|
|
37
35
|
* Check if a locale code is valid/exists in config
|
|
38
36
|
*/
|
|
39
37
|
export function isValidLocaleCode(config: I18nConfig, code: string): boolean {
|
|
40
|
-
return config.locales.some(loc => loc.code === code);
|
|
38
|
+
return config.locales.some((loc) => loc.code === code);
|
|
41
39
|
}
|
|
42
40
|
|
|
43
41
|
// ============================================
|
|
@@ -112,11 +110,7 @@ export function isI18nValue(value: unknown): value is I18nValue {
|
|
|
112
110
|
* Fallback order: exact locale -> default locale -> first available -> empty
|
|
113
111
|
* Works with any value type (strings, arrays for list props, etc.)
|
|
114
112
|
*/
|
|
115
|
-
export function resolveTranslation(
|
|
116
|
-
value: I18nValue,
|
|
117
|
-
locale: string,
|
|
118
|
-
config: I18nConfig
|
|
119
|
-
): unknown {
|
|
113
|
+
export function resolveTranslation(value: I18nValue, locale: string, config: I18nConfig): unknown {
|
|
120
114
|
// Try exact locale match (any type - string, array, etc.)
|
|
121
115
|
if (locale in value && value[locale] !== undefined && locale !== '_i18n') {
|
|
122
116
|
return value[locale];
|
|
@@ -136,9 +130,7 @@ export function resolveTranslation(
|
|
|
136
130
|
|
|
137
131
|
// Return empty string for string i18n, empty array for list i18n
|
|
138
132
|
// Check if any value is an array to determine the type
|
|
139
|
-
const hasArrayValue = Object.entries(value).some(
|
|
140
|
-
([k, v]) => k !== '_i18n' && Array.isArray(v)
|
|
141
|
-
);
|
|
133
|
+
const hasArrayValue = Object.entries(value).some(([k, v]) => k !== '_i18n' && Array.isArray(v));
|
|
142
134
|
return hasArrayValue ? [] : '';
|
|
143
135
|
}
|
|
144
136
|
|
|
@@ -146,11 +138,7 @@ export function resolveTranslation(
|
|
|
146
138
|
* Resolve a value that might be an I18nValue or a regular value
|
|
147
139
|
* Returns the original value if not an I18nValue
|
|
148
140
|
*/
|
|
149
|
-
export function resolveI18nValue(
|
|
150
|
-
value: unknown,
|
|
151
|
-
locale: string,
|
|
152
|
-
config: I18nConfig
|
|
153
|
-
): unknown {
|
|
141
|
+
export function resolveI18nValue(value: unknown, locale: string, config: I18nConfig): unknown {
|
|
154
142
|
if (isI18nValue(value)) {
|
|
155
143
|
return resolveTranslation(value, locale, config);
|
|
156
144
|
}
|
|
@@ -163,7 +151,7 @@ export function resolveI18nValue(
|
|
|
163
151
|
export function resolveI18nInProps(
|
|
164
152
|
props: Record<string, unknown>,
|
|
165
153
|
locale: string,
|
|
166
|
-
config: I18nConfig
|
|
154
|
+
config: I18nConfig,
|
|
167
155
|
): Record<string, unknown> {
|
|
168
156
|
const resolved: Record<string, unknown> = {};
|
|
169
157
|
|
|
@@ -171,16 +159,10 @@ export function resolveI18nInProps(
|
|
|
171
159
|
if (isI18nValue(value)) {
|
|
172
160
|
resolved[key] = resolveTranslation(value, locale, config);
|
|
173
161
|
} else if (Array.isArray(value)) {
|
|
174
|
-
resolved[key] = value.map((item) =>
|
|
175
|
-
isI18nValue(item) ? resolveTranslation(item, locale, config) : item
|
|
176
|
-
);
|
|
162
|
+
resolved[key] = value.map((item) => (isI18nValue(item) ? resolveTranslation(item, locale, config) : item));
|
|
177
163
|
} else if (typeof value === 'object' && value !== null) {
|
|
178
164
|
// Recursively resolve nested objects (but not I18nValue which is already handled)
|
|
179
|
-
resolved[key] = resolveI18nInProps(
|
|
180
|
-
value as Record<string, unknown>,
|
|
181
|
-
locale,
|
|
182
|
-
config
|
|
183
|
-
);
|
|
165
|
+
resolved[key] = resolveI18nInProps(value as Record<string, unknown>, locale, config);
|
|
184
166
|
} else {
|
|
185
167
|
resolved[key] = value;
|
|
186
168
|
}
|
|
@@ -195,7 +177,7 @@ export function resolveI18nInProps(
|
|
|
195
177
|
*/
|
|
196
178
|
export function extractLocaleFromPath(
|
|
197
179
|
path: string,
|
|
198
|
-
config: I18nConfig
|
|
180
|
+
config: I18nConfig,
|
|
199
181
|
): { locale: string | null; pathWithoutLocale: string } {
|
|
200
182
|
// Remove leading slash and split
|
|
201
183
|
const cleanPath = path.startsWith('/') ? path.slice(1) : path;
|
|
@@ -235,17 +217,14 @@ export interface LocaleContext {
|
|
|
235
217
|
* Parse locale from path and return full context with effective locale
|
|
236
218
|
* Combines extractLocaleFromPath + defaultLocale resolution in one call
|
|
237
219
|
*/
|
|
238
|
-
export function parseLocaleFromPath(
|
|
239
|
-
path: string,
|
|
240
|
-
config: I18nConfig
|
|
241
|
-
): LocaleContext {
|
|
220
|
+
export function parseLocaleFromPath(path: string, config: I18nConfig): LocaleContext {
|
|
242
221
|
const { locale, pathWithoutLocale } = extractLocaleFromPath(path, config);
|
|
243
222
|
const effectiveLocale = locale || config.defaultLocale;
|
|
244
223
|
|
|
245
224
|
return {
|
|
246
225
|
locale: effectiveLocale,
|
|
247
226
|
pathWithoutLocale,
|
|
248
|
-
isDefaultLocale: effectiveLocale === config.defaultLocale
|
|
227
|
+
isDefaultLocale: effectiveLocale === config.defaultLocale,
|
|
249
228
|
};
|
|
250
229
|
}
|
|
251
230
|
|
package/lib/shared/index.ts
CHANGED
|
@@ -116,6 +116,10 @@ export { isSupportedTemplateExpression } from './expressionEvaluator';
|
|
|
116
116
|
// Path security utilities
|
|
117
117
|
export * from './pathSecurity';
|
|
118
118
|
|
|
119
|
+
// Netlify locale-scoped 404 rules + the managed netlify.toml block (shared by
|
|
120
|
+
// every netlify.toml producer and the studio save-config sync)
|
|
121
|
+
export * from './netlifyLocale404';
|
|
122
|
+
|
|
119
123
|
// Interactive style mapping utilities
|
|
120
124
|
export * from './interactiveStyleMappings';
|
|
121
125
|
|
|
@@ -59,9 +59,7 @@ function rewriteSvgBlock(svgBlock: string): string {
|
|
|
59
59
|
const rawContent = m[1];
|
|
60
60
|
// Strip CDATA wrapper if present (DOMPurify already unwraps it, but the
|
|
61
61
|
// pre-sanitize call path may see one).
|
|
62
|
-
const content = stripCssComments(
|
|
63
|
-
rawContent.replace(/^\s*<!\[CDATA\[/, '').replace(/\]\]>\s*$/, '')
|
|
64
|
-
);
|
|
62
|
+
const content = stripCssComments(rawContent.replace(/^\s*<!\[CDATA\[/, '').replace(/\]\]>\s*$/, ''));
|
|
65
63
|
|
|
66
64
|
// Pull out every simple `.cls { ... }` rule we can match.
|
|
67
65
|
const localClasses = new Map<string, string>();
|
|
@@ -99,7 +97,7 @@ function rewriteSvgBlock(svgBlock: string): string {
|
|
|
99
97
|
for (const [className, decls] of classDeclarations) {
|
|
100
98
|
const elementRe = new RegExp(
|
|
101
99
|
`(<[A-Za-z][\\w-]*\\b[^>]*?\\sclass="[^"]*\\b${escapeForRegex(className)}\\b[^"]*"[^>]*?)(/?>)`,
|
|
102
|
-
'g'
|
|
100
|
+
'g',
|
|
103
101
|
);
|
|
104
102
|
out = out.replace(elementRe, (_full, openHead: string, closing: string) => {
|
|
105
103
|
const styleAttrMatch = openHead.match(/\sstyle="([^"]*)"/);
|
|
@@ -46,9 +46,7 @@ describe('interactiveStyleMappings', () => {
|
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
it('returns false when no mappings present', () => {
|
|
49
|
-
const styles: InteractiveStyles = [
|
|
50
|
-
{ postfix: ':hover', style: { base: { backgroundColor: '#0070f3' } } },
|
|
51
|
-
];
|
|
49
|
+
const styles: InteractiveStyles = [{ postfix: ':hover', style: { base: { backgroundColor: '#0070f3' } } }];
|
|
52
50
|
expect(hasInteractiveStyleMappings(styles)).toBe(false);
|
|
53
51
|
});
|
|
54
52
|
|
|
@@ -89,9 +87,7 @@ describe('interactiveStyleMappings', () => {
|
|
|
89
87
|
|
|
90
88
|
describe('extractInteractiveStyleMappings', () => {
|
|
91
89
|
it('returns unchanged styles when no mappings', () => {
|
|
92
|
-
const styles: InteractiveStyles = [
|
|
93
|
-
{ postfix: ':hover', style: { base: { backgroundColor: '#0070f3' } } },
|
|
94
|
-
];
|
|
90
|
+
const styles: InteractiveStyles = [{ postfix: ':hover', style: { base: { backgroundColor: '#0070f3' } } }];
|
|
95
91
|
|
|
96
92
|
const { resolvedStyles, mappings } = extractInteractiveStyleMappings(styles);
|
|
97
93
|
|
|
@@ -106,9 +102,7 @@ describe('interactiveStyleMappings', () => {
|
|
|
106
102
|
values: { primary: '#0070f3', secondary: '#gray' },
|
|
107
103
|
};
|
|
108
104
|
|
|
109
|
-
const styles: InteractiveStyles = [
|
|
110
|
-
{ postfix: ':hover', style: { backgroundColor: mapping } },
|
|
111
|
-
];
|
|
105
|
+
const styles: InteractiveStyles = [{ postfix: ':hover', style: { backgroundColor: mapping } }];
|
|
112
106
|
|
|
113
107
|
const { resolvedStyles, mappings } = extractInteractiveStyleMappings(styles);
|
|
114
108
|
|
|
@@ -133,9 +127,7 @@ describe('interactiveStyleMappings', () => {
|
|
|
133
127
|
values: { primary: '#0070f3' },
|
|
134
128
|
};
|
|
135
129
|
|
|
136
|
-
const styles: InteractiveStyles = [
|
|
137
|
-
{ postfix: ':hover', style: { base: { backgroundColor: mapping } } },
|
|
138
|
-
];
|
|
130
|
+
const styles: InteractiveStyles = [{ postfix: ':hover', style: { base: { backgroundColor: mapping } } }];
|
|
139
131
|
|
|
140
132
|
const { resolvedStyles, mappings } = extractInteractiveStyleMappings(styles);
|
|
141
133
|
|
|
@@ -287,9 +279,7 @@ describe('interactiveStyleMappings', () => {
|
|
|
287
279
|
values: { primary: '#0070f3' },
|
|
288
280
|
};
|
|
289
281
|
|
|
290
|
-
const styles: InteractiveStyles = [
|
|
291
|
-
{ postfix: ':hover', style: { base: { backgroundColor: mapping } } },
|
|
292
|
-
];
|
|
282
|
+
const styles: InteractiveStyles = [{ postfix: ':hover', style: { base: { backgroundColor: mapping } } }];
|
|
293
283
|
|
|
294
284
|
const { resolvedStyles } = extractInteractiveStyleMappings(styles);
|
|
295
285
|
|
|
@@ -305,9 +295,7 @@ describe('interactiveStyleMappings', () => {
|
|
|
305
295
|
values: { primary: '#0070f3', secondary: '#gray' },
|
|
306
296
|
};
|
|
307
297
|
|
|
308
|
-
const styles: InteractiveStyles = [
|
|
309
|
-
{ postfix: ':hover', style: { base: { backgroundColor: mapping } } },
|
|
310
|
-
];
|
|
298
|
+
const styles: InteractiveStyles = [{ postfix: ':hover', style: { base: { backgroundColor: mapping } } }];
|
|
311
299
|
|
|
312
300
|
const { mappings } = extractInteractiveStyleMappings(styles);
|
|
313
301
|
const cssVariables = resolveExtractedMappings(mappings, { variant: 'primary' });
|
|
@@ -351,9 +339,7 @@ describe('interactiveStyleMappings', () => {
|
|
|
351
339
|
values: { primary: '#0070f3', secondary: '#gray' },
|
|
352
340
|
};
|
|
353
341
|
|
|
354
|
-
const styles: InteractiveStyles = [
|
|
355
|
-
{ postfix: ':hover', style: { base: { backgroundColor: mapping } } },
|
|
356
|
-
];
|
|
342
|
+
const styles: InteractiveStyles = [{ postfix: ':hover', style: { base: { backgroundColor: mapping } } }];
|
|
357
343
|
|
|
358
344
|
const { mappings } = extractInteractiveStyleMappings(styles);
|
|
359
345
|
const cssVariables = resolveExtractedMappings(mappings, { variant: 'purple' });
|
|
@@ -368,9 +354,7 @@ describe('interactiveStyleMappings', () => {
|
|
|
368
354
|
values: { primary: '#0070f3' },
|
|
369
355
|
};
|
|
370
356
|
|
|
371
|
-
const styles: InteractiveStyles = [
|
|
372
|
-
{ postfix: ':hover', style: { base: { backgroundColor: mapping } } },
|
|
373
|
-
];
|
|
357
|
+
const styles: InteractiveStyles = [{ postfix: ':hover', style: { base: { backgroundColor: mapping } } }];
|
|
374
358
|
|
|
375
359
|
const { mappings } = extractInteractiveStyleMappings(styles);
|
|
376
360
|
const cssVariables = resolveExtractedMappings(mappings, {});
|
|
@@ -385,9 +369,7 @@ describe('interactiveStyleMappings', () => {
|
|
|
385
369
|
values: { primary: '#0070f3' },
|
|
386
370
|
};
|
|
387
371
|
|
|
388
|
-
const styles: InteractiveStyles = [
|
|
389
|
-
{ postfix: ':hover', style: { base: { backgroundColor: mapping } } },
|
|
390
|
-
];
|
|
372
|
+
const styles: InteractiveStyles = [{ postfix: ':hover', style: { base: { backgroundColor: mapping } } }];
|
|
391
373
|
|
|
392
374
|
const { mappings } = extractInteractiveStyleMappings(styles);
|
|
393
375
|
const cssVariables = resolveExtractedMappings(mappings, { variant: null });
|
|
@@ -402,9 +384,7 @@ describe('interactiveStyleMappings', () => {
|
|
|
402
384
|
values: { true: '0.5', false: '1' },
|
|
403
385
|
};
|
|
404
386
|
|
|
405
|
-
const styles: InteractiveStyles = [
|
|
406
|
-
{ postfix: ':hover', style: { base: { opacity: mapping } } },
|
|
407
|
-
];
|
|
387
|
+
const styles: InteractiveStyles = [{ postfix: ':hover', style: { base: { opacity: mapping } } }];
|
|
408
388
|
|
|
409
389
|
const { mappings } = extractInteractiveStyleMappings(styles);
|
|
410
390
|
const cssVariables = resolveExtractedMappings(mappings, { disabled: true });
|
|
@@ -419,9 +399,7 @@ describe('interactiveStyleMappings', () => {
|
|
|
419
399
|
values: { '1': '10px', '2': '20px', '3': '30px' },
|
|
420
400
|
};
|
|
421
401
|
|
|
422
|
-
const styles: InteractiveStyles = [
|
|
423
|
-
{ postfix: ':hover', style: { base: { padding: mapping } } },
|
|
424
|
-
];
|
|
402
|
+
const styles: InteractiveStyles = [{ postfix: ':hover', style: { base: { padding: mapping } } }];
|
|
425
403
|
|
|
426
404
|
const { mappings } = extractInteractiveStyleMappings(styles);
|
|
427
405
|
const cssVariables = resolveExtractedMappings(mappings, { size: 2 });
|