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
|
@@ -1,3821 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CachedConfigLoader,
|
|
3
|
-
colorService,
|
|
4
|
-
formatJsonErrorMessage,
|
|
5
|
-
generateFontCSS,
|
|
6
|
-
generateSSRHTML,
|
|
7
|
-
generateVariablesCSS,
|
|
8
|
-
loadBreakpointConfig,
|
|
9
|
-
loadComponentDirectory,
|
|
10
|
-
loadI18nConfig,
|
|
11
|
-
loadJSONFile,
|
|
12
|
-
loadProjectConfig,
|
|
13
|
-
mapPageNameToPath,
|
|
14
|
-
parseJSON,
|
|
15
|
-
variableService
|
|
16
|
-
} from "./chunk-IGYR22T6.js";
|
|
17
|
-
import {
|
|
18
|
-
configService
|
|
19
|
-
} from "./chunk-2MHDV5BF.js";
|
|
20
|
-
import {
|
|
21
|
-
bundleFile,
|
|
22
|
-
createRuntimeServer,
|
|
23
|
-
getPackageRoot,
|
|
24
|
-
getProjectRoot,
|
|
25
|
-
packagePaths,
|
|
26
|
-
projectPaths,
|
|
27
|
-
resolvePackagePath,
|
|
28
|
-
resolveProjectPath
|
|
29
|
-
} from "./chunk-I7YIGZXT.js";
|
|
30
|
-
import {
|
|
31
|
-
fileExists,
|
|
32
|
-
hashContent,
|
|
33
|
-
readTextFile,
|
|
34
|
-
serveFile,
|
|
35
|
-
writeFile
|
|
36
|
-
} from "./chunk-WQFG7PAH.js";
|
|
37
|
-
import {
|
|
38
|
-
extractStringValue,
|
|
39
|
-
rewriteComponentRefs
|
|
40
|
-
} from "./chunk-QB2LNO4W.js";
|
|
41
|
-
import {
|
|
42
|
-
buildSlugIndex,
|
|
43
|
-
isPathWithinRoot,
|
|
44
|
-
resolveSlugToPageId
|
|
45
|
-
} from "./chunk-X754AHS5.js";
|
|
46
|
-
import {
|
|
47
|
-
addItemUrls,
|
|
48
|
-
toFriendlyError
|
|
49
|
-
} from "./chunk-JGWFTO6P.js";
|
|
50
|
-
import {
|
|
51
|
-
parseLocaleFromPath
|
|
52
|
-
} from "./chunk-AZQYF6KE.js";
|
|
53
|
-
import {
|
|
54
|
-
API_ROUTES,
|
|
55
|
-
HMR_ROUTE,
|
|
56
|
-
MAX_PORT_ATTEMPTS,
|
|
57
|
-
SERVER_PORT,
|
|
58
|
-
WEBSOCKET_STATES,
|
|
59
|
-
init_constants
|
|
60
|
-
} from "./chunk-YBLHKYFF.js";
|
|
61
|
-
|
|
62
|
-
// lib/server/pageCache.ts
|
|
63
|
-
var PageCache = class {
|
|
64
|
-
cache = /* @__PURE__ */ new Map();
|
|
65
|
-
/**
|
|
66
|
-
* Store page content and line map in cache
|
|
67
|
-
*/
|
|
68
|
-
set(path2, content, lineMap) {
|
|
69
|
-
this.cache.set(path2, { content, lineMap });
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Get cached page data
|
|
73
|
-
*/
|
|
74
|
-
get(path2) {
|
|
75
|
-
return this.cache.get(path2);
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Get page content from cache
|
|
79
|
-
*/
|
|
80
|
-
getContent(path2) {
|
|
81
|
-
return this.cache.get(path2)?.content;
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Get line map from cache
|
|
85
|
-
*/
|
|
86
|
-
getLineMap(path2) {
|
|
87
|
-
return this.cache.get(path2)?.lineMap;
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Get line range for a specific element path
|
|
91
|
-
*/
|
|
92
|
-
getLineRange(pagePath, elementPath) {
|
|
93
|
-
return this.cache.get(pagePath)?.lineMap.get(elementPath);
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Delete page from cache
|
|
97
|
-
*/
|
|
98
|
-
delete(path2) {
|
|
99
|
-
this.cache.delete(path2);
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Check if page exists in cache
|
|
103
|
-
*/
|
|
104
|
-
has(path2) {
|
|
105
|
-
return this.cache.has(path2);
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Get all page paths
|
|
109
|
-
*/
|
|
110
|
-
keys() {
|
|
111
|
-
return Array.from(this.cache.keys());
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Clear all cached pages
|
|
115
|
-
*/
|
|
116
|
-
clear() {
|
|
117
|
-
this.cache.clear();
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Get cache size
|
|
121
|
-
*/
|
|
122
|
-
size() {
|
|
123
|
-
return this.cache.size;
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Get all pages content as object (without line maps)
|
|
127
|
-
*/
|
|
128
|
-
toObject() {
|
|
129
|
-
const obj = {};
|
|
130
|
-
this.cache.forEach((value, key) => {
|
|
131
|
-
obj[key] = value.content;
|
|
132
|
-
});
|
|
133
|
-
return obj;
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
// lib/server/websocketManager.ts
|
|
138
|
-
init_constants();
|
|
139
|
-
var WebSocketManager = class {
|
|
140
|
-
clients = /* @__PURE__ */ new Set();
|
|
141
|
-
/**
|
|
142
|
-
* Add a WebSocket client
|
|
143
|
-
*/
|
|
144
|
-
addClient(ws) {
|
|
145
|
-
this.clients.add(ws);
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Remove a WebSocket client
|
|
149
|
-
*/
|
|
150
|
-
removeClient(ws) {
|
|
151
|
-
this.clients.delete(ws);
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Broadcast message to all connected clients
|
|
155
|
-
*/
|
|
156
|
-
broadcast(message) {
|
|
157
|
-
const messageStr = typeof message === "string" ? message : JSON.stringify(message);
|
|
158
|
-
for (const ws of this.clients) {
|
|
159
|
-
if (ws.readyState === WEBSOCKET_STATES.OPEN) {
|
|
160
|
-
ws.send(messageStr);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Broadcast update notification
|
|
166
|
-
*/
|
|
167
|
-
broadcastUpdate(path2) {
|
|
168
|
-
this.broadcast({
|
|
169
|
-
type: "hmr:update",
|
|
170
|
-
path: path2 || "all"
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
/**
|
|
174
|
-
* Broadcast colors/themes update notification
|
|
175
|
-
*/
|
|
176
|
-
broadcastColorsUpdate() {
|
|
177
|
-
this.broadcast({
|
|
178
|
-
type: "hmr:colors-update"
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* Broadcast variables update notification
|
|
183
|
-
*/
|
|
184
|
-
broadcastVariablesUpdate() {
|
|
185
|
-
this.broadcast({
|
|
186
|
-
type: "hmr:variables-update"
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Broadcast fonts update notification
|
|
191
|
-
*/
|
|
192
|
-
broadcastFontsUpdate() {
|
|
193
|
-
this.broadcast({
|
|
194
|
-
type: "hmr:fonts-update"
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
/**
|
|
198
|
-
* Broadcast libraries update notification
|
|
199
|
-
*/
|
|
200
|
-
broadcastLibrariesUpdate() {
|
|
201
|
-
this.broadcast({
|
|
202
|
-
type: "hmr:libraries-update"
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
/**
|
|
206
|
-
* Broadcast enums update notification
|
|
207
|
-
*/
|
|
208
|
-
broadcastEnumsUpdate() {
|
|
209
|
-
this.broadcast({
|
|
210
|
-
type: "hmr:enums-update"
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* Broadcast CMS content update notification
|
|
215
|
-
*/
|
|
216
|
-
broadcastCMSUpdate(collection) {
|
|
217
|
-
this.broadcast({
|
|
218
|
-
type: "hmr:cms-update",
|
|
219
|
-
collection
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
/**
|
|
223
|
-
* Broadcast CMS collections-list update notification.
|
|
224
|
-
* Emitted when a template file is added, removed, or its schema changes —
|
|
225
|
-
* tells connected clients to re-fetch the collections list.
|
|
226
|
-
*/
|
|
227
|
-
broadcastCollectionsUpdate() {
|
|
228
|
-
this.broadcast({
|
|
229
|
-
type: "hmr:cms-collections-update"
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
/**
|
|
233
|
-
* Broadcast project.config.json update notification.
|
|
234
|
-
* Emitted when project.config.json changes (e.g. an AI tool adds a new
|
|
235
|
-
* locale) — tells connected clients to re-fetch config-derived state
|
|
236
|
-
* such as the i18n locale list.
|
|
237
|
-
*/
|
|
238
|
-
broadcastConfigUpdate() {
|
|
239
|
-
this.broadcast({
|
|
240
|
-
type: "hmr:config-update"
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
/**
|
|
244
|
-
* Get number of connected clients
|
|
245
|
-
*/
|
|
246
|
-
getClientCount() {
|
|
247
|
-
return this.clients.size;
|
|
248
|
-
}
|
|
249
|
-
/**
|
|
250
|
-
* Clear all clients
|
|
251
|
-
*/
|
|
252
|
-
clear() {
|
|
253
|
-
this.clients.clear();
|
|
254
|
-
}
|
|
255
|
-
/**
|
|
256
|
-
* Get all clients
|
|
257
|
-
*/
|
|
258
|
-
getClients() {
|
|
259
|
-
return new Set(this.clients);
|
|
260
|
-
}
|
|
261
|
-
/**
|
|
262
|
-
* Check if any clients are connected
|
|
263
|
-
*/
|
|
264
|
-
hasClients() {
|
|
265
|
-
return this.clients.size > 0;
|
|
266
|
-
}
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
// lib/server/utils/jsonLineMapper.ts
|
|
270
|
-
function buildLineMap(jsonText) {
|
|
271
|
-
const lineMap = /* @__PURE__ */ new Map();
|
|
272
|
-
const lines = jsonText.split("\n");
|
|
273
|
-
const charToLine = [];
|
|
274
|
-
let currentLine = 1;
|
|
275
|
-
for (let i = 0; i < jsonText.length; i++) {
|
|
276
|
-
charToLine[i] = currentLine;
|
|
277
|
-
if (jsonText[i] === "\n") {
|
|
278
|
-
currentLine++;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
let pos = 0;
|
|
282
|
-
function skipWhitespace() {
|
|
283
|
-
while (pos < jsonText.length && /\s/.test(jsonText[pos])) {
|
|
284
|
-
pos++;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
function parseString() {
|
|
288
|
-
if (jsonText[pos] !== '"') return "";
|
|
289
|
-
pos++;
|
|
290
|
-
let result = "";
|
|
291
|
-
while (pos < jsonText.length && jsonText[pos] !== '"') {
|
|
292
|
-
if (jsonText[pos] === "\\") {
|
|
293
|
-
pos++;
|
|
294
|
-
if (pos < jsonText.length) {
|
|
295
|
-
result += jsonText[pos];
|
|
296
|
-
pos++;
|
|
297
|
-
}
|
|
298
|
-
} else {
|
|
299
|
-
result += jsonText[pos];
|
|
300
|
-
pos++;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
pos++;
|
|
304
|
-
return result;
|
|
305
|
-
}
|
|
306
|
-
function parseNumber() {
|
|
307
|
-
while (pos < jsonText.length && /[\d.eE\-+]/.test(jsonText[pos])) {
|
|
308
|
-
pos++;
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
function parseValue(path2, trackChildren) {
|
|
312
|
-
skipWhitespace();
|
|
313
|
-
const startPos = pos;
|
|
314
|
-
if (jsonText[pos] === "{") {
|
|
315
|
-
parseObject(path2, trackChildren);
|
|
316
|
-
} else if (jsonText[pos] === "[") {
|
|
317
|
-
parseArray(path2, trackChildren);
|
|
318
|
-
} else if (jsonText[pos] === '"') {
|
|
319
|
-
parseString();
|
|
320
|
-
} else if (jsonText[pos] === "t") {
|
|
321
|
-
pos += 4;
|
|
322
|
-
} else if (jsonText[pos] === "f") {
|
|
323
|
-
pos += 5;
|
|
324
|
-
} else if (jsonText[pos] === "n") {
|
|
325
|
-
pos += 4;
|
|
326
|
-
} else {
|
|
327
|
-
parseNumber();
|
|
328
|
-
}
|
|
329
|
-
return { start: startPos, end: pos };
|
|
330
|
-
}
|
|
331
|
-
function recordTrackedRoot(path2) {
|
|
332
|
-
const { start, end } = parseValue(path2, true);
|
|
333
|
-
lineMap.set("", {
|
|
334
|
-
startLine: charToLine[start],
|
|
335
|
-
endLine: charToLine[end - 1] || charToLine[start]
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
function parseObject(path2, trackChildren) {
|
|
339
|
-
pos++;
|
|
340
|
-
skipWhitespace();
|
|
341
|
-
while (pos < jsonText.length && jsonText[pos] !== "}") {
|
|
342
|
-
skipWhitespace();
|
|
343
|
-
const key = parseString();
|
|
344
|
-
skipWhitespace();
|
|
345
|
-
pos++;
|
|
346
|
-
skipWhitespace();
|
|
347
|
-
if (key === "root" && path2.length === 0) {
|
|
348
|
-
recordTrackedRoot(path2);
|
|
349
|
-
} else if (key === "component" && path2.length === 0) {
|
|
350
|
-
parseComponentWrapper(path2);
|
|
351
|
-
} else if (key === "children" && trackChildren) {
|
|
352
|
-
parseValue(path2, true);
|
|
353
|
-
} else {
|
|
354
|
-
parseValue(path2, false);
|
|
355
|
-
}
|
|
356
|
-
skipWhitespace();
|
|
357
|
-
if (jsonText[pos] === ",") pos++;
|
|
358
|
-
skipWhitespace();
|
|
359
|
-
}
|
|
360
|
-
pos++;
|
|
361
|
-
}
|
|
362
|
-
function parseComponentWrapper(path2) {
|
|
363
|
-
if (jsonText[pos] !== "{") {
|
|
364
|
-
parseValue(path2, false);
|
|
365
|
-
return;
|
|
366
|
-
}
|
|
367
|
-
pos++;
|
|
368
|
-
skipWhitespace();
|
|
369
|
-
while (pos < jsonText.length && jsonText[pos] !== "}") {
|
|
370
|
-
skipWhitespace();
|
|
371
|
-
const key = parseString();
|
|
372
|
-
skipWhitespace();
|
|
373
|
-
pos++;
|
|
374
|
-
skipWhitespace();
|
|
375
|
-
if (key === "structure") {
|
|
376
|
-
recordTrackedRoot(path2);
|
|
377
|
-
} else {
|
|
378
|
-
parseValue(path2, false);
|
|
379
|
-
}
|
|
380
|
-
skipWhitespace();
|
|
381
|
-
if (jsonText[pos] === ",") pos++;
|
|
382
|
-
skipWhitespace();
|
|
383
|
-
}
|
|
384
|
-
pos++;
|
|
385
|
-
}
|
|
386
|
-
function parseArray(path2, trackChildren) {
|
|
387
|
-
pos++;
|
|
388
|
-
skipWhitespace();
|
|
389
|
-
let index = 0;
|
|
390
|
-
while (pos < jsonText.length && jsonText[pos] !== "]") {
|
|
391
|
-
const childPath = [...path2, index];
|
|
392
|
-
const { start, end } = parseValue(childPath, trackChildren);
|
|
393
|
-
if (trackChildren) {
|
|
394
|
-
const pathStr = childPath.join(",");
|
|
395
|
-
lineMap.set(pathStr, {
|
|
396
|
-
startLine: charToLine[start],
|
|
397
|
-
endLine: charToLine[end - 1] || charToLine[start]
|
|
398
|
-
});
|
|
399
|
-
}
|
|
400
|
-
index++;
|
|
401
|
-
skipWhitespace();
|
|
402
|
-
if (jsonText[pos] === ",") pos++;
|
|
403
|
-
skipWhitespace();
|
|
404
|
-
}
|
|
405
|
-
pos++;
|
|
406
|
-
}
|
|
407
|
-
try {
|
|
408
|
-
skipWhitespace();
|
|
409
|
-
parseValue([], false);
|
|
410
|
-
} catch (e) {
|
|
411
|
-
console.error("Failed to build line map:", e);
|
|
412
|
-
}
|
|
413
|
-
return lineMap;
|
|
414
|
-
}
|
|
415
|
-
function lineMapToObject(lineMap) {
|
|
416
|
-
const obj = {};
|
|
417
|
-
lineMap.forEach((value, key) => {
|
|
418
|
-
obj[key] = value;
|
|
419
|
-
});
|
|
420
|
-
return obj;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
// lib/server/services/pageService.ts
|
|
424
|
-
import { existsSync, readdirSync, mkdirSync, rmdirSync } from "fs";
|
|
425
|
-
import { join } from "path";
|
|
426
|
-
var PageService = class {
|
|
427
|
-
/**
|
|
428
|
-
* Creates a new PageService instance
|
|
429
|
-
*
|
|
430
|
-
* @param pageCache - PageCache instance to use for caching pages
|
|
431
|
-
* @param provider - Optional PageProvider for loading pages (enables DI for testing/CMS)
|
|
432
|
-
*/
|
|
433
|
-
constructor(pageCache, provider) {
|
|
434
|
-
this.pageCache = pageCache;
|
|
435
|
-
this.provider = provider;
|
|
436
|
-
}
|
|
437
|
-
/**
|
|
438
|
-
* Set the page provider
|
|
439
|
-
* Allows setting provider after construction for backward compatibility
|
|
440
|
-
*
|
|
441
|
-
* @param provider - PageProvider implementation
|
|
442
|
-
*/
|
|
443
|
-
setProvider(provider) {
|
|
444
|
-
this.provider = provider;
|
|
445
|
-
}
|
|
446
|
-
/**
|
|
447
|
-
* Load all pages from the data provider
|
|
448
|
-
*
|
|
449
|
-
* Uses the provider to load pages. If no provider is set, does nothing.
|
|
450
|
-
* Pages are cached in memory for fast access.
|
|
451
|
-
*
|
|
452
|
-
* @returns Promise that resolves when all pages are loaded
|
|
453
|
-
*
|
|
454
|
-
* @example
|
|
455
|
-
* ```typescript
|
|
456
|
-
* await pageService.loadAllPages();
|
|
457
|
-
* const paths = pageService.getAllPagePaths();
|
|
458
|
-
* ```
|
|
459
|
-
*/
|
|
460
|
-
async loadAllPages() {
|
|
461
|
-
if (!this.provider) {
|
|
462
|
-
return;
|
|
463
|
-
}
|
|
464
|
-
const pages = await this.provider.loadAll();
|
|
465
|
-
for (const [path2, content] of pages) {
|
|
466
|
-
const lineMap = buildLineMap(content);
|
|
467
|
-
this.pageCache.set(path2, content, lineMap);
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
/**
|
|
471
|
-
* Get page content from cache
|
|
472
|
-
*
|
|
473
|
-
* @param path - Page path (e.g., "/" or "/about")
|
|
474
|
-
* @returns Raw JSON string content, or undefined if page not found
|
|
475
|
-
*
|
|
476
|
-
* @example
|
|
477
|
-
* ```typescript
|
|
478
|
-
* const content = pageService.getPage('/');
|
|
479
|
-
* if (content) {
|
|
480
|
-
* const data = JSON.parse(content);
|
|
481
|
-
* }
|
|
482
|
-
* ```
|
|
483
|
-
*/
|
|
484
|
-
getPage(path2) {
|
|
485
|
-
return this.pageCache.getContent(path2);
|
|
486
|
-
}
|
|
487
|
-
/**
|
|
488
|
-
* Get parsed page data
|
|
489
|
-
*
|
|
490
|
-
* Retrieves page content from cache and parses it as JSON. Returns null if
|
|
491
|
-
* the page doesn't exist or if parsing fails.
|
|
492
|
-
*
|
|
493
|
-
* @param path - Page path (e.g., "/" or "/about")
|
|
494
|
-
* @returns Parsed JSONPage object, or null if not found or invalid
|
|
495
|
-
*
|
|
496
|
-
* @example
|
|
497
|
-
* ```typescript
|
|
498
|
-
* const pageData = pageService.getPageData('/');
|
|
499
|
-
* if (pageData) {
|
|
500
|
-
* console.log(pageData.meta.title);
|
|
501
|
-
* }
|
|
502
|
-
* ```
|
|
503
|
-
*/
|
|
504
|
-
getPageData(path2) {
|
|
505
|
-
const content = this.pageCache.getContent(path2);
|
|
506
|
-
if (!content) {
|
|
507
|
-
return null;
|
|
508
|
-
}
|
|
509
|
-
try {
|
|
510
|
-
return parseJSON(content);
|
|
511
|
-
} catch {
|
|
512
|
-
return null;
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
/**
|
|
516
|
-
* Save page content
|
|
517
|
-
*
|
|
518
|
-
* Saves page data via the provider and updates the cache. The page path
|
|
519
|
-
* is converted to a filename (e.g., "/" -> "index.json", "/about" -> "about.json").
|
|
520
|
-
*
|
|
521
|
-
* @param path - Page path (e.g., "/" or "/about")
|
|
522
|
-
* @param data - JSONPage object to save
|
|
523
|
-
* @returns Promise that resolves when the page is saved
|
|
524
|
-
*
|
|
525
|
-
* @throws {Error} If provider is not set or file write fails
|
|
526
|
-
*
|
|
527
|
-
* @example
|
|
528
|
-
* ```typescript
|
|
529
|
-
* await pageService.savePage('/', {
|
|
530
|
-
* meta: { title: 'Home' },
|
|
531
|
-
* root: { type: 'node', tag: 'div', children: [] }
|
|
532
|
-
* });
|
|
533
|
-
* ```
|
|
534
|
-
*/
|
|
535
|
-
async savePage(path2, data) {
|
|
536
|
-
if (!this.provider) {
|
|
537
|
-
throw new Error("PageProvider not set");
|
|
538
|
-
}
|
|
539
|
-
const { _lineMap, ...dataToSave } = data;
|
|
540
|
-
const content = JSON.stringify(dataToSave, null, 2);
|
|
541
|
-
await this.provider.save(path2, content);
|
|
542
|
-
const lineMap = buildLineMap(content);
|
|
543
|
-
this.pageCache.set(path2, content, lineMap);
|
|
544
|
-
}
|
|
545
|
-
/**
|
|
546
|
-
* Re-read a single page from disk via the provider and refresh the cache.
|
|
547
|
-
*
|
|
548
|
-
* Used by the file watcher for live reload. Provider-based (not a hardcoded
|
|
549
|
-
* JSON read), so it works for any format — the FileSystem provider returns the
|
|
550
|
-
* raw JSON and the Astro provider returns the parsed model serialized to JSON.
|
|
551
|
-
*
|
|
552
|
-
* @returns true if the cache changed (page updated or confirmed-deleted) and a
|
|
553
|
-
* reload broadcast is warranted; false if the read failed transiently (e.g. a
|
|
554
|
-
* partial mid-write or parse error), in which case the cache is left intact and
|
|
555
|
-
* the next watcher event retries.
|
|
556
|
-
*/
|
|
557
|
-
async reloadPageFromDisk(path2) {
|
|
558
|
-
if (!this.provider) return false;
|
|
559
|
-
let content = null;
|
|
560
|
-
try {
|
|
561
|
-
content = await this.provider.get(path2);
|
|
562
|
-
} catch {
|
|
563
|
-
return false;
|
|
564
|
-
}
|
|
565
|
-
if (content) {
|
|
566
|
-
this.pageCache.set(path2, content, buildLineMap(content));
|
|
567
|
-
return true;
|
|
568
|
-
}
|
|
569
|
-
try {
|
|
570
|
-
if (!await this.provider.exists(path2)) {
|
|
571
|
-
this.pageCache.delete(path2);
|
|
572
|
-
return true;
|
|
573
|
-
}
|
|
574
|
-
} catch {
|
|
575
|
-
}
|
|
576
|
-
return false;
|
|
577
|
-
}
|
|
578
|
-
/**
|
|
579
|
-
* Delete page from cache and optionally from storage
|
|
580
|
-
*
|
|
581
|
-
* Removes a page from the in-memory cache. If provider is set,
|
|
582
|
-
* also deletes from the underlying storage.
|
|
583
|
-
*
|
|
584
|
-
* @param path - Page path to delete
|
|
585
|
-
* @param deleteFromStorage - If true and provider is set, also delete from storage
|
|
586
|
-
*
|
|
587
|
-
* @example
|
|
588
|
-
* ```typescript
|
|
589
|
-
* pageService.deletePage('/old-page');
|
|
590
|
-
* // Or with storage deletion:
|
|
591
|
-
* await pageService.deletePage('/old-page', true);
|
|
592
|
-
* ```
|
|
593
|
-
*/
|
|
594
|
-
async deletePage(path2, deleteFromStorage = false) {
|
|
595
|
-
this.pageCache.delete(path2);
|
|
596
|
-
if (deleteFromStorage && this.provider) {
|
|
597
|
-
await this.provider.delete(path2);
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
/**
|
|
601
|
-
* Get all page paths
|
|
602
|
-
*
|
|
603
|
-
* Returns an array of all page paths currently in the cache.
|
|
604
|
-
*
|
|
605
|
-
* @returns Array of page paths (e.g., ["/", "/about", "/contact"])
|
|
606
|
-
*
|
|
607
|
-
* @example
|
|
608
|
-
* ```typescript
|
|
609
|
-
* const paths = pageService.getAllPagePaths();
|
|
610
|
-
* console.log(`Loaded ${paths.length} pages`);
|
|
611
|
-
* ```
|
|
612
|
-
*/
|
|
613
|
-
getAllPagePaths() {
|
|
614
|
-
return this.pageCache.keys();
|
|
615
|
-
}
|
|
616
|
-
/**
|
|
617
|
-
* Get all pages with their info (including draft status)
|
|
618
|
-
*
|
|
619
|
-
* Returns an array of page info objects including path and draft status.
|
|
620
|
-
*
|
|
621
|
-
* @returns Array of page info objects
|
|
622
|
-
*
|
|
623
|
-
* @example
|
|
624
|
-
* ```typescript
|
|
625
|
-
* const pages = pageService.getAllPagesWithInfo();
|
|
626
|
-
* // [{ path: "/", isDraft: false }, { path: "/about", isDraft: true }]
|
|
627
|
-
* ```
|
|
628
|
-
*/
|
|
629
|
-
getAllPagesWithInfo() {
|
|
630
|
-
const paths = this.pageCache.keys().filter((p) => !p.startsWith("/_sketch/"));
|
|
631
|
-
return paths.map((path2) => {
|
|
632
|
-
const pageData = this.getPageData(path2);
|
|
633
|
-
const pathWithoutSlash = path2 === "/" ? "index" : path2.substring(1);
|
|
634
|
-
const slashIndex = pathWithoutSlash.lastIndexOf("/");
|
|
635
|
-
const folder = slashIndex > 0 ? pathWithoutSlash.substring(0, slashIndex) : void 0;
|
|
636
|
-
return {
|
|
637
|
-
path: path2,
|
|
638
|
-
isDraft: pageData?.meta?.draft === true,
|
|
639
|
-
...folder ? { folder } : {}
|
|
640
|
-
};
|
|
641
|
-
});
|
|
642
|
-
}
|
|
643
|
-
/**
|
|
644
|
-
* Get line map for a page
|
|
645
|
-
*
|
|
646
|
-
* Returns the line number mapping for elements in the page JSON.
|
|
647
|
-
*
|
|
648
|
-
* @param path - Page path (e.g., "/" or "/about")
|
|
649
|
-
* @returns LineMap object or undefined if page not found
|
|
650
|
-
*/
|
|
651
|
-
getLineMap(path2) {
|
|
652
|
-
return this.pageCache.getLineMap(path2);
|
|
653
|
-
}
|
|
654
|
-
/**
|
|
655
|
-
* Pages base directory — provider-aware (e.g. `src/pages` for astro projects),
|
|
656
|
-
* falling back to `projectPaths.pages()` when the provider doesn't expose
|
|
657
|
-
* `baseDir()` (or no provider is set). Used by the folder/move/rename ops that
|
|
658
|
-
* touch the filesystem directly.
|
|
659
|
-
*/
|
|
660
|
-
pagesBaseDir() {
|
|
661
|
-
return this.provider?.baseDir?.() ?? projectPaths.pages();
|
|
662
|
-
}
|
|
663
|
-
/** Page file extension (e.g. `.astro` for astro projects), defaulting to `.json`. */
|
|
664
|
-
pageExt() {
|
|
665
|
-
return this.provider?.extension?.() ?? ".json";
|
|
666
|
-
}
|
|
667
|
-
/**
|
|
668
|
-
* Get all page folders
|
|
669
|
-
*
|
|
670
|
-
* Scans the pages directory for subdirectories.
|
|
671
|
-
*
|
|
672
|
-
* @returns Sorted array of folder names
|
|
673
|
-
*/
|
|
674
|
-
getAllFolders() {
|
|
675
|
-
const pagesDir = this.pagesBaseDir();
|
|
676
|
-
if (!existsSync(pagesDir)) {
|
|
677
|
-
return [];
|
|
678
|
-
}
|
|
679
|
-
const collectFolders = (dir, prefix) => {
|
|
680
|
-
try {
|
|
681
|
-
const entries = readdirSync(dir, { withFileTypes: true });
|
|
682
|
-
const result = [];
|
|
683
|
-
for (const entry of entries) {
|
|
684
|
-
if (!entry.isDirectory()) continue;
|
|
685
|
-
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
686
|
-
result.push(relativePath);
|
|
687
|
-
result.push(...collectFolders(join(dir, entry.name), relativePath));
|
|
688
|
-
}
|
|
689
|
-
return result;
|
|
690
|
-
} catch {
|
|
691
|
-
return [];
|
|
692
|
-
}
|
|
693
|
-
};
|
|
694
|
-
return collectFolders(pagesDir, "").sort();
|
|
695
|
-
}
|
|
696
|
-
/**
|
|
697
|
-
* Create a page folder
|
|
698
|
-
*
|
|
699
|
-
* Creates an empty folder in the pages directory.
|
|
700
|
-
*
|
|
701
|
-
* @param folderName - Name of the folder to create
|
|
702
|
-
* @throws {Error} If folder name is invalid or already exists
|
|
703
|
-
*/
|
|
704
|
-
async createFolder(folderName) {
|
|
705
|
-
if (!folderName || folderName.trim() === "") {
|
|
706
|
-
throw new Error("Folder name cannot be empty");
|
|
707
|
-
}
|
|
708
|
-
const trimmed = folderName.trim();
|
|
709
|
-
const segments = trimmed.split("/");
|
|
710
|
-
const segmentRegex = /^[a-z0-9][a-z0-9-_]*$/;
|
|
711
|
-
for (const segment of segments) {
|
|
712
|
-
if (!segment) throw new Error("Folder path cannot have empty segments");
|
|
713
|
-
if (!segmentRegex.test(segment)) {
|
|
714
|
-
throw new Error("Each folder segment can only contain lowercase letters, numbers, dashes, and underscores");
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
const pagesDir = this.pagesBaseDir();
|
|
718
|
-
const folderPath = join(pagesDir, trimmed);
|
|
719
|
-
if (existsSync(folderPath)) {
|
|
720
|
-
throw new Error("Folder already exists");
|
|
721
|
-
}
|
|
722
|
-
mkdirSync(folderPath, { recursive: true });
|
|
723
|
-
}
|
|
724
|
-
/**
|
|
725
|
-
* Move a page to a different folder
|
|
726
|
-
*
|
|
727
|
-
* Moves a page file from its current location to the target folder.
|
|
728
|
-
* If folder is empty string, moves to root pages directory.
|
|
729
|
-
*
|
|
730
|
-
* @param pagePath - Current page path (e.g., "/blog/post" or "/about")
|
|
731
|
-
* @param newFolder - Target folder name (empty string for root)
|
|
732
|
-
*/
|
|
733
|
-
async movePage(pagePath, newFolder) {
|
|
734
|
-
const { rename } = await import("fs/promises");
|
|
735
|
-
const pagesDir = this.pagesBaseDir();
|
|
736
|
-
const ext = this.pageExt();
|
|
737
|
-
const pageName = pagePath === "/" ? "index" : pagePath.substring(1);
|
|
738
|
-
const slashIndex = pageName.lastIndexOf("/");
|
|
739
|
-
const baseName = slashIndex >= 0 ? pageName.substring(slashIndex + 1) : pageName;
|
|
740
|
-
const sourceFile = join(pagesDir, `${pageName}${ext}`);
|
|
741
|
-
const targetFile = newFolder ? join(pagesDir, newFolder, `${baseName}${ext}`) : join(pagesDir, `${baseName}${ext}`);
|
|
742
|
-
if (newFolder) {
|
|
743
|
-
const targetDir = join(pagesDir, newFolder);
|
|
744
|
-
if (!existsSync(targetDir)) {
|
|
745
|
-
mkdirSync(targetDir, { recursive: true });
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
if (!existsSync(sourceFile)) {
|
|
749
|
-
throw new Error(`Page not found: ${pagePath}`);
|
|
750
|
-
}
|
|
751
|
-
await rename(sourceFile, targetFile);
|
|
752
|
-
const content = this.pageCache.getContent(pagePath);
|
|
753
|
-
if (content) {
|
|
754
|
-
this.pageCache.delete(pagePath);
|
|
755
|
-
const newPageName = newFolder ? `${newFolder}/${baseName}` : baseName;
|
|
756
|
-
const newPath = newPageName === "index" ? "/" : `/${newPageName}`;
|
|
757
|
-
const lineMap = buildLineMap(content);
|
|
758
|
-
this.pageCache.set(newPath, content, lineMap);
|
|
759
|
-
}
|
|
760
|
-
const sourceDir = slashIndex >= 0 ? join(pagesDir, pageName.substring(0, slashIndex)) : null;
|
|
761
|
-
if (sourceDir && sourceDir !== pagesDir) {
|
|
762
|
-
try {
|
|
763
|
-
const remaining = readdirSync(sourceDir);
|
|
764
|
-
if (remaining.length === 0) {
|
|
765
|
-
rmdirSync(sourceDir);
|
|
766
|
-
}
|
|
767
|
-
} catch {
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
/**
|
|
772
|
-
* Rename a page (file rename + cache update).
|
|
773
|
-
*
|
|
774
|
-
* Renames the underlying .json file from `oldPath`'s filename to `newPath`'s
|
|
775
|
-
* filename, keeping the same folder. Updates the in-memory cache so the
|
|
776
|
-
* page is now keyed at `newPath`. Folder moves stay with `movePage()` —
|
|
777
|
-
* this is purely "change the URL slug of an existing page" for
|
|
778
|
-
* single-locale projects.
|
|
779
|
-
*
|
|
780
|
-
* @param oldPath - Current page path (e.g., "/about" or "/blog/post")
|
|
781
|
-
* @param newPath - New page path with same folder (e.g., "/about-us" or "/blog/new-slug")
|
|
782
|
-
* @throws {Error} If oldPath doesn't exist, newPath already exists, or paths
|
|
783
|
-
* reference different folders.
|
|
784
|
-
*/
|
|
785
|
-
async renamePage(oldPath, newPath) {
|
|
786
|
-
if (oldPath === newPath) return;
|
|
787
|
-
if (newPath === "/") {
|
|
788
|
-
throw new Error("Cannot rename a page to the index path");
|
|
789
|
-
}
|
|
790
|
-
if (!newPath.startsWith("/")) {
|
|
791
|
-
throw new Error('newPath must start with "/"');
|
|
792
|
-
}
|
|
793
|
-
const oldName = oldPath === "/" ? "index" : oldPath.substring(1);
|
|
794
|
-
const newName = newPath.substring(1);
|
|
795
|
-
const oldSlash = oldName.lastIndexOf("/");
|
|
796
|
-
const newSlash = newName.lastIndexOf("/");
|
|
797
|
-
const oldFolder = oldSlash >= 0 ? oldName.substring(0, oldSlash) : "";
|
|
798
|
-
const newFolder = newSlash >= 0 ? newName.substring(0, newSlash) : "";
|
|
799
|
-
if (oldFolder !== newFolder) {
|
|
800
|
-
throw new Error("renamePage only changes the filename; use movePage to change folder");
|
|
801
|
-
}
|
|
802
|
-
const baseSegment = newSlash >= 0 ? newName.substring(newSlash + 1) : newName;
|
|
803
|
-
if (!/^[a-z0-9][a-z0-9-_]*$/.test(baseSegment)) {
|
|
804
|
-
throw new Error("Page slug can only contain lowercase letters, numbers, dashes, and underscores");
|
|
805
|
-
}
|
|
806
|
-
const { rename } = await import("fs/promises");
|
|
807
|
-
const pagesDir = this.pagesBaseDir();
|
|
808
|
-
const ext = this.pageExt();
|
|
809
|
-
const sourceFile = join(pagesDir, `${oldName}${ext}`);
|
|
810
|
-
const targetFile = join(pagesDir, `${newName}${ext}`);
|
|
811
|
-
if (!existsSync(sourceFile)) {
|
|
812
|
-
throw new Error(`Page not found: ${oldPath}`);
|
|
813
|
-
}
|
|
814
|
-
if (existsSync(targetFile)) {
|
|
815
|
-
throw new Error(`Page already exists: ${newPath}`);
|
|
816
|
-
}
|
|
817
|
-
await rename(sourceFile, targetFile);
|
|
818
|
-
const content = this.pageCache.getContent(oldPath);
|
|
819
|
-
if (content) {
|
|
820
|
-
this.pageCache.delete(oldPath);
|
|
821
|
-
const lineMap = buildLineMap(content);
|
|
822
|
-
this.pageCache.set(newPath, content, lineMap);
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
/**
|
|
826
|
-
* Get slug mappings for all pages
|
|
827
|
-
*
|
|
828
|
-
* Extracts slug translations from all pages' meta.slugs fields.
|
|
829
|
-
* Returns a list of { pageId, slugs } objects for locale-aware routing.
|
|
830
|
-
*
|
|
831
|
-
* @returns Array of SlugMap objects with pageId and translated slugs
|
|
832
|
-
*
|
|
833
|
-
* @example
|
|
834
|
-
* ```typescript
|
|
835
|
-
* const mappings = pageService.getSlugMappings();
|
|
836
|
-
* // [{ pageId: "about", slugs: { "en": "about", "pl": "o-nas" } }]
|
|
837
|
-
* ```
|
|
838
|
-
*/
|
|
839
|
-
getSlugMappings() {
|
|
840
|
-
const mappings = [];
|
|
841
|
-
const paths = this.pageCache.keys();
|
|
842
|
-
for (const path2 of paths) {
|
|
843
|
-
const pageData = this.getPageData(path2);
|
|
844
|
-
if (pageData?.meta?.slugs) {
|
|
845
|
-
const pageId = path2 === "/" ? "index" : path2.substring(1);
|
|
846
|
-
mappings.push({
|
|
847
|
-
pageId,
|
|
848
|
-
slugs: pageData.meta.slugs
|
|
849
|
-
});
|
|
850
|
-
} else {
|
|
851
|
-
const pageId = path2 === "/" ? "index" : path2.substring(1);
|
|
852
|
-
const defaultSlug = path2 === "/" ? "" : path2.substring(1);
|
|
853
|
-
mappings.push({
|
|
854
|
-
pageId,
|
|
855
|
-
slugs: { _default: defaultSlug }
|
|
856
|
-
});
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
return mappings;
|
|
860
|
-
}
|
|
861
|
-
};
|
|
862
|
-
|
|
863
|
-
// lib/server/services/componentService.ts
|
|
864
|
-
import { join as join2 } from "path";
|
|
865
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync as readdirSync2, rmdirSync as rmdirSync2 } from "fs";
|
|
866
|
-
var ComponentService = class {
|
|
867
|
-
components = /* @__PURE__ */ new Map();
|
|
868
|
-
componentCategories = /* @__PURE__ */ new Map();
|
|
869
|
-
fs;
|
|
870
|
-
loader;
|
|
871
|
-
writer;
|
|
872
|
-
loadErrors = /* @__PURE__ */ new Map();
|
|
873
|
-
loadWarnings = [];
|
|
874
|
-
/**
|
|
875
|
-
* Creates a new ComponentService instance
|
|
876
|
-
*
|
|
877
|
-
* @param options - Optional configuration for dependency injection
|
|
878
|
-
* @param options.fs - Optional file system interface for testing
|
|
879
|
-
* @param options.loader - Optional component loader interface for testing (READ)
|
|
880
|
-
* @param options.writer - Optional component writer for non-JSON on-disk formats
|
|
881
|
-
* (WRITE). When omitted, the service writes JSON exactly as before — legacy
|
|
882
|
-
* projects are byte-identical. The astro bootstrap injects an AstroComponentWriter
|
|
883
|
-
* so saves emit `.astro` files under `src/components/`.
|
|
884
|
-
*/
|
|
885
|
-
constructor(options) {
|
|
886
|
-
this.fs = options?.fs;
|
|
887
|
-
this.loader = options?.loader;
|
|
888
|
-
this.writer = options?.writer;
|
|
889
|
-
}
|
|
890
|
-
/**
|
|
891
|
-
* Base components directory — `<root>/components` for JSON, or whatever the
|
|
892
|
-
* injected writer owns (e.g. `<root>/src/components` for astro). All path
|
|
893
|
-
* resolution (categories, folders, save/move/rename targets) routes through
|
|
894
|
-
* here so a single switch covers every write op.
|
|
895
|
-
* @internal
|
|
896
|
-
*/
|
|
897
|
-
componentsBaseDir() {
|
|
898
|
-
return this.writer ? this.writer.componentsDir() : projectPaths.components();
|
|
899
|
-
}
|
|
900
|
-
/**
|
|
901
|
-
* Load all components from the components directory
|
|
902
|
-
*
|
|
903
|
-
* Scans the ./components directory recursively and loads all component definitions.
|
|
904
|
-
* Subdirectories are treated as category folders. Clears existing components before loading.
|
|
905
|
-
* Components are loaded from .json files, and associated .js and .css files are
|
|
906
|
-
* automatically loaded if they exist.
|
|
907
|
-
*
|
|
908
|
-
* @returns Promise that resolves when all components are loaded
|
|
909
|
-
*
|
|
910
|
-
* @example
|
|
911
|
-
* ```typescript
|
|
912
|
-
* await componentService.loadAllComponents();
|
|
913
|
-
* const count = Object.keys(componentService.getAllComponents()).length;
|
|
914
|
-
* ```
|
|
915
|
-
*/
|
|
916
|
-
async loadAllComponents() {
|
|
917
|
-
const nextComponents = /* @__PURE__ */ new Map();
|
|
918
|
-
const nextCategories = /* @__PURE__ */ new Map();
|
|
919
|
-
const nextErrors = /* @__PURE__ */ new Map();
|
|
920
|
-
let nextWarnings = [];
|
|
921
|
-
let loadedComponents;
|
|
922
|
-
if (this.loader) {
|
|
923
|
-
loadedComponents = await this.loader.loadDirectory(projectPaths.components());
|
|
924
|
-
} else {
|
|
925
|
-
const result = await loadComponentDirectory(projectPaths.components());
|
|
926
|
-
loadedComponents = result.components;
|
|
927
|
-
nextWarnings = result.warnings;
|
|
928
|
-
for (const error of result.errors) {
|
|
929
|
-
nextErrors.set(error.componentName, error);
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
loadedComponents.forEach((value, key) => {
|
|
933
|
-
const componentWithCategory = value;
|
|
934
|
-
const category = componentWithCategory._category;
|
|
935
|
-
nextCategories.set(key, category);
|
|
936
|
-
const { _category, _relativePath, ...cleanDef } = componentWithCategory;
|
|
937
|
-
nextComponents.set(key, cleanDef);
|
|
938
|
-
});
|
|
939
|
-
this.components = nextComponents;
|
|
940
|
-
this.componentCategories = nextCategories;
|
|
941
|
-
this.loadErrors = nextErrors;
|
|
942
|
-
this.loadWarnings = nextWarnings;
|
|
943
|
-
}
|
|
944
|
-
/**
|
|
945
|
-
* Get component by name
|
|
946
|
-
*
|
|
947
|
-
* @param name - Component name (without .json extension)
|
|
948
|
-
* @returns ComponentDefinition if found, undefined otherwise
|
|
949
|
-
*
|
|
950
|
-
* @example
|
|
951
|
-
* ```typescript
|
|
952
|
-
* const button = componentService.getComponent('Button');
|
|
953
|
-
* if (button) {
|
|
954
|
-
* console.log(button.component.interface);
|
|
955
|
-
* }
|
|
956
|
-
* ```
|
|
957
|
-
*/
|
|
958
|
-
getComponent(name) {
|
|
959
|
-
return this.components.get(name);
|
|
960
|
-
}
|
|
961
|
-
/**
|
|
962
|
-
* Check if component exists
|
|
963
|
-
*
|
|
964
|
-
* @param name - Component name to check
|
|
965
|
-
* @returns True if component exists in cache, false otherwise
|
|
966
|
-
*
|
|
967
|
-
* @example
|
|
968
|
-
* ```typescript
|
|
969
|
-
* if (componentService.hasComponent('Button')) {
|
|
970
|
-
* // Component is available
|
|
971
|
-
* }
|
|
972
|
-
* ```
|
|
973
|
-
*/
|
|
974
|
-
hasComponent(name) {
|
|
975
|
-
return this.components.has(name);
|
|
976
|
-
}
|
|
977
|
-
/**
|
|
978
|
-
* Get all components as a Record
|
|
979
|
-
*
|
|
980
|
-
* Returns all loaded components as a plain object for easy iteration
|
|
981
|
-
* or serialization.
|
|
982
|
-
*
|
|
983
|
-
* @returns Record mapping component names to ComponentDefinition objects
|
|
984
|
-
*
|
|
985
|
-
* @example
|
|
986
|
-
* ```typescript
|
|
987
|
-
* const allComponents = componentService.getAllComponents();
|
|
988
|
-
* Object.keys(allComponents).forEach(name => {
|
|
989
|
-
* console.log(`Component: ${name}`);
|
|
990
|
-
* });
|
|
991
|
-
* ```
|
|
992
|
-
*/
|
|
993
|
-
getAllComponents() {
|
|
994
|
-
const record = {};
|
|
995
|
-
this.components.forEach((value, key) => {
|
|
996
|
-
record[key] = value;
|
|
997
|
-
});
|
|
998
|
-
return record;
|
|
999
|
-
}
|
|
1000
|
-
/**
|
|
1001
|
-
* Get all components with their category information
|
|
1002
|
-
*
|
|
1003
|
-
* Returns all loaded components with category metadata derived from folder structure.
|
|
1004
|
-
*
|
|
1005
|
-
* @returns Record mapping component names to ComponentInfo objects (definition + category)
|
|
1006
|
-
*
|
|
1007
|
-
* @example
|
|
1008
|
-
* ```typescript
|
|
1009
|
-
* const components = componentService.getAllComponentsWithCategories();
|
|
1010
|
-
* Object.entries(components).forEach(([name, info]) => {
|
|
1011
|
-
* console.log(`${name}: category=${info.category || 'uncategorized'}`);
|
|
1012
|
-
* });
|
|
1013
|
-
* ```
|
|
1014
|
-
*/
|
|
1015
|
-
getAllComponentsWithCategories() {
|
|
1016
|
-
const record = {};
|
|
1017
|
-
this.components.forEach((value, key) => {
|
|
1018
|
-
record[key] = {
|
|
1019
|
-
definition: value,
|
|
1020
|
-
category: this.componentCategories.get(key)
|
|
1021
|
-
};
|
|
1022
|
-
});
|
|
1023
|
-
return record;
|
|
1024
|
-
}
|
|
1025
|
-
/**
|
|
1026
|
-
* Get category for a component
|
|
1027
|
-
*
|
|
1028
|
-
* @param name - Component name
|
|
1029
|
-
* @returns Category string if component is in a category folder, undefined otherwise
|
|
1030
|
-
*/
|
|
1031
|
-
getComponentCategory(name) {
|
|
1032
|
-
return this.componentCategories.get(name);
|
|
1033
|
-
}
|
|
1034
|
-
/**
|
|
1035
|
-
* Get load errors keyed by component name
|
|
1036
|
-
*/
|
|
1037
|
-
getLoadErrors() {
|
|
1038
|
-
return this.loadErrors;
|
|
1039
|
-
}
|
|
1040
|
-
/**
|
|
1041
|
-
* Get load warnings
|
|
1042
|
-
*/
|
|
1043
|
-
getLoadWarnings() {
|
|
1044
|
-
return this.loadWarnings;
|
|
1045
|
-
}
|
|
1046
|
-
/**
|
|
1047
|
-
* Get all load diagnostics
|
|
1048
|
-
*/
|
|
1049
|
-
getLoadDiagnostics() {
|
|
1050
|
-
return {
|
|
1051
|
-
errors: Array.from(this.loadErrors.values()),
|
|
1052
|
-
warnings: this.loadWarnings
|
|
1053
|
-
};
|
|
1054
|
-
}
|
|
1055
|
-
/**
|
|
1056
|
-
* Validate that a component has a valid structure
|
|
1057
|
-
*
|
|
1058
|
-
* @param componentDef - ComponentDefinition to validate
|
|
1059
|
-
* @returns true if component has valid structure, false otherwise
|
|
1060
|
-
*
|
|
1061
|
-
* @example
|
|
1062
|
-
* ```typescript
|
|
1063
|
-
* const component = componentService.getComponent('Button');
|
|
1064
|
-
* if (component && componentService.validateComponentStructure(component)) {
|
|
1065
|
-
* // Component has valid structure
|
|
1066
|
-
* }
|
|
1067
|
-
* ```
|
|
1068
|
-
*/
|
|
1069
|
-
validateComponentStructure(componentDef) {
|
|
1070
|
-
if (!componentDef) {
|
|
1071
|
-
return false;
|
|
1072
|
-
}
|
|
1073
|
-
const component = componentDef.component;
|
|
1074
|
-
if (!component || typeof component !== "object") {
|
|
1075
|
-
return false;
|
|
1076
|
-
}
|
|
1077
|
-
const structure = component.structure;
|
|
1078
|
-
if (!structure) {
|
|
1079
|
-
return false;
|
|
1080
|
-
}
|
|
1081
|
-
if (typeof structure !== "object" || structure === null || Array.isArray(structure)) {
|
|
1082
|
-
return false;
|
|
1083
|
-
}
|
|
1084
|
-
if (!("type" in structure) || typeof structure.type !== "string") {
|
|
1085
|
-
return false;
|
|
1086
|
-
}
|
|
1087
|
-
return true;
|
|
1088
|
-
}
|
|
1089
|
-
/**
|
|
1090
|
-
* Get the directory path for a component based on its category
|
|
1091
|
-
* @internal
|
|
1092
|
-
*/
|
|
1093
|
-
getComponentDir(name, category) {
|
|
1094
|
-
const componentsDir = this.componentsBaseDir();
|
|
1095
|
-
const cat = this.resolveComponentCategory(name, category);
|
|
1096
|
-
return cat ? join2(componentsDir, cat) : componentsDir;
|
|
1097
|
-
}
|
|
1098
|
-
/**
|
|
1099
|
-
* Resolve which category a save should land in. Priority:
|
|
1100
|
-
* 1. Explicit caller-supplied category (including '' for root)
|
|
1101
|
-
* 2. In-memory categories cache
|
|
1102
|
-
* 3. Existing file on disk (defense against a stale/empty cache)
|
|
1103
|
-
* Falls back to root only when none of the above resolves. The disk
|
|
1104
|
-
* scan in step 3 closes the race where a save fires while
|
|
1105
|
-
* loadAllComponents has been called but hasn't rebuilt the cache yet.
|
|
1106
|
-
* @internal
|
|
1107
|
-
*/
|
|
1108
|
-
resolveComponentCategory(name, category) {
|
|
1109
|
-
if (category !== void 0) {
|
|
1110
|
-
return category || void 0;
|
|
1111
|
-
}
|
|
1112
|
-
if (this.componentCategories.has(name)) {
|
|
1113
|
-
return this.componentCategories.get(name);
|
|
1114
|
-
}
|
|
1115
|
-
return this.findComponentCategoryOnDisk(name);
|
|
1116
|
-
}
|
|
1117
|
-
/**
|
|
1118
|
-
* Look on disk for an existing `<name>.json` and return its category.
|
|
1119
|
-
* Returns undefined for "exists at root" or "not found anywhere" — both
|
|
1120
|
-
* map to writing at root, which is the right default.
|
|
1121
|
-
* @internal
|
|
1122
|
-
*/
|
|
1123
|
-
findComponentCategoryOnDisk(name) {
|
|
1124
|
-
const componentsDir = this.componentsBaseDir();
|
|
1125
|
-
if (!existsSync2(componentsDir)) return void 0;
|
|
1126
|
-
const ext = this.writer ? null : ".json";
|
|
1127
|
-
const existsAt = (dir) => {
|
|
1128
|
-
if (ext) return existsSync2(join2(dir, `${name}${ext}`));
|
|
1129
|
-
try {
|
|
1130
|
-
return readdirSync2(dir, { withFileTypes: true }).some(
|
|
1131
|
-
(e) => e.isFile() && (e.name === `${name}.json` || e.name === `${name}.astro`)
|
|
1132
|
-
);
|
|
1133
|
-
} catch {
|
|
1134
|
-
return false;
|
|
1135
|
-
}
|
|
1136
|
-
};
|
|
1137
|
-
if (existsAt(componentsDir)) return void 0;
|
|
1138
|
-
try {
|
|
1139
|
-
for (const entry of readdirSync2(componentsDir, { withFileTypes: true })) {
|
|
1140
|
-
if (entry.isDirectory() && existsAt(join2(componentsDir, entry.name))) {
|
|
1141
|
-
return entry.name;
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
} catch {
|
|
1145
|
-
}
|
|
1146
|
-
return void 0;
|
|
1147
|
-
}
|
|
1148
|
-
/**
|
|
1149
|
-
* Get component JavaScript from .js file
|
|
1150
|
-
*
|
|
1151
|
-
* Loads the JavaScript code for a component from its .js file.
|
|
1152
|
-
* Returns null if the file doesn't exist.
|
|
1153
|
-
*
|
|
1154
|
-
* @param name - Component name (without .js extension)
|
|
1155
|
-
* @returns JavaScript code as string, or null if file doesn't exist
|
|
1156
|
-
*
|
|
1157
|
-
* @example
|
|
1158
|
-
* ```typescript
|
|
1159
|
-
* const js = await componentService.getComponentJavaScript('Button');
|
|
1160
|
-
* if (js) {
|
|
1161
|
-
* console.log('Component has JavaScript');
|
|
1162
|
-
* }
|
|
1163
|
-
* ```
|
|
1164
|
-
*/
|
|
1165
|
-
async getComponentJavaScript(name) {
|
|
1166
|
-
const componentDir = this.getComponentDir(name);
|
|
1167
|
-
const jsFilePath = join2(componentDir, `${name}.js`);
|
|
1168
|
-
try {
|
|
1169
|
-
if (await fileExists(jsFilePath)) {
|
|
1170
|
-
return await readTextFile(jsFilePath);
|
|
1171
|
-
}
|
|
1172
|
-
return null;
|
|
1173
|
-
} catch (error) {
|
|
1174
|
-
return null;
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
/**
|
|
1178
|
-
* Save component definition
|
|
1179
|
-
*
|
|
1180
|
-
* Saves component definition to a .json file and updates the cache.
|
|
1181
|
-
* The javascript field is automatically removed from the saved data
|
|
1182
|
-
* (JavaScript should only be in .js files).
|
|
1183
|
-
*
|
|
1184
|
-
* If category is provided, the component is saved to the category subfolder.
|
|
1185
|
-
* If category is undefined and component exists, it stays in its current location.
|
|
1186
|
-
* If category is empty string '', component is saved to root (uncategorized).
|
|
1187
|
-
*
|
|
1188
|
-
* @param name - Component name (without .json extension)
|
|
1189
|
-
* @param data - ComponentDefinition object to save
|
|
1190
|
-
* @param category - Optional category folder name (empty string for root/uncategorized)
|
|
1191
|
-
* @returns Promise that resolves when the component is saved
|
|
1192
|
-
*
|
|
1193
|
-
* @throws {Error} If file write fails
|
|
1194
|
-
*
|
|
1195
|
-
* @example
|
|
1196
|
-
* ```typescript
|
|
1197
|
-
* // Save to root (uncategorized)
|
|
1198
|
-
* await componentService.saveComponent('Button', buttonDef);
|
|
1199
|
-
*
|
|
1200
|
-
* // Save to 'ui' category folder
|
|
1201
|
-
* await componentService.saveComponent('Card', cardDef, 'ui');
|
|
1202
|
-
* ```
|
|
1203
|
-
*/
|
|
1204
|
-
async saveComponent(name, data, category) {
|
|
1205
|
-
const dataWithoutJS = JSON.parse(JSON.stringify(data));
|
|
1206
|
-
if (dataWithoutJS?.component?.javascript !== void 0) {
|
|
1207
|
-
delete dataWithoutJS.component.javascript;
|
|
1208
|
-
}
|
|
1209
|
-
const targetCategory = this.resolveComponentCategory(name, category);
|
|
1210
|
-
const componentsDir = this.componentsBaseDir();
|
|
1211
|
-
const componentDir = targetCategory ? join2(componentsDir, targetCategory) : componentsDir;
|
|
1212
|
-
if (this.writer) {
|
|
1213
|
-
await this.writer.writeComponent(componentDir, name, dataWithoutJS);
|
|
1214
|
-
} else {
|
|
1215
|
-
const writeFile2 = this.fs ? this.fs.writeFile.bind(this.fs) : (await import("fs/promises")).writeFile;
|
|
1216
|
-
if (targetCategory && !existsSync2(componentDir)) {
|
|
1217
|
-
mkdirSync2(componentDir, { recursive: true });
|
|
1218
|
-
}
|
|
1219
|
-
const filePath = join2(componentDir, `${name}.json`);
|
|
1220
|
-
await writeFile2(filePath, JSON.stringify(dataWithoutJS, null, 2), "utf-8");
|
|
1221
|
-
}
|
|
1222
|
-
this.components.set(name, dataWithoutJS);
|
|
1223
|
-
this.componentCategories.set(name, targetCategory);
|
|
1224
|
-
}
|
|
1225
|
-
/**
|
|
1226
|
-
* Save component JavaScript to .js file
|
|
1227
|
-
*
|
|
1228
|
-
* Saves JavaScript code to a component's .js file and reloads the component
|
|
1229
|
-
* to update the in-memory cache with the new JavaScript.
|
|
1230
|
-
*
|
|
1231
|
-
* @param name - Component name (without .js extension)
|
|
1232
|
-
* @param javascript - JavaScript code to save
|
|
1233
|
-
* @returns Promise that resolves when the JavaScript is saved
|
|
1234
|
-
*
|
|
1235
|
-
* @throws {Error} If file write fails
|
|
1236
|
-
*
|
|
1237
|
-
* @example
|
|
1238
|
-
* ```typescript
|
|
1239
|
-
* await componentService.saveComponentJavaScript('Button', 'console.log("clicked");');
|
|
1240
|
-
* ```
|
|
1241
|
-
*/
|
|
1242
|
-
async saveComponentJavaScript(name, javascript) {
|
|
1243
|
-
const componentDir = this.getComponentDir(name);
|
|
1244
|
-
if (this.writer) {
|
|
1245
|
-
const current = this.components.get(name);
|
|
1246
|
-
await this.writer.writeJavaScript(componentDir, name, javascript || "", current);
|
|
1247
|
-
const next = current ? { ...current, component: { ...current.component, javascript: javascript || "" } } : { component: { javascript: javascript || "" } };
|
|
1248
|
-
this.components.set(name, next);
|
|
1249
|
-
return;
|
|
1250
|
-
}
|
|
1251
|
-
const writeFile2 = this.fs ? this.fs.writeFile.bind(this.fs) : (await import("fs/promises")).writeFile;
|
|
1252
|
-
const jsFilePath = join2(componentDir, `${name}.js`);
|
|
1253
|
-
await writeFile2(jsFilePath, javascript || "", "utf-8");
|
|
1254
|
-
const componentPath = join2(componentDir, `${name}.json`);
|
|
1255
|
-
const componentData = await loadJSONFile(componentPath);
|
|
1256
|
-
if (componentData) {
|
|
1257
|
-
const parsed = parseJSON(componentData);
|
|
1258
|
-
try {
|
|
1259
|
-
if (await fileExists(jsFilePath)) {
|
|
1260
|
-
const jsContent = await readTextFile(jsFilePath);
|
|
1261
|
-
if (!parsed.component) {
|
|
1262
|
-
parsed.component = {};
|
|
1263
|
-
}
|
|
1264
|
-
parsed.component.javascript = jsContent;
|
|
1265
|
-
}
|
|
1266
|
-
} catch (error) {
|
|
1267
|
-
}
|
|
1268
|
-
this.components.set(name, parsed);
|
|
1269
|
-
}
|
|
1270
|
-
}
|
|
1271
|
-
/**
|
|
1272
|
-
* Save component CSS to .css file
|
|
1273
|
-
*
|
|
1274
|
-
* Saves CSS code to a component's .css file and reloads the component
|
|
1275
|
-
* to update the in-memory cache with the new CSS.
|
|
1276
|
-
*
|
|
1277
|
-
* @param name - Component name (without .css extension)
|
|
1278
|
-
* @param css - CSS code to save
|
|
1279
|
-
* @returns Promise that resolves when the CSS is saved
|
|
1280
|
-
*
|
|
1281
|
-
* @throws {Error} If file write fails
|
|
1282
|
-
*
|
|
1283
|
-
* @example
|
|
1284
|
-
* ```typescript
|
|
1285
|
-
* await componentService.saveComponentCSS('Button', '.button { color: blue; }');
|
|
1286
|
-
* ```
|
|
1287
|
-
*/
|
|
1288
|
-
async saveComponentCSS(name, css) {
|
|
1289
|
-
const componentDir = this.getComponentDir(name);
|
|
1290
|
-
if (this.writer) {
|
|
1291
|
-
const current = this.components.get(name);
|
|
1292
|
-
await this.writer.writeCSS(componentDir, name, css || "", current);
|
|
1293
|
-
const next = current ? { ...current, component: { ...current.component, css: css || "" } } : { component: { css: css || "" } };
|
|
1294
|
-
this.components.set(name, next);
|
|
1295
|
-
return;
|
|
1296
|
-
}
|
|
1297
|
-
const writeFile2 = this.fs ? this.fs.writeFile.bind(this.fs) : (await import("fs/promises")).writeFile;
|
|
1298
|
-
const cssFilePath = join2(componentDir, `${name}.css`);
|
|
1299
|
-
await writeFile2(cssFilePath, css || "", "utf-8");
|
|
1300
|
-
const componentPath = join2(componentDir, `${name}.json`);
|
|
1301
|
-
const componentData = await loadJSONFile(componentPath);
|
|
1302
|
-
if (componentData) {
|
|
1303
|
-
const parsed = parseJSON(componentData);
|
|
1304
|
-
try {
|
|
1305
|
-
if (await fileExists(cssFilePath)) {
|
|
1306
|
-
const cssContent = await readTextFile(cssFilePath);
|
|
1307
|
-
if (!parsed.component) {
|
|
1308
|
-
parsed.component = {};
|
|
1309
|
-
}
|
|
1310
|
-
parsed.component.css = cssContent;
|
|
1311
|
-
}
|
|
1312
|
-
} catch (error) {
|
|
1313
|
-
}
|
|
1314
|
-
this.components.set(name, parsed);
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
-
/**
|
|
1318
|
-
* Get all component folders (including empty ones)
|
|
1319
|
-
*
|
|
1320
|
-
* Scans the components directory for subdirectories.
|
|
1321
|
-
* Returns all folder names, whether they contain components or not.
|
|
1322
|
-
*
|
|
1323
|
-
* @returns Array of folder names
|
|
1324
|
-
*
|
|
1325
|
-
* @example
|
|
1326
|
-
* ```typescript
|
|
1327
|
-
* const folders = componentService.getAllFolders();
|
|
1328
|
-
* // ['ui', 'layout', 'content']
|
|
1329
|
-
* ```
|
|
1330
|
-
*/
|
|
1331
|
-
getAllFolders() {
|
|
1332
|
-
const componentsDir = this.componentsBaseDir();
|
|
1333
|
-
if (!existsSync2(componentsDir)) {
|
|
1334
|
-
return [];
|
|
1335
|
-
}
|
|
1336
|
-
try {
|
|
1337
|
-
const entries = readdirSync2(componentsDir, { withFileTypes: true });
|
|
1338
|
-
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
1339
|
-
} catch {
|
|
1340
|
-
return [];
|
|
1341
|
-
}
|
|
1342
|
-
}
|
|
1343
|
-
/**
|
|
1344
|
-
* Create a component folder
|
|
1345
|
-
*
|
|
1346
|
-
* Creates an empty folder in the components directory.
|
|
1347
|
-
* Useful for pre-creating category folders before moving components.
|
|
1348
|
-
*
|
|
1349
|
-
* @param folderName - Name of the folder to create
|
|
1350
|
-
* @returns Promise that resolves when folder is created
|
|
1351
|
-
*
|
|
1352
|
-
* @throws {Error} If folder creation fails
|
|
1353
|
-
*
|
|
1354
|
-
* @example
|
|
1355
|
-
* ```typescript
|
|
1356
|
-
* await componentService.createFolder('ui');
|
|
1357
|
-
* ```
|
|
1358
|
-
*/
|
|
1359
|
-
async createFolder(folderName) {
|
|
1360
|
-
if (!folderName || folderName.trim() === "") {
|
|
1361
|
-
throw new Error("Folder name cannot be empty");
|
|
1362
|
-
}
|
|
1363
|
-
const sanitized = folderName.trim().toLowerCase().replace(/[^a-z0-9-_]/g, "-");
|
|
1364
|
-
if (sanitized !== folderName.trim()) {
|
|
1365
|
-
throw new Error("Folder name can only contain lowercase letters, numbers, dashes, and underscores");
|
|
1366
|
-
}
|
|
1367
|
-
const componentsDir = this.componentsBaseDir();
|
|
1368
|
-
const folderPath = join2(componentsDir, folderName);
|
|
1369
|
-
if (existsSync2(folderPath)) {
|
|
1370
|
-
throw new Error("Folder already exists");
|
|
1371
|
-
}
|
|
1372
|
-
mkdirSync2(folderPath, { recursive: true });
|
|
1373
|
-
}
|
|
1374
|
-
/**
|
|
1375
|
-
* Move component to a different category folder
|
|
1376
|
-
*
|
|
1377
|
-
* Moves all component files (.json, .js, .css) from current location to new category folder.
|
|
1378
|
-
* If newCategory is empty string or undefined, moves to root (uncategorized).
|
|
1379
|
-
*
|
|
1380
|
-
* @param name - Component name
|
|
1381
|
-
* @param newCategory - Target category folder (empty string or undefined for root)
|
|
1382
|
-
* @returns Promise that resolves when move is complete
|
|
1383
|
-
*
|
|
1384
|
-
* @throws {Error} If component doesn't exist or move fails
|
|
1385
|
-
*
|
|
1386
|
-
* @example
|
|
1387
|
-
* ```typescript
|
|
1388
|
-
* // Move Button from root to 'ui' category
|
|
1389
|
-
* await componentService.moveComponent('Button', 'ui');
|
|
1390
|
-
*
|
|
1391
|
-
* // Move Card from 'ui' to root (uncategorized)
|
|
1392
|
-
* await componentService.moveComponent('Card', '');
|
|
1393
|
-
* ```
|
|
1394
|
-
*/
|
|
1395
|
-
async moveComponent(name, newCategory) {
|
|
1396
|
-
const currentCategory = this.componentCategories.get(name);
|
|
1397
|
-
const targetCategory = newCategory || void 0;
|
|
1398
|
-
if (currentCategory === targetCategory) {
|
|
1399
|
-
return;
|
|
1400
|
-
}
|
|
1401
|
-
const componentsDir = this.componentsBaseDir();
|
|
1402
|
-
const sourceDir = currentCategory ? join2(componentsDir, currentCategory) : componentsDir;
|
|
1403
|
-
const targetDir = targetCategory ? join2(componentsDir, targetCategory) : componentsDir;
|
|
1404
|
-
if (this.writer) {
|
|
1405
|
-
await this.writer.moveComponent(sourceDir, targetDir, name);
|
|
1406
|
-
} else {
|
|
1407
|
-
if (targetCategory && !existsSync2(targetDir)) {
|
|
1408
|
-
mkdirSync2(targetDir, { recursive: true });
|
|
1409
|
-
}
|
|
1410
|
-
const extensions = [".json", ".js", ".css"];
|
|
1411
|
-
const { rename } = await import("fs/promises");
|
|
1412
|
-
for (const ext of extensions) {
|
|
1413
|
-
const sourcePath = join2(sourceDir, `${name}${ext}`);
|
|
1414
|
-
const targetPath = join2(targetDir, `${name}${ext}`);
|
|
1415
|
-
if (existsSync2(sourcePath)) {
|
|
1416
|
-
await rename(sourcePath, targetPath);
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
this.componentCategories.set(name, targetCategory);
|
|
1421
|
-
if (currentCategory) {
|
|
1422
|
-
try {
|
|
1423
|
-
const remaining = readdirSync2(sourceDir);
|
|
1424
|
-
if (remaining.length === 0) {
|
|
1425
|
-
rmdirSync2(sourceDir);
|
|
1426
|
-
}
|
|
1427
|
-
} catch {
|
|
1428
|
-
}
|
|
1429
|
-
}
|
|
1430
|
-
}
|
|
1431
|
-
/**
|
|
1432
|
-
* Rename a component across the project.
|
|
1433
|
-
*
|
|
1434
|
-
* Renames the component's `.json`/`.js`/`.css` files on disk (respecting its
|
|
1435
|
-
* category folder), updates the in-memory caches, rewrites every
|
|
1436
|
-
* `{ type: "component", component: "<oldName>" }` reference inside other
|
|
1437
|
-
* component structures, and — if `pageService` is provided — also walks every
|
|
1438
|
-
* page/template and rewrites references there, saving any pages that changed.
|
|
1439
|
-
*
|
|
1440
|
-
* Matching is structural (only `node.component` string equality), not
|
|
1441
|
-
* text-based, so it never touches HTML tags, CSS classes, prop values, or
|
|
1442
|
-
* embedded markup that happen to contain the name as a substring.
|
|
1443
|
-
*
|
|
1444
|
-
* @throws {Error} If old/new name is missing, names are identical, new name
|
|
1445
|
-
* isn't a valid JS identifier, the source component doesn't exist, or the
|
|
1446
|
-
* new name is already taken.
|
|
1447
|
-
*/
|
|
1448
|
-
async renameComponent(oldName, newName, pageService) {
|
|
1449
|
-
if (!oldName || typeof oldName !== "string") {
|
|
1450
|
-
throw new Error("oldName is required");
|
|
1451
|
-
}
|
|
1452
|
-
if (!newName || typeof newName !== "string") {
|
|
1453
|
-
throw new Error("newName is required");
|
|
1454
|
-
}
|
|
1455
|
-
const trimmedNew = newName.trim();
|
|
1456
|
-
if (oldName === trimmedNew) {
|
|
1457
|
-
throw new Error("newName must differ from oldName");
|
|
1458
|
-
}
|
|
1459
|
-
if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(trimmedNew)) {
|
|
1460
|
-
throw new Error("newName must be a valid JavaScript identifier");
|
|
1461
|
-
}
|
|
1462
|
-
if (!this.components.has(oldName)) {
|
|
1463
|
-
throw new Error(`Component "${oldName}" not found`);
|
|
1464
|
-
}
|
|
1465
|
-
if (this.components.has(trimmedNew)) {
|
|
1466
|
-
throw new Error(`Component "${trimmedNew}" already exists`);
|
|
1467
|
-
}
|
|
1468
|
-
const category = this.componentCategories.get(oldName);
|
|
1469
|
-
const componentsDir = this.componentsBaseDir();
|
|
1470
|
-
const dir = category ? join2(componentsDir, category) : componentsDir;
|
|
1471
|
-
if (this.writer) {
|
|
1472
|
-
if (await this.writer.componentExists(dir, trimmedNew)) {
|
|
1473
|
-
throw new Error(`Component "${trimmedNew}" already exists in components/${category ?? ""}`);
|
|
1474
|
-
}
|
|
1475
|
-
await this.writer.moveComponent(dir, dir, oldName, trimmedNew);
|
|
1476
|
-
} else {
|
|
1477
|
-
for (const ext of [".json", ".js", ".css"]) {
|
|
1478
|
-
if (existsSync2(join2(dir, `${trimmedNew}${ext}`))) {
|
|
1479
|
-
throw new Error(`File "${trimmedNew}${ext}" already exists in components/${category ?? ""}`);
|
|
1480
|
-
}
|
|
1481
|
-
}
|
|
1482
|
-
const { rename } = await import("fs/promises");
|
|
1483
|
-
for (const ext of [".json", ".js", ".css"]) {
|
|
1484
|
-
const src = join2(dir, `${oldName}${ext}`);
|
|
1485
|
-
const dst = join2(dir, `${trimmedNew}${ext}`);
|
|
1486
|
-
if (existsSync2(src)) {
|
|
1487
|
-
await rename(src, dst);
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
}
|
|
1491
|
-
const def = this.components.get(oldName);
|
|
1492
|
-
this.components.delete(oldName);
|
|
1493
|
-
this.componentCategories.delete(oldName);
|
|
1494
|
-
this.components.set(trimmedNew, def);
|
|
1495
|
-
this.componentCategories.set(trimmedNew, category);
|
|
1496
|
-
let componentRefs = 0;
|
|
1497
|
-
const writeFile2 = this.fs ? this.fs.writeFile.bind(this.fs) : (await import("fs/promises")).writeFile;
|
|
1498
|
-
for (const [name, otherDef] of this.components.entries()) {
|
|
1499
|
-
if (name === trimmedNew) continue;
|
|
1500
|
-
const structure = otherDef.component?.structure;
|
|
1501
|
-
if (!structure) continue;
|
|
1502
|
-
if (rewriteComponentRefs(structure, oldName, trimmedNew)) {
|
|
1503
|
-
const otherCategory = this.componentCategories.get(name);
|
|
1504
|
-
const otherDir = otherCategory ? join2(componentsDir, otherCategory) : componentsDir;
|
|
1505
|
-
const persisted = JSON.parse(JSON.stringify(otherDef));
|
|
1506
|
-
if (persisted?.component?.javascript !== void 0) {
|
|
1507
|
-
delete persisted.component.javascript;
|
|
1508
|
-
}
|
|
1509
|
-
if (this.writer) {
|
|
1510
|
-
await this.writer.writeComponent(otherDir, name, persisted);
|
|
1511
|
-
} else {
|
|
1512
|
-
await writeFile2(join2(otherDir, `${name}.json`), JSON.stringify(persisted, null, 2), "utf-8");
|
|
1513
|
-
}
|
|
1514
|
-
componentRefs++;
|
|
1515
|
-
}
|
|
1516
|
-
}
|
|
1517
|
-
let pageRefs = 0;
|
|
1518
|
-
if (pageService) {
|
|
1519
|
-
const pagePaths = pageService.getAllPagePaths();
|
|
1520
|
-
for (const path2 of pagePaths) {
|
|
1521
|
-
const pageData = pageService.getPageData(path2);
|
|
1522
|
-
if (!pageData || !("root" in pageData) || !pageData.root) continue;
|
|
1523
|
-
if (rewriteComponentRefs(pageData.root, oldName, trimmedNew)) {
|
|
1524
|
-
await pageService.savePage(path2, pageData);
|
|
1525
|
-
pageRefs++;
|
|
1526
|
-
}
|
|
1527
|
-
}
|
|
1528
|
-
}
|
|
1529
|
-
return {
|
|
1530
|
-
oldName,
|
|
1531
|
-
newName: trimmedNew,
|
|
1532
|
-
category,
|
|
1533
|
-
componentRefs,
|
|
1534
|
-
pageRefs
|
|
1535
|
-
};
|
|
1536
|
-
}
|
|
1537
|
-
};
|
|
1538
|
-
|
|
1539
|
-
// lib/server/services/EnumService.ts
|
|
1540
|
-
var EnumService = class extends CachedConfigLoader {
|
|
1541
|
-
/**
|
|
1542
|
-
* Load enums configuration from enums.json
|
|
1543
|
-
*/
|
|
1544
|
-
async loadConfig() {
|
|
1545
|
-
return this.load();
|
|
1546
|
-
}
|
|
1547
|
-
async performLoad() {
|
|
1548
|
-
const [enumsContent, configContent] = await Promise.all([
|
|
1549
|
-
loadJSONFile(projectPaths.enums()).catch(() => null),
|
|
1550
|
-
loadJSONFile(projectPaths.config()).catch(() => null)
|
|
1551
|
-
]);
|
|
1552
|
-
if (enumsContent) {
|
|
1553
|
-
try {
|
|
1554
|
-
const data = parseJSON(enumsContent);
|
|
1555
|
-
if (data && typeof data === "object" && !Array.isArray(data)) {
|
|
1556
|
-
return this.validateEnums(data);
|
|
1557
|
-
}
|
|
1558
|
-
} catch (error) {
|
|
1559
|
-
console.warn("Failed to parse enums.json:", error);
|
|
1560
|
-
}
|
|
1561
|
-
}
|
|
1562
|
-
if (configContent) {
|
|
1563
|
-
try {
|
|
1564
|
-
const configData = parseJSON(configContent);
|
|
1565
|
-
if (configData && typeof configData === "object" && "enums" in configData) {
|
|
1566
|
-
const enums = configData.enums;
|
|
1567
|
-
if (enums && typeof enums === "object" && !Array.isArray(enums)) {
|
|
1568
|
-
return this.validateEnums(enums);
|
|
1569
|
-
}
|
|
1570
|
-
}
|
|
1571
|
-
} catch {
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1574
|
-
return this.getDefaultConfig();
|
|
1575
|
-
}
|
|
1576
|
-
getDefaultConfig() {
|
|
1577
|
-
return {};
|
|
1578
|
-
}
|
|
1579
|
-
/**
|
|
1580
|
-
* Validate that all values are string arrays
|
|
1581
|
-
*/
|
|
1582
|
-
validateEnums(data) {
|
|
1583
|
-
const result = {};
|
|
1584
|
-
for (const [key, value] of Object.entries(data)) {
|
|
1585
|
-
if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
|
|
1586
|
-
result[key] = value;
|
|
1587
|
-
}
|
|
1588
|
-
}
|
|
1589
|
-
return result;
|
|
1590
|
-
}
|
|
1591
|
-
/**
|
|
1592
|
-
* Get config with status info (for editor UI)
|
|
1593
|
-
*/
|
|
1594
|
-
async getConfigWithStatus() {
|
|
1595
|
-
const filePath = projectPaths.enums();
|
|
1596
|
-
try {
|
|
1597
|
-
const content = await loadJSONFile(filePath);
|
|
1598
|
-
if (!content) {
|
|
1599
|
-
const fallbackConfig = await this.performLoad();
|
|
1600
|
-
const hasFallback = Object.keys(fallbackConfig).length > 0;
|
|
1601
|
-
return {
|
|
1602
|
-
status: hasFallback ? "valid" : "missing",
|
|
1603
|
-
config: fallbackConfig,
|
|
1604
|
-
filePath
|
|
1605
|
-
};
|
|
1606
|
-
}
|
|
1607
|
-
const data = parseJSON(content);
|
|
1608
|
-
if (data && typeof data === "object" && !Array.isArray(data)) {
|
|
1609
|
-
return { status: "valid", config: this.validateEnums(data), filePath };
|
|
1610
|
-
}
|
|
1611
|
-
return { status: "invalid", config: this.getDefaultConfig(), error: "Invalid structure: expected Record<string, string[]>", filePath };
|
|
1612
|
-
} catch (error) {
|
|
1613
|
-
return {
|
|
1614
|
-
status: "invalid",
|
|
1615
|
-
config: this.getDefaultConfig(),
|
|
1616
|
-
error: error instanceof Error ? error.message : "Parse error",
|
|
1617
|
-
filePath
|
|
1618
|
-
};
|
|
1619
|
-
}
|
|
1620
|
-
}
|
|
1621
|
-
/**
|
|
1622
|
-
* Save enums configuration to enums.json
|
|
1623
|
-
*/
|
|
1624
|
-
async saveConfig(config) {
|
|
1625
|
-
const enumsPath = projectPaths.enums();
|
|
1626
|
-
const content = JSON.stringify(config, null, 2);
|
|
1627
|
-
await writeFile(enumsPath, content);
|
|
1628
|
-
this.setCache(config);
|
|
1629
|
-
}
|
|
1630
|
-
};
|
|
1631
|
-
var enumService = new EnumService();
|
|
1632
|
-
|
|
1633
|
-
// lib/server/fileWatcher.ts
|
|
1634
|
-
import { watch, existsSync as existsSync3 } from "fs";
|
|
1635
|
-
import { basename, dirname, join as join3 } from "path";
|
|
1636
|
-
function attachWhenDirExists(dirPath, attach, setWatcher) {
|
|
1637
|
-
if (existsSync3(dirPath)) {
|
|
1638
|
-
setWatcher(attach());
|
|
1639
|
-
return;
|
|
1640
|
-
}
|
|
1641
|
-
const parentDir = dirname(dirPath);
|
|
1642
|
-
const targetName = basename(dirPath);
|
|
1643
|
-
if (!existsSync3(parentDir)) {
|
|
1644
|
-
return;
|
|
1645
|
-
}
|
|
1646
|
-
let parentWatcher = null;
|
|
1647
|
-
parentWatcher = watch(parentDir, (_event, filename) => {
|
|
1648
|
-
if (filename !== targetName) return;
|
|
1649
|
-
if (!existsSync3(dirPath)) return;
|
|
1650
|
-
parentWatcher?.close();
|
|
1651
|
-
parentWatcher = null;
|
|
1652
|
-
setWatcher(attach());
|
|
1653
|
-
});
|
|
1654
|
-
setWatcher(parentWatcher);
|
|
1655
|
-
}
|
|
1656
|
-
var FileWatcher = class {
|
|
1657
|
-
constructor(callbacks) {
|
|
1658
|
-
this.callbacks = callbacks;
|
|
1659
|
-
}
|
|
1660
|
-
componentsWatcher = null;
|
|
1661
|
-
pagesWatcher = null;
|
|
1662
|
-
templatesWatcher = null;
|
|
1663
|
-
colorsWatcher = null;
|
|
1664
|
-
variablesWatcher = null;
|
|
1665
|
-
enumsWatcher = null;
|
|
1666
|
-
cmsWatcher = null;
|
|
1667
|
-
imagesWatcher = null;
|
|
1668
|
-
librariesWatcher = null;
|
|
1669
|
-
projectConfigWatcher = null;
|
|
1670
|
-
// Astro-format projects keep pages/components/CMS under src/ as .astro/.json.
|
|
1671
|
-
astroPagesWatcher = null;
|
|
1672
|
-
astroComponentsWatcher = null;
|
|
1673
|
-
astroContentWatcher = null;
|
|
1674
|
-
/**
|
|
1675
|
-
* Start watching components directory
|
|
1676
|
-
* Watches both .json and .js files to detect component definition and JavaScript changes
|
|
1677
|
-
*/
|
|
1678
|
-
watchComponents(dirPath = projectPaths.components()) {
|
|
1679
|
-
if (!existsSync3(dirPath)) {
|
|
1680
|
-
return;
|
|
1681
|
-
}
|
|
1682
|
-
this.componentsWatcher = watch(
|
|
1683
|
-
dirPath,
|
|
1684
|
-
{ recursive: true },
|
|
1685
|
-
async (event, filename) => {
|
|
1686
|
-
if (filename && (filename.endsWith(".json") || filename.endsWith(".js") || filename.endsWith(".css"))) {
|
|
1687
|
-
const fileType = filename.endsWith(".js") ? "JavaScript" : filename.endsWith(".css") ? "CSS" : "component";
|
|
1688
|
-
if (this.callbacks.onComponentChange) {
|
|
1689
|
-
await this.callbacks.onComponentChange();
|
|
1690
|
-
}
|
|
1691
|
-
}
|
|
1692
|
-
}
|
|
1693
|
-
);
|
|
1694
|
-
}
|
|
1695
|
-
/**
|
|
1696
|
-
* Start watching pages directory
|
|
1697
|
-
*/
|
|
1698
|
-
watchPages(dirPath = projectPaths.pages()) {
|
|
1699
|
-
if (!existsSync3(dirPath)) {
|
|
1700
|
-
return;
|
|
1701
|
-
}
|
|
1702
|
-
this.pagesWatcher = watch(
|
|
1703
|
-
dirPath,
|
|
1704
|
-
{ recursive: true },
|
|
1705
|
-
async (event, filename) => {
|
|
1706
|
-
if (filename && filename.endsWith(".json")) {
|
|
1707
|
-
const pageName = filename.replace(".json", "");
|
|
1708
|
-
const pagePath = mapPageNameToPath(pageName);
|
|
1709
|
-
if (this.callbacks.onPageChange) {
|
|
1710
|
-
await this.callbacks.onPageChange(pagePath);
|
|
1711
|
-
}
|
|
1712
|
-
}
|
|
1713
|
-
}
|
|
1714
|
-
);
|
|
1715
|
-
}
|
|
1716
|
-
/**
|
|
1717
|
-
* Start watching root templates directory for CMS template changes.
|
|
1718
|
-
* Falls back to a deferred-attach watcher on the project root when
|
|
1719
|
-
* `templates/` doesn't exist yet (blank projects, projects that have never
|
|
1720
|
-
* had a CMS collection).
|
|
1721
|
-
*/
|
|
1722
|
-
watchTemplates(dirPath = projectPaths.templates()) {
|
|
1723
|
-
attachWhenDirExists(
|
|
1724
|
-
dirPath,
|
|
1725
|
-
() => watch(
|
|
1726
|
-
dirPath,
|
|
1727
|
-
{ recursive: true },
|
|
1728
|
-
async (event, filename) => {
|
|
1729
|
-
if (filename && filename.endsWith(".json")) {
|
|
1730
|
-
const pageName = `templates/${filename.replace(".json", "")}`;
|
|
1731
|
-
const pagePath = mapPageNameToPath(pageName);
|
|
1732
|
-
if (this.callbacks.onPageChange) {
|
|
1733
|
-
await this.callbacks.onPageChange(pagePath);
|
|
1734
|
-
}
|
|
1735
|
-
}
|
|
1736
|
-
}
|
|
1737
|
-
),
|
|
1738
|
-
(w) => {
|
|
1739
|
-
this.templatesWatcher = w;
|
|
1740
|
-
}
|
|
1741
|
-
);
|
|
1742
|
-
}
|
|
1743
|
-
/**
|
|
1744
|
-
* Start watching colors.json file
|
|
1745
|
-
*/
|
|
1746
|
-
watchColors(filePath = projectPaths.colors()) {
|
|
1747
|
-
if (!existsSync3(filePath)) {
|
|
1748
|
-
return;
|
|
1749
|
-
}
|
|
1750
|
-
const dirPath = getProjectRoot();
|
|
1751
|
-
this.colorsWatcher = watch(
|
|
1752
|
-
dirPath,
|
|
1753
|
-
{ recursive: false },
|
|
1754
|
-
async (event, filename) => {
|
|
1755
|
-
if (filename === "colors.json" && this.callbacks.onColorsChange) {
|
|
1756
|
-
await this.callbacks.onColorsChange();
|
|
1757
|
-
}
|
|
1758
|
-
}
|
|
1759
|
-
);
|
|
1760
|
-
}
|
|
1761
|
-
/**
|
|
1762
|
-
* Start watching variables.json file
|
|
1763
|
-
*/
|
|
1764
|
-
watchVariables() {
|
|
1765
|
-
const dirPath = getProjectRoot();
|
|
1766
|
-
this.variablesWatcher = watch(
|
|
1767
|
-
dirPath,
|
|
1768
|
-
{ recursive: false },
|
|
1769
|
-
async (event, filename) => {
|
|
1770
|
-
if (filename === "variables.json" && this.callbacks.onVariablesChange) {
|
|
1771
|
-
await this.callbacks.onVariablesChange();
|
|
1772
|
-
}
|
|
1773
|
-
}
|
|
1774
|
-
);
|
|
1775
|
-
}
|
|
1776
|
-
/**
|
|
1777
|
-
* Start watching enums.json file
|
|
1778
|
-
*/
|
|
1779
|
-
watchEnums() {
|
|
1780
|
-
const dirPath = getProjectRoot();
|
|
1781
|
-
this.enumsWatcher = watch(
|
|
1782
|
-
dirPath,
|
|
1783
|
-
{ recursive: false },
|
|
1784
|
-
async (event, filename) => {
|
|
1785
|
-
if (filename === "enums.json" && this.callbacks.onEnumsChange) {
|
|
1786
|
-
await this.callbacks.onEnumsChange();
|
|
1787
|
-
}
|
|
1788
|
-
}
|
|
1789
|
-
);
|
|
1790
|
-
}
|
|
1791
|
-
/**
|
|
1792
|
-
* Start watching CMS directory.
|
|
1793
|
-
* Watches for changes in CMS content files (cms/{collection}/*.json).
|
|
1794
|
-
* Falls back to a deferred-attach watcher when `cms/` doesn't exist yet.
|
|
1795
|
-
*/
|
|
1796
|
-
watchCMS(dirPath = projectPaths.cms()) {
|
|
1797
|
-
attachWhenDirExists(
|
|
1798
|
-
dirPath,
|
|
1799
|
-
() => watch(
|
|
1800
|
-
dirPath,
|
|
1801
|
-
{ recursive: true },
|
|
1802
|
-
async (event, filename) => {
|
|
1803
|
-
if (filename && filename.endsWith(".json")) {
|
|
1804
|
-
const collection = filename.split("/")[0];
|
|
1805
|
-
if (this.callbacks.onCMSChange) {
|
|
1806
|
-
await this.callbacks.onCMSChange(collection);
|
|
1807
|
-
}
|
|
1808
|
-
}
|
|
1809
|
-
}
|
|
1810
|
-
),
|
|
1811
|
-
(w) => {
|
|
1812
|
-
this.cmsWatcher = w;
|
|
1813
|
-
}
|
|
1814
|
-
);
|
|
1815
|
-
}
|
|
1816
|
-
/**
|
|
1817
|
-
* Start watching images directory for new source images
|
|
1818
|
-
*/
|
|
1819
|
-
watchImages(dirPath = projectPaths.images()) {
|
|
1820
|
-
if (!existsSync3(dirPath)) {
|
|
1821
|
-
return;
|
|
1822
|
-
}
|
|
1823
|
-
this.imagesWatcher = watch(
|
|
1824
|
-
dirPath,
|
|
1825
|
-
{ recursive: false },
|
|
1826
|
-
async (event, filename) => {
|
|
1827
|
-
if (event === "rename" && filename && this.callbacks.onImageAdded) {
|
|
1828
|
-
await this.callbacks.onImageAdded(filename);
|
|
1829
|
-
}
|
|
1830
|
-
}
|
|
1831
|
-
);
|
|
1832
|
-
}
|
|
1833
|
-
/**
|
|
1834
|
-
* Start watching project.config.json for changes.
|
|
1835
|
-
* Picks up edits to i18n locales, breakpoints, libraries, icons, etc. so the
|
|
1836
|
-
* studio reflects external writes (e.g. an AI tool adding a new locale) without
|
|
1837
|
-
* a dev-server restart.
|
|
1838
|
-
*/
|
|
1839
|
-
watchProjectConfig() {
|
|
1840
|
-
const dirPath = getProjectRoot();
|
|
1841
|
-
this.projectConfigWatcher = watch(
|
|
1842
|
-
dirPath,
|
|
1843
|
-
{ recursive: false },
|
|
1844
|
-
async (_event, filename) => {
|
|
1845
|
-
if (filename === "project.config.json" && this.callbacks.onProjectConfigChange) {
|
|
1846
|
-
await this.callbacks.onProjectConfigChange();
|
|
1847
|
-
}
|
|
1848
|
-
}
|
|
1849
|
-
);
|
|
1850
|
-
}
|
|
1851
|
-
/**
|
|
1852
|
-
* Start watching libraries directory for CSS/JS changes
|
|
1853
|
-
*/
|
|
1854
|
-
watchLibraries(dirPath = projectPaths.libraries()) {
|
|
1855
|
-
if (!existsSync3(dirPath)) {
|
|
1856
|
-
return;
|
|
1857
|
-
}
|
|
1858
|
-
this.librariesWatcher = watch(
|
|
1859
|
-
dirPath,
|
|
1860
|
-
{ recursive: true },
|
|
1861
|
-
async (event, filename) => {
|
|
1862
|
-
if (filename && (filename.endsWith(".css") || filename.endsWith(".js"))) {
|
|
1863
|
-
if (this.callbacks.onLibraryChange) {
|
|
1864
|
-
await this.callbacks.onLibraryChange();
|
|
1865
|
-
}
|
|
1866
|
-
}
|
|
1867
|
-
}
|
|
1868
|
-
);
|
|
1869
|
-
}
|
|
1870
|
-
/**
|
|
1871
|
-
* Astro-format projects keep pages/components/CMS under src/ as .astro/.json.
|
|
1872
|
-
* The onPageChange/onComponentChange/onCMSChange callbacks are format-agnostic
|
|
1873
|
-
* (reload goes through the active provider), so file→editor live reload works
|
|
1874
|
-
* for `.astro` the same way it does for JSON. These are no-ops in JSON projects
|
|
1875
|
-
* (the src/ dirs don't exist).
|
|
1876
|
-
*/
|
|
1877
|
-
watchAstroPages(dirPath = join3(getProjectRoot(), "src", "pages")) {
|
|
1878
|
-
if (!existsSync3(dirPath)) return;
|
|
1879
|
-
this.astroPagesWatcher = watch(dirPath, { recursive: true }, async (_event, filename) => {
|
|
1880
|
-
if (filename && filename.endsWith(".astro") && !filename.includes("[")) {
|
|
1881
|
-
const pagePath = mapPageNameToPath(filename.replace(/\.astro$/, ""));
|
|
1882
|
-
if (this.callbacks.onPageChange) await this.callbacks.onPageChange(pagePath);
|
|
1883
|
-
}
|
|
1884
|
-
});
|
|
1885
|
-
}
|
|
1886
|
-
watchAstroComponents(dirPath = join3(getProjectRoot(), "src", "components")) {
|
|
1887
|
-
if (!existsSync3(dirPath)) return;
|
|
1888
|
-
this.astroComponentsWatcher = watch(dirPath, { recursive: true }, async (_event, filename) => {
|
|
1889
|
-
if (filename && filename.endsWith(".astro") && this.callbacks.onComponentChange) {
|
|
1890
|
-
await this.callbacks.onComponentChange();
|
|
1891
|
-
}
|
|
1892
|
-
});
|
|
1893
|
-
}
|
|
1894
|
-
watchAstroContent(dirPath = join3(getProjectRoot(), "src", "content")) {
|
|
1895
|
-
if (!existsSync3(dirPath)) return;
|
|
1896
|
-
this.astroContentWatcher = watch(dirPath, { recursive: true }, async (_event, filename) => {
|
|
1897
|
-
if (filename && filename.endsWith(".json") && this.callbacks.onCMSChange) {
|
|
1898
|
-
const collection = filename.split("/")[0];
|
|
1899
|
-
await this.callbacks.onCMSChange(collection);
|
|
1900
|
-
}
|
|
1901
|
-
});
|
|
1902
|
-
}
|
|
1903
|
-
/**
|
|
1904
|
-
* Start watching all directories
|
|
1905
|
-
*/
|
|
1906
|
-
watchAll() {
|
|
1907
|
-
this.watchComponents();
|
|
1908
|
-
this.watchPages();
|
|
1909
|
-
this.watchTemplates();
|
|
1910
|
-
this.watchColors();
|
|
1911
|
-
this.watchVariables();
|
|
1912
|
-
this.watchEnums();
|
|
1913
|
-
this.watchCMS();
|
|
1914
|
-
this.watchImages();
|
|
1915
|
-
this.watchLibraries();
|
|
1916
|
-
this.watchProjectConfig();
|
|
1917
|
-
this.watchAstroPages();
|
|
1918
|
-
this.watchAstroComponents();
|
|
1919
|
-
this.watchAstroContent();
|
|
1920
|
-
}
|
|
1921
|
-
/**
|
|
1922
|
-
* Stop watching all directories
|
|
1923
|
-
*/
|
|
1924
|
-
stopAll() {
|
|
1925
|
-
if (this.componentsWatcher) {
|
|
1926
|
-
this.componentsWatcher.close();
|
|
1927
|
-
this.componentsWatcher = null;
|
|
1928
|
-
}
|
|
1929
|
-
if (this.pagesWatcher) {
|
|
1930
|
-
this.pagesWatcher.close();
|
|
1931
|
-
this.pagesWatcher = null;
|
|
1932
|
-
}
|
|
1933
|
-
if (this.templatesWatcher) {
|
|
1934
|
-
this.templatesWatcher.close();
|
|
1935
|
-
this.templatesWatcher = null;
|
|
1936
|
-
}
|
|
1937
|
-
if (this.colorsWatcher) {
|
|
1938
|
-
this.colorsWatcher.close();
|
|
1939
|
-
this.colorsWatcher = null;
|
|
1940
|
-
}
|
|
1941
|
-
if (this.variablesWatcher) {
|
|
1942
|
-
this.variablesWatcher.close();
|
|
1943
|
-
this.variablesWatcher = null;
|
|
1944
|
-
}
|
|
1945
|
-
if (this.enumsWatcher) {
|
|
1946
|
-
this.enumsWatcher.close();
|
|
1947
|
-
this.enumsWatcher = null;
|
|
1948
|
-
}
|
|
1949
|
-
if (this.cmsWatcher) {
|
|
1950
|
-
this.cmsWatcher.close();
|
|
1951
|
-
this.cmsWatcher = null;
|
|
1952
|
-
}
|
|
1953
|
-
if (this.imagesWatcher) {
|
|
1954
|
-
this.imagesWatcher.close();
|
|
1955
|
-
this.imagesWatcher = null;
|
|
1956
|
-
}
|
|
1957
|
-
if (this.librariesWatcher) {
|
|
1958
|
-
this.librariesWatcher.close();
|
|
1959
|
-
this.librariesWatcher = null;
|
|
1960
|
-
}
|
|
1961
|
-
if (this.projectConfigWatcher) {
|
|
1962
|
-
this.projectConfigWatcher.close();
|
|
1963
|
-
this.projectConfigWatcher = null;
|
|
1964
|
-
}
|
|
1965
|
-
if (this.astroPagesWatcher) {
|
|
1966
|
-
this.astroPagesWatcher.close();
|
|
1967
|
-
this.astroPagesWatcher = null;
|
|
1968
|
-
}
|
|
1969
|
-
if (this.astroComponentsWatcher) {
|
|
1970
|
-
this.astroComponentsWatcher.close();
|
|
1971
|
-
this.astroComponentsWatcher = null;
|
|
1972
|
-
}
|
|
1973
|
-
if (this.astroContentWatcher) {
|
|
1974
|
-
this.astroContentWatcher.close();
|
|
1975
|
-
this.astroContentWatcher = null;
|
|
1976
|
-
}
|
|
1977
|
-
}
|
|
1978
|
-
/**
|
|
1979
|
-
* Check if watchers are active
|
|
1980
|
-
*/
|
|
1981
|
-
isWatching() {
|
|
1982
|
-
return this.componentsWatcher !== null || this.pagesWatcher !== null || this.cmsWatcher !== null || this.imagesWatcher !== null;
|
|
1983
|
-
}
|
|
1984
|
-
};
|
|
1985
|
-
|
|
1986
|
-
// lib/server/services/fileWatcherService.ts
|
|
1987
|
-
var FileWatcherService = class {
|
|
1988
|
-
constructor(componentService, pageService, pageCache, wsManager, cmsService, onImageAdded) {
|
|
1989
|
-
this.componentService = componentService;
|
|
1990
|
-
this.pageService = pageService;
|
|
1991
|
-
this.pageCache = pageCache;
|
|
1992
|
-
this.wsManager = wsManager;
|
|
1993
|
-
this.cmsService = cmsService;
|
|
1994
|
-
this.onImageAdded = onImageAdded;
|
|
1995
|
-
}
|
|
1996
|
-
fileWatcher = null;
|
|
1997
|
-
/** Timer for debouncing refreshSchemas calls from burst fs.watch events */
|
|
1998
|
-
refreshSchemasTimer = null;
|
|
1999
|
-
/**
|
|
2000
|
-
* Initialize file watchers
|
|
2001
|
-
*/
|
|
2002
|
-
initialize() {
|
|
2003
|
-
this.fileWatcher = new FileWatcher({
|
|
2004
|
-
onComponentChange: async () => {
|
|
2005
|
-
await this.componentService.loadAllComponents();
|
|
2006
|
-
this.wsManager.broadcastUpdate("all");
|
|
2007
|
-
},
|
|
2008
|
-
onPageChange: async (pagePath) => {
|
|
2009
|
-
if (await this.pageService.reloadPageFromDisk(pagePath)) {
|
|
2010
|
-
this.wsManager.broadcastUpdate(pagePath);
|
|
2011
|
-
}
|
|
2012
|
-
if (pagePath.startsWith("/templates/") && this.cmsService) {
|
|
2013
|
-
if (this.refreshSchemasTimer !== null) {
|
|
2014
|
-
clearTimeout(this.refreshSchemasTimer);
|
|
2015
|
-
}
|
|
2016
|
-
const cmsService = this.cmsService;
|
|
2017
|
-
const wsManager = this.wsManager;
|
|
2018
|
-
this.refreshSchemasTimer = setTimeout(async () => {
|
|
2019
|
-
this.refreshSchemasTimer = null;
|
|
2020
|
-
await cmsService.refreshSchemas();
|
|
2021
|
-
wsManager.broadcastCollectionsUpdate();
|
|
2022
|
-
}, 50);
|
|
2023
|
-
}
|
|
2024
|
-
},
|
|
2025
|
-
onColorsChange: async () => {
|
|
2026
|
-
colorService.clearCache();
|
|
2027
|
-
this.wsManager.broadcastColorsUpdate();
|
|
2028
|
-
},
|
|
2029
|
-
onVariablesChange: async () => {
|
|
2030
|
-
variableService.clearCache();
|
|
2031
|
-
this.wsManager.broadcastVariablesUpdate();
|
|
2032
|
-
},
|
|
2033
|
-
onEnumsChange: async () => {
|
|
2034
|
-
enumService.clearCache();
|
|
2035
|
-
this.wsManager.broadcastEnumsUpdate();
|
|
2036
|
-
},
|
|
2037
|
-
onCMSChange: async (collection) => {
|
|
2038
|
-
this.cmsService?.clearItemsCache(collection);
|
|
2039
|
-
this.wsManager.broadcastCMSUpdate(collection);
|
|
2040
|
-
},
|
|
2041
|
-
onImageAdded: this.onImageAdded,
|
|
2042
|
-
onLibraryChange: async () => {
|
|
2043
|
-
this.wsManager.broadcastLibrariesUpdate();
|
|
2044
|
-
},
|
|
2045
|
-
onProjectConfigChange: async () => {
|
|
2046
|
-
configService.reset();
|
|
2047
|
-
await configService.load();
|
|
2048
|
-
this.wsManager.broadcastConfigUpdate();
|
|
2049
|
-
}
|
|
2050
|
-
});
|
|
2051
|
-
this.fileWatcher.watchAll();
|
|
2052
|
-
}
|
|
2053
|
-
/**
|
|
2054
|
-
* Stop file watchers
|
|
2055
|
-
*/
|
|
2056
|
-
stop() {
|
|
2057
|
-
if (this.refreshSchemasTimer !== null) {
|
|
2058
|
-
clearTimeout(this.refreshSchemasTimer);
|
|
2059
|
-
this.refreshSchemasTimer = null;
|
|
2060
|
-
}
|
|
2061
|
-
if (this.fileWatcher) {
|
|
2062
|
-
this.fileWatcher.stopAll();
|
|
2063
|
-
this.fileWatcher = null;
|
|
2064
|
-
}
|
|
2065
|
-
}
|
|
2066
|
-
};
|
|
2067
|
-
|
|
2068
|
-
// lib/server/middleware/cors.ts
|
|
2069
|
-
var DEFAULT_OPTIONS = {
|
|
2070
|
-
origin: "*",
|
|
2071
|
-
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
|
2072
|
-
allowedHeaders: ["Content-Type", "Authorization"],
|
|
2073
|
-
credentials: false
|
|
2074
|
-
};
|
|
2075
|
-
function createCorsHeaders(options = {}) {
|
|
2076
|
-
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
2077
|
-
const headers = {};
|
|
2078
|
-
if (opts.origin) {
|
|
2079
|
-
if (typeof opts.origin === "function") {
|
|
2080
|
-
headers["Access-Control-Allow-Origin"] = "*";
|
|
2081
|
-
} else if (Array.isArray(opts.origin)) {
|
|
2082
|
-
headers["Access-Control-Allow-Origin"] = opts.origin.join(", ");
|
|
2083
|
-
} else {
|
|
2084
|
-
headers["Access-Control-Allow-Origin"] = opts.origin;
|
|
2085
|
-
}
|
|
2086
|
-
}
|
|
2087
|
-
if (opts.methods && opts.methods.length > 0) {
|
|
2088
|
-
headers["Access-Control-Allow-Methods"] = opts.methods.join(", ");
|
|
2089
|
-
}
|
|
2090
|
-
if (opts.allowedHeaders && opts.allowedHeaders.length > 0) {
|
|
2091
|
-
headers["Access-Control-Allow-Headers"] = opts.allowedHeaders.join(", ");
|
|
2092
|
-
}
|
|
2093
|
-
if (opts.credentials) {
|
|
2094
|
-
headers["Access-Control-Allow-Credentials"] = "true";
|
|
2095
|
-
}
|
|
2096
|
-
return headers;
|
|
2097
|
-
}
|
|
2098
|
-
function handleCorsPreflight(req, options = {}) {
|
|
2099
|
-
if (req.method === "OPTIONS") {
|
|
2100
|
-
return new Response(null, {
|
|
2101
|
-
status: 204,
|
|
2102
|
-
headers: createCorsHeaders(options)
|
|
2103
|
-
});
|
|
2104
|
-
}
|
|
2105
|
-
return null;
|
|
2106
|
-
}
|
|
2107
|
-
|
|
2108
|
-
// lib/server/middleware/errorHandler.ts
|
|
2109
|
-
function createErrorResponse(error, status = 500, message) {
|
|
2110
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2111
|
-
const response = {
|
|
2112
|
-
error: errorMessage,
|
|
2113
|
-
status
|
|
2114
|
-
};
|
|
2115
|
-
if (message) {
|
|
2116
|
-
response.message = message;
|
|
2117
|
-
}
|
|
2118
|
-
return new Response(JSON.stringify(response), {
|
|
2119
|
-
status,
|
|
2120
|
-
headers: {
|
|
2121
|
-
"Content-Type": "application/json"
|
|
2122
|
-
}
|
|
2123
|
-
});
|
|
2124
|
-
}
|
|
2125
|
-
async function handleRouteError(handler, errorMessage = "Internal server error") {
|
|
2126
|
-
try {
|
|
2127
|
-
return await handler();
|
|
2128
|
-
} catch (error) {
|
|
2129
|
-
return createErrorResponse(error, 500, errorMessage);
|
|
2130
|
-
}
|
|
2131
|
-
}
|
|
2132
|
-
function withErrorHandling(handler, errorMessage) {
|
|
2133
|
-
return async (...args) => {
|
|
2134
|
-
return handleRouteError(() => handler(...args), errorMessage);
|
|
2135
|
-
};
|
|
2136
|
-
}
|
|
2137
|
-
|
|
2138
|
-
// lib/server/middleware/logger.ts
|
|
2139
|
-
var DEFAULT_OPTIONS2 = {
|
|
2140
|
-
logRequests: true,
|
|
2141
|
-
logErrors: true,
|
|
2142
|
-
logResponseTime: true
|
|
2143
|
-
};
|
|
2144
|
-
function logRequest(req, options = {}) {
|
|
2145
|
-
const opts = { ...DEFAULT_OPTIONS2, ...options };
|
|
2146
|
-
if (!opts.logRequests) {
|
|
2147
|
-
return;
|
|
2148
|
-
}
|
|
2149
|
-
const url = new URL(req.url);
|
|
2150
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2151
|
-
}
|
|
2152
|
-
function logError(error, context, options = {}) {
|
|
2153
|
-
const opts = { ...DEFAULT_OPTIONS2, ...options };
|
|
2154
|
-
if (!opts.logErrors) {
|
|
2155
|
-
return;
|
|
2156
|
-
}
|
|
2157
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2158
|
-
const contextStr = context ? ` [${context}]` : "";
|
|
2159
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2160
|
-
if (error instanceof Error && error.stack) {
|
|
2161
|
-
}
|
|
2162
|
-
}
|
|
2163
|
-
function logResponseTime(startTime, req, options = {}) {
|
|
2164
|
-
const opts = { ...DEFAULT_OPTIONS2, ...options };
|
|
2165
|
-
if (!opts.logResponseTime) {
|
|
2166
|
-
return;
|
|
2167
|
-
}
|
|
2168
|
-
const url = new URL(req.url);
|
|
2169
|
-
const duration = Date.now() - startTime;
|
|
2170
|
-
}
|
|
2171
|
-
function withLogging(handler, options = {}) {
|
|
2172
|
-
return async (...args) => {
|
|
2173
|
-
const startTime = Date.now();
|
|
2174
|
-
const req = args[0];
|
|
2175
|
-
logRequest(req, options);
|
|
2176
|
-
try {
|
|
2177
|
-
const response = await handler(...args);
|
|
2178
|
-
logResponseTime(startTime, req, options);
|
|
2179
|
-
return response;
|
|
2180
|
-
} catch (error) {
|
|
2181
|
-
logError(error, req.method, options);
|
|
2182
|
-
logResponseTime(startTime, req, options);
|
|
2183
|
-
throw error;
|
|
2184
|
-
}
|
|
2185
|
-
};
|
|
2186
|
-
}
|
|
2187
|
-
|
|
2188
|
-
// lib/server/routes/api/shared.ts
|
|
2189
|
-
function jsonResponse(data, options = {}) {
|
|
2190
|
-
const corsHeaders = createCorsHeaders();
|
|
2191
|
-
return new Response(JSON.stringify(data), {
|
|
2192
|
-
status: options.status || 200,
|
|
2193
|
-
headers: {
|
|
2194
|
-
"Content-Type": "application/json",
|
|
2195
|
-
"Cache-Control": "no-store, max-age=0",
|
|
2196
|
-
"Pragma": "no-cache",
|
|
2197
|
-
"Expires": "0",
|
|
2198
|
-
...corsHeaders
|
|
2199
|
-
}
|
|
2200
|
-
});
|
|
2201
|
-
}
|
|
2202
|
-
function errorResponse(message, status = 500) {
|
|
2203
|
-
return jsonResponse({ error: message }, { status });
|
|
2204
|
-
}
|
|
2205
|
-
function fnv1a(str) {
|
|
2206
|
-
let hash = 2166136261;
|
|
2207
|
-
for (let i = 0; i < str.length; i++) {
|
|
2208
|
-
hash ^= str.charCodeAt(i);
|
|
2209
|
-
hash = Math.imul(hash, 16777619);
|
|
2210
|
-
}
|
|
2211
|
-
return (hash >>> 0).toString(36);
|
|
2212
|
-
}
|
|
2213
|
-
function cachedJsonResponse(req, body, options = {}) {
|
|
2214
|
-
const corsHeaders = createCorsHeaders();
|
|
2215
|
-
const contentType = options.contentType ?? "application/json";
|
|
2216
|
-
const etag = `W/"${fnv1a(body)}"`;
|
|
2217
|
-
const ifNoneMatch = req.headers.get("if-none-match");
|
|
2218
|
-
if (ifNoneMatch && ifNoneMatch === etag) {
|
|
2219
|
-
return new Response(null, {
|
|
2220
|
-
status: 304,
|
|
2221
|
-
headers: {
|
|
2222
|
-
"ETag": etag,
|
|
2223
|
-
"Cache-Control": "no-cache",
|
|
2224
|
-
...corsHeaders
|
|
2225
|
-
}
|
|
2226
|
-
});
|
|
2227
|
-
}
|
|
2228
|
-
return new Response(body, {
|
|
2229
|
-
status: options.status ?? 200,
|
|
2230
|
-
headers: {
|
|
2231
|
-
"Content-Type": contentType,
|
|
2232
|
-
"ETag": etag,
|
|
2233
|
-
// `no-cache` (not `no-store`) — browser may keep the body but must
|
|
2234
|
-
// revalidate every time. Pairs with the 304 branch above.
|
|
2235
|
-
"Cache-Control": "no-cache",
|
|
2236
|
-
...corsHeaders
|
|
2237
|
-
}
|
|
2238
|
-
});
|
|
2239
|
-
}
|
|
2240
|
-
|
|
2241
|
-
// lib/server/routes/api/cms.ts
|
|
2242
|
-
function handleCollectionsRoute(cmsService) {
|
|
2243
|
-
const schemas = cmsService.getAllSchemas();
|
|
2244
|
-
const collections = [];
|
|
2245
|
-
for (const [, schemaInfo] of schemas) {
|
|
2246
|
-
collections.push(schemaInfo.schema);
|
|
2247
|
-
}
|
|
2248
|
-
return jsonResponse({ collections });
|
|
2249
|
-
}
|
|
2250
|
-
function handleCollectionSchemaRoute(collection, cmsService) {
|
|
2251
|
-
const schema = cmsService.getSchema(collection);
|
|
2252
|
-
if (!schema) {
|
|
2253
|
-
return errorResponse("Collection not found", 404);
|
|
2254
|
-
}
|
|
2255
|
-
return jsonResponse({ schema });
|
|
2256
|
-
}
|
|
2257
|
-
async function handleItemsRoute(collection, cmsService, searchParams) {
|
|
2258
|
-
const limit = searchParams?.get("limit") ? parseInt(searchParams.get("limit"), 10) : void 0;
|
|
2259
|
-
const offset = searchParams?.get("offset") ? parseInt(searchParams.get("offset"), 10) : void 0;
|
|
2260
|
-
const search = searchParams?.get("search") || void 0;
|
|
2261
|
-
const allItems = await cmsService.queryItems({ collection });
|
|
2262
|
-
const total = allItems.length;
|
|
2263
|
-
let filteredItems = allItems;
|
|
2264
|
-
if (search) {
|
|
2265
|
-
const searchLower = search.toLowerCase();
|
|
2266
|
-
const schema2 = cmsService.getSchema(collection);
|
|
2267
|
-
const slugField = schema2?.slugField || "slug";
|
|
2268
|
-
filteredItems = allItems.filter((item) => {
|
|
2269
|
-
const title = extractStringValue(item.title || "").toLowerCase();
|
|
2270
|
-
const slug = extractStringValue(item[slugField] || item._slug || "").toLowerCase();
|
|
2271
|
-
const filename = String(item._filename || "").toLowerCase();
|
|
2272
|
-
return title.includes(searchLower) || slug.includes(searchLower) || filename.includes(searchLower);
|
|
2273
|
-
});
|
|
2274
|
-
}
|
|
2275
|
-
let items = filteredItems;
|
|
2276
|
-
if (offset !== void 0 && offset > 0) {
|
|
2277
|
-
items = items.slice(offset);
|
|
2278
|
-
}
|
|
2279
|
-
if (limit !== void 0 && limit > 0) {
|
|
2280
|
-
items = items.slice(0, limit);
|
|
2281
|
-
}
|
|
2282
|
-
const schema = cmsService.getSchema(collection);
|
|
2283
|
-
const itemsWithUrl = schema ? addItemUrls(items, schema) : items;
|
|
2284
|
-
return jsonResponse({
|
|
2285
|
-
items: itemsWithUrl,
|
|
2286
|
-
total: search ? filteredItems.length : total,
|
|
2287
|
-
limit,
|
|
2288
|
-
offset: offset || 0
|
|
2289
|
-
});
|
|
2290
|
-
}
|
|
2291
|
-
async function handleItemRoute(collection, slug, cmsService) {
|
|
2292
|
-
const match = await cmsService.matchRoute(
|
|
2293
|
-
cmsService.getSchema(collection)?.urlPattern.replace("{{slug}}", slug) || ""
|
|
2294
|
-
);
|
|
2295
|
-
if (!match) {
|
|
2296
|
-
const items = await cmsService.queryItems({ collection });
|
|
2297
|
-
const item = items.find((i) => i._slug === slug || i[cmsService.getSchema(collection)?.slugField || "slug"] === slug);
|
|
2298
|
-
if (!item) {
|
|
2299
|
-
return errorResponse("Item not found", 404);
|
|
2300
|
-
}
|
|
2301
|
-
return jsonResponse({ item });
|
|
2302
|
-
}
|
|
2303
|
-
return jsonResponse({ item: match.item });
|
|
2304
|
-
}
|
|
2305
|
-
|
|
2306
|
-
// lib/server/routes/api/core-routes.ts
|
|
2307
|
-
init_constants();
|
|
2308
|
-
|
|
2309
|
-
// lib/server/routes/api/pages.ts
|
|
2310
|
-
init_constants();
|
|
2311
|
-
function handlePagesRoute(pageService) {
|
|
2312
|
-
const pages = pageService.getAllPagesWithInfo();
|
|
2313
|
-
return jsonResponse({ pages });
|
|
2314
|
-
}
|
|
2315
|
-
function handlePageDataRoute(url, pageService) {
|
|
2316
|
-
const pagePath = url.pathname.replace(API_ROUTES.PAGE_DATA, "") || "/";
|
|
2317
|
-
const content = pageService.getPage(pagePath);
|
|
2318
|
-
if (content) {
|
|
2319
|
-
try {
|
|
2320
|
-
const parsedContent = parseJSON(content);
|
|
2321
|
-
const lineMap = pageService.getLineMap(pagePath);
|
|
2322
|
-
const lineMapObj = {};
|
|
2323
|
-
if (lineMap) {
|
|
2324
|
-
lineMap.forEach((value, key) => {
|
|
2325
|
-
lineMapObj[key] = value;
|
|
2326
|
-
});
|
|
2327
|
-
}
|
|
2328
|
-
return jsonResponse({ ...parsedContent, _lineMap: lineMapObj });
|
|
2329
|
-
} catch (error) {
|
|
2330
|
-
return jsonResponse({ error: "Invalid JSON" }, { status: 500 });
|
|
2331
|
-
}
|
|
2332
|
-
} else {
|
|
2333
|
-
return jsonResponse({ error: "Page not found" }, { status: 404 });
|
|
2334
|
-
}
|
|
2335
|
-
}
|
|
2336
|
-
function handlePageContentRoute(req, url, pageService) {
|
|
2337
|
-
const page = url.searchParams.get("page") || "/";
|
|
2338
|
-
const content = pageService.getPage(page);
|
|
2339
|
-
if (content) {
|
|
2340
|
-
return cachedJsonResponse(req, content);
|
|
2341
|
-
}
|
|
2342
|
-
const corsHeaders = createCorsHeaders();
|
|
2343
|
-
return new Response("Page not found", {
|
|
2344
|
-
status: 404,
|
|
2345
|
-
headers: {
|
|
2346
|
-
"Content-Type": "application/json",
|
|
2347
|
-
"Cache-Control": "no-store, max-age=0",
|
|
2348
|
-
...corsHeaders
|
|
2349
|
-
}
|
|
2350
|
-
});
|
|
2351
|
-
}
|
|
2352
|
-
function handleSlugMappingsRoute(pageService) {
|
|
2353
|
-
const mappings = pageService.getSlugMappings();
|
|
2354
|
-
return jsonResponse({ mappings });
|
|
2355
|
-
}
|
|
2356
|
-
|
|
2357
|
-
// lib/server/routes/api/components.ts
|
|
2358
|
-
function handleComponentsRoute(req, componentService) {
|
|
2359
|
-
const componentsWithCategories = componentService.getAllComponentsWithCategories();
|
|
2360
|
-
const result = {};
|
|
2361
|
-
for (const [name, info] of Object.entries(componentsWithCategories)) {
|
|
2362
|
-
result[name] = {
|
|
2363
|
-
...info.definition,
|
|
2364
|
-
_category: info.category
|
|
2365
|
-
};
|
|
2366
|
-
}
|
|
2367
|
-
const diagnostics = componentService.getLoadDiagnostics();
|
|
2368
|
-
if (diagnostics.errors.length > 0 || diagnostics.warnings.length > 0) {
|
|
2369
|
-
result._diagnostics = diagnostics;
|
|
2370
|
-
}
|
|
2371
|
-
return cachedJsonResponse(req, JSON.stringify(result));
|
|
2372
|
-
}
|
|
2373
|
-
function handleComponentDataRoute(url, componentService) {
|
|
2374
|
-
const componentName = url.pathname.replace("/api/component-data/", "");
|
|
2375
|
-
const componentDef = componentService.getComponent(componentName);
|
|
2376
|
-
if (!componentDef) {
|
|
2377
|
-
const loadErrors = componentService.getLoadErrors();
|
|
2378
|
-
const diagnostic = loadErrors.get(componentName);
|
|
2379
|
-
if (diagnostic) {
|
|
2380
|
-
const message = formatJsonErrorMessage(
|
|
2381
|
-
`${diagnostic.componentName}.json`,
|
|
2382
|
-
{ message: diagnostic.description, line: diagnostic.line, column: diagnostic.column }
|
|
2383
|
-
);
|
|
2384
|
-
return jsonResponse(
|
|
2385
|
-
{ error: "json_parse_error", message, errorCode: "JSON_PARSE_ERROR" },
|
|
2386
|
-
{ status: 422 }
|
|
2387
|
-
);
|
|
2388
|
-
}
|
|
2389
|
-
return jsonResponse({ error: "Component not found" }, { status: 404 });
|
|
2390
|
-
}
|
|
2391
|
-
if (!componentService.validateComponentStructure(componentDef)) {
|
|
2392
|
-
return jsonResponse(
|
|
2393
|
-
{
|
|
2394
|
-
error: "Component structure is missing",
|
|
2395
|
-
message: `Component "${componentName}" does not have a valid structure definition. Please add a structure field to the component definition.`
|
|
2396
|
-
},
|
|
2397
|
-
{ status: 400 }
|
|
2398
|
-
);
|
|
2399
|
-
}
|
|
2400
|
-
return jsonResponse(componentDef);
|
|
2401
|
-
}
|
|
2402
|
-
async function handleComponentJavaScriptRoute(url, componentService) {
|
|
2403
|
-
const componentName = url.pathname.replace("/api/component-js/", "");
|
|
2404
|
-
const jsContent = await componentService.getComponentJavaScript(componentName);
|
|
2405
|
-
const corsHeaders = createCorsHeaders();
|
|
2406
|
-
return new Response(jsContent || "", {
|
|
2407
|
-
headers: {
|
|
2408
|
-
"Content-Type": "text/javascript",
|
|
2409
|
-
...corsHeaders
|
|
2410
|
-
}
|
|
2411
|
-
});
|
|
2412
|
-
}
|
|
2413
|
-
function handleComponentUsageRoute(pageService, componentService) {
|
|
2414
|
-
const usageMap = /* @__PURE__ */ new Map();
|
|
2415
|
-
function walkTree(node, fileName, filePath, fileType) {
|
|
2416
|
-
if (!node || typeof node !== "object") return;
|
|
2417
|
-
if (node.type === "component" && "component" in node && node.component) {
|
|
2418
|
-
const compName = node.component;
|
|
2419
|
-
if (!usageMap.has(compName)) {
|
|
2420
|
-
usageMap.set(compName, { count: 0, files: /* @__PURE__ */ new Map() });
|
|
2421
|
-
}
|
|
2422
|
-
const entry = usageMap.get(compName);
|
|
2423
|
-
entry.count++;
|
|
2424
|
-
entry.files.set(filePath, { name: fileName, path: filePath, type: fileType });
|
|
2425
|
-
}
|
|
2426
|
-
if ("children" in node && Array.isArray(node.children)) {
|
|
2427
|
-
for (const child of node.children) {
|
|
2428
|
-
if (child && typeof child === "object") {
|
|
2429
|
-
walkTree(child, fileName, filePath, fileType);
|
|
2430
|
-
}
|
|
2431
|
-
}
|
|
2432
|
-
}
|
|
2433
|
-
}
|
|
2434
|
-
const pagePaths = pageService.getAllPagePaths();
|
|
2435
|
-
for (const path2 of pagePaths) {
|
|
2436
|
-
const pageData = pageService.getPageData(path2);
|
|
2437
|
-
if (pageData && "root" in pageData && pageData.root) {
|
|
2438
|
-
const name = path2 === "/" ? "index" : path2.replace(/^\//, "").replace(/\//g, "-");
|
|
2439
|
-
walkTree(pageData.root, name, path2, "page");
|
|
2440
|
-
}
|
|
2441
|
-
}
|
|
2442
|
-
const allComponents = componentService.getAllComponents();
|
|
2443
|
-
for (const [compName, compDef] of Object.entries(allComponents)) {
|
|
2444
|
-
if (compDef.component?.structure) {
|
|
2445
|
-
walkTree(compDef.component.structure, compName, compName, "component");
|
|
2446
|
-
}
|
|
2447
|
-
}
|
|
2448
|
-
const result = {};
|
|
2449
|
-
for (const [name, data] of usageMap) {
|
|
2450
|
-
result[name] = {
|
|
2451
|
-
count: data.count,
|
|
2452
|
-
files: Array.from(data.files.values())
|
|
2453
|
-
};
|
|
2454
|
-
}
|
|
2455
|
-
return jsonResponse(result);
|
|
2456
|
-
}
|
|
2457
|
-
|
|
2458
|
-
// lib/server/routes/api/config.ts
|
|
2459
|
-
async function handleConfigRoute() {
|
|
2460
|
-
try {
|
|
2461
|
-
const configContent = await loadJSONFile(projectPaths.config());
|
|
2462
|
-
if (configContent) {
|
|
2463
|
-
const config = parseJSON(configContent);
|
|
2464
|
-
return jsonResponse(config);
|
|
2465
|
-
} else {
|
|
2466
|
-
return jsonResponse({ editor: { theme: "dark" } });
|
|
2467
|
-
}
|
|
2468
|
-
} catch (error) {
|
|
2469
|
-
return jsonResponse({ editor: { theme: "dark" } });
|
|
2470
|
-
}
|
|
2471
|
-
}
|
|
2472
|
-
async function handleComponentsConfigRoute() {
|
|
2473
|
-
try {
|
|
2474
|
-
const configContent = await loadJSONFile(projectPaths.componentsConfig());
|
|
2475
|
-
if (configContent) {
|
|
2476
|
-
const config = parseJSON(configContent);
|
|
2477
|
-
return jsonResponse(config);
|
|
2478
|
-
} else {
|
|
2479
|
-
return jsonResponse({ componentTypes: [], componentCategories: {} });
|
|
2480
|
-
}
|
|
2481
|
-
} catch (error) {
|
|
2482
|
-
return jsonResponse({ componentTypes: [], componentCategories: {} });
|
|
2483
|
-
}
|
|
2484
|
-
}
|
|
2485
|
-
|
|
2486
|
-
// lib/server/routes/api/colors.ts
|
|
2487
|
-
async function handleColorsRoute(url) {
|
|
2488
|
-
const themeName = url?.searchParams.get("theme") || void 0;
|
|
2489
|
-
const colors = await colorService.getThemeColors(themeName);
|
|
2490
|
-
return jsonResponse(colors);
|
|
2491
|
-
}
|
|
2492
|
-
async function handleThemesRoute() {
|
|
2493
|
-
const themes = await colorService.getAvailableThemes();
|
|
2494
|
-
const defaultTheme = await colorService.getDefaultTheme();
|
|
2495
|
-
return jsonResponse({ themes, default: defaultTheme });
|
|
2496
|
-
}
|
|
2497
|
-
async function handleColorsConfigRoute() {
|
|
2498
|
-
const config = await colorService.getFullConfig();
|
|
2499
|
-
return jsonResponse(config);
|
|
2500
|
-
}
|
|
2501
|
-
async function handleColorsStatusRoute() {
|
|
2502
|
-
const result = await colorService.getConfigWithStatus();
|
|
2503
|
-
return jsonResponse(result);
|
|
2504
|
-
}
|
|
2505
|
-
|
|
2506
|
-
// lib/server/routes/api/variables.ts
|
|
2507
|
-
async function handleVariablesStatusRoute() {
|
|
2508
|
-
const result = await variableService.getConfigWithStatus();
|
|
2509
|
-
return jsonResponse(result);
|
|
2510
|
-
}
|
|
2511
|
-
async function handleVariablesCSSRoute() {
|
|
2512
|
-
await configService.load();
|
|
2513
|
-
const variablesConfig = await variableService.loadConfig();
|
|
2514
|
-
const breakpointConfig = await loadBreakpointConfig();
|
|
2515
|
-
const responsiveScalesConfig = configService.getResponsiveScales();
|
|
2516
|
-
const css = generateVariablesCSS(variablesConfig, breakpointConfig, responsiveScalesConfig);
|
|
2517
|
-
return new Response(css, {
|
|
2518
|
-
headers: { "Content-Type": "text/css" }
|
|
2519
|
-
});
|
|
2520
|
-
}
|
|
2521
|
-
|
|
2522
|
-
// lib/server/routes/api/enums.ts
|
|
2523
|
-
async function handleEnumsRoute() {
|
|
2524
|
-
const result = await enumService.getConfigWithStatus();
|
|
2525
|
-
return jsonResponse(result);
|
|
2526
|
-
}
|
|
2527
|
-
|
|
2528
|
-
// lib/server/routes/api/functions.ts
|
|
2529
|
-
import path from "path";
|
|
2530
|
-
async function loadProjectEnv() {
|
|
2531
|
-
const envPath = projectPaths.env();
|
|
2532
|
-
const env = {};
|
|
2533
|
-
try {
|
|
2534
|
-
if (await fileExists(envPath)) {
|
|
2535
|
-
const content = await readTextFile(envPath);
|
|
2536
|
-
for (const line of content.split("\n")) {
|
|
2537
|
-
const trimmed = line.trim();
|
|
2538
|
-
if (trimmed && !trimmed.startsWith("#")) {
|
|
2539
|
-
const [key, ...valueParts] = trimmed.split("=");
|
|
2540
|
-
if (key) {
|
|
2541
|
-
let value = valueParts.join("=");
|
|
2542
|
-
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
2543
|
-
value = value.slice(1, -1);
|
|
2544
|
-
}
|
|
2545
|
-
env[key.trim()] = value;
|
|
2546
|
-
}
|
|
2547
|
-
}
|
|
2548
|
-
}
|
|
2549
|
-
}
|
|
2550
|
-
} catch (error) {
|
|
2551
|
-
console.warn("Could not load .env file:", error);
|
|
2552
|
-
}
|
|
2553
|
-
return env;
|
|
2554
|
-
}
|
|
2555
|
-
async function handleFunctionsRoute(req, url) {
|
|
2556
|
-
if (!url.pathname.startsWith("/api/")) {
|
|
2557
|
-
return void 0;
|
|
2558
|
-
}
|
|
2559
|
-
if (url.pathname.startsWith("/api/editor/")) {
|
|
2560
|
-
return void 0;
|
|
2561
|
-
}
|
|
2562
|
-
const functionPath = url.pathname.replace("/api/", "");
|
|
2563
|
-
const functionFilePath = path.join(
|
|
2564
|
-
projectPaths.functions(),
|
|
2565
|
-
"api",
|
|
2566
|
-
`${functionPath}.ts`
|
|
2567
|
-
);
|
|
2568
|
-
if (!await fileExists(functionFilePath)) {
|
|
2569
|
-
const jsFilePath = functionFilePath.replace(".ts", ".js");
|
|
2570
|
-
if (!await fileExists(jsFilePath)) {
|
|
2571
|
-
return void 0;
|
|
2572
|
-
}
|
|
2573
|
-
}
|
|
2574
|
-
try {
|
|
2575
|
-
const moduleUrl = `${functionFilePath}?t=${Date.now()}`;
|
|
2576
|
-
const functionModule = await import(moduleUrl);
|
|
2577
|
-
const method = req.method.toUpperCase();
|
|
2578
|
-
const handlerName = `onRequest${method.charAt(0)}${method.slice(1).toLowerCase()}`;
|
|
2579
|
-
const handler = functionModule[handlerName] || functionModule.onRequest;
|
|
2580
|
-
if (!handler || typeof handler !== "function") {
|
|
2581
|
-
return new Response(
|
|
2582
|
-
JSON.stringify({ error: `No handler found for ${method} request` }),
|
|
2583
|
-
{ status: 405, headers: { "Content-Type": "application/json" } }
|
|
2584
|
-
);
|
|
2585
|
-
}
|
|
2586
|
-
const env = await loadProjectEnv();
|
|
2587
|
-
const context = {
|
|
2588
|
-
request: req,
|
|
2589
|
-
env,
|
|
2590
|
-
params: {},
|
|
2591
|
-
// Could parse URL params if needed
|
|
2592
|
-
waitUntil: () => {
|
|
2593
|
-
},
|
|
2594
|
-
// No-op in dev
|
|
2595
|
-
passThroughOnException: () => {
|
|
2596
|
-
}
|
|
2597
|
-
// No-op in dev
|
|
2598
|
-
};
|
|
2599
|
-
const response = await handler(context);
|
|
2600
|
-
const headers = new Headers(response.headers);
|
|
2601
|
-
headers.set("Access-Control-Allow-Origin", "*");
|
|
2602
|
-
headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
|
2603
|
-
headers.set("Access-Control-Allow-Headers", "Content-Type");
|
|
2604
|
-
return new Response(response.body, {
|
|
2605
|
-
status: response.status,
|
|
2606
|
-
statusText: response.statusText,
|
|
2607
|
-
headers
|
|
2608
|
-
});
|
|
2609
|
-
} catch (error) {
|
|
2610
|
-
console.error(`Error executing function ${functionPath}:`, error);
|
|
2611
|
-
return new Response(
|
|
2612
|
-
JSON.stringify({
|
|
2613
|
-
error: "Function execution error",
|
|
2614
|
-
message: error instanceof Error ? error.message : "Unknown error"
|
|
2615
|
-
}),
|
|
2616
|
-
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
2617
|
-
);
|
|
2618
|
-
}
|
|
2619
|
-
}
|
|
2620
|
-
function handleFunctionsPreflight(req, url) {
|
|
2621
|
-
if (req.method !== "OPTIONS" || !url.pathname.startsWith("/api/")) {
|
|
2622
|
-
return void 0;
|
|
2623
|
-
}
|
|
2624
|
-
return new Response(null, {
|
|
2625
|
-
status: 204,
|
|
2626
|
-
headers: {
|
|
2627
|
-
"Access-Control-Allow-Origin": "*",
|
|
2628
|
-
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
|
|
2629
|
-
"Access-Control-Allow-Headers": "Content-Type",
|
|
2630
|
-
"Access-Control-Max-Age": "86400"
|
|
2631
|
-
}
|
|
2632
|
-
});
|
|
2633
|
-
}
|
|
2634
|
-
|
|
2635
|
-
// lib/server/routes/api/core-routes.ts
|
|
2636
|
-
async function handleCoreApiRoutes(req, url, context) {
|
|
2637
|
-
const { pageService, componentService, cmsService } = context;
|
|
2638
|
-
const preflightResponse = handleFunctionsPreflight(req, url);
|
|
2639
|
-
if (preflightResponse) {
|
|
2640
|
-
return preflightResponse;
|
|
2641
|
-
}
|
|
2642
|
-
const functionsResponse = await handleFunctionsRoute(req, url);
|
|
2643
|
-
if (functionsResponse) {
|
|
2644
|
-
return functionsResponse;
|
|
2645
|
-
}
|
|
2646
|
-
if (url.pathname === API_ROUTES.PAGES && req.method === "GET") {
|
|
2647
|
-
return await handleRouteError(
|
|
2648
|
-
() => Promise.resolve(handlePagesRoute(pageService)),
|
|
2649
|
-
"Failed to fetch pages list"
|
|
2650
|
-
);
|
|
2651
|
-
}
|
|
2652
|
-
if (url.pathname.startsWith(API_ROUTES.PAGE_DATA) && req.method === "GET") {
|
|
2653
|
-
return await handleRouteError(
|
|
2654
|
-
() => Promise.resolve(handlePageDataRoute(url, pageService)),
|
|
2655
|
-
"Failed to fetch page data"
|
|
2656
|
-
);
|
|
2657
|
-
}
|
|
2658
|
-
if (url.pathname === API_ROUTES.PAGE_CONTENT && req.method === "GET") {
|
|
2659
|
-
return await handleRouteError(
|
|
2660
|
-
() => Promise.resolve(handlePageContentRoute(req, url, pageService)),
|
|
2661
|
-
"Failed to fetch page content"
|
|
2662
|
-
);
|
|
2663
|
-
}
|
|
2664
|
-
if (url.pathname === API_ROUTES.COMPONENTS && req.method === "GET") {
|
|
2665
|
-
return await handleRouteError(
|
|
2666
|
-
() => Promise.resolve(handleComponentsRoute(req, componentService)),
|
|
2667
|
-
"Failed to fetch components list"
|
|
2668
|
-
);
|
|
2669
|
-
}
|
|
2670
|
-
if (url.pathname.startsWith("/api/component-data/") && req.method === "GET") {
|
|
2671
|
-
return await handleRouteError(
|
|
2672
|
-
() => Promise.resolve(handleComponentDataRoute(url, componentService)),
|
|
2673
|
-
"Failed to fetch component data"
|
|
2674
|
-
);
|
|
2675
|
-
}
|
|
2676
|
-
if (url.pathname.startsWith("/api/component-js/") && req.method === "GET") {
|
|
2677
|
-
return await handleRouteError(
|
|
2678
|
-
() => handleComponentJavaScriptRoute(url, componentService),
|
|
2679
|
-
"Failed to fetch component JavaScript"
|
|
2680
|
-
);
|
|
2681
|
-
}
|
|
2682
|
-
if (url.pathname === "/api/config" && req.method === "GET") {
|
|
2683
|
-
return await handleRouteError(
|
|
2684
|
-
() => handleConfigRoute(),
|
|
2685
|
-
"Failed to fetch config"
|
|
2686
|
-
);
|
|
2687
|
-
}
|
|
2688
|
-
if (url.pathname === "/api/components-config" && req.method === "GET") {
|
|
2689
|
-
return await handleRouteError(
|
|
2690
|
-
() => handleComponentsConfigRoute(),
|
|
2691
|
-
"Failed to fetch components config"
|
|
2692
|
-
);
|
|
2693
|
-
}
|
|
2694
|
-
if (url.pathname === "/api/colors" && req.method === "GET") {
|
|
2695
|
-
return await handleRouteError(
|
|
2696
|
-
() => handleColorsRoute(url),
|
|
2697
|
-
"Failed to fetch colors"
|
|
2698
|
-
);
|
|
2699
|
-
}
|
|
2700
|
-
if (url.pathname === "/api/themes" && req.method === "GET") {
|
|
2701
|
-
return await handleRouteError(
|
|
2702
|
-
() => handleThemesRoute(),
|
|
2703
|
-
"Failed to fetch themes"
|
|
2704
|
-
);
|
|
2705
|
-
}
|
|
2706
|
-
if (url.pathname === "/api/colors-config" && req.method === "GET") {
|
|
2707
|
-
return await handleRouteError(
|
|
2708
|
-
() => handleColorsConfigRoute(),
|
|
2709
|
-
"Failed to fetch colors config"
|
|
2710
|
-
);
|
|
2711
|
-
}
|
|
2712
|
-
if (url.pathname === "/api/colors-status" && req.method === "GET") {
|
|
2713
|
-
return await handleRouteError(
|
|
2714
|
-
() => handleColorsStatusRoute(),
|
|
2715
|
-
"Failed to fetch colors status"
|
|
2716
|
-
);
|
|
2717
|
-
}
|
|
2718
|
-
if (url.pathname === "/api/variables-status" && req.method === "GET") {
|
|
2719
|
-
return await handleRouteError(
|
|
2720
|
-
() => handleVariablesStatusRoute(),
|
|
2721
|
-
"Failed to fetch variables status"
|
|
2722
|
-
);
|
|
2723
|
-
}
|
|
2724
|
-
if (url.pathname === "/api/variables-css" && req.method === "GET") {
|
|
2725
|
-
return await handleRouteError(
|
|
2726
|
-
() => handleVariablesCSSRoute(),
|
|
2727
|
-
"Failed to generate variables CSS"
|
|
2728
|
-
);
|
|
2729
|
-
}
|
|
2730
|
-
if (url.pathname === "/api/fonts-css" && req.method === "GET") {
|
|
2731
|
-
return await handleRouteError(
|
|
2732
|
-
async () => {
|
|
2733
|
-
await loadProjectConfig();
|
|
2734
|
-
const css = generateFontCSS();
|
|
2735
|
-
return new Response(css, {
|
|
2736
|
-
headers: { "Content-Type": "text/css" }
|
|
2737
|
-
});
|
|
2738
|
-
},
|
|
2739
|
-
"Failed to generate fonts CSS"
|
|
2740
|
-
);
|
|
2741
|
-
}
|
|
2742
|
-
if (url.pathname === "/api/enums" && req.method === "GET") {
|
|
2743
|
-
return await handleRouteError(
|
|
2744
|
-
() => handleEnumsRoute(),
|
|
2745
|
-
"Failed to fetch enums"
|
|
2746
|
-
);
|
|
2747
|
-
}
|
|
2748
|
-
if (url.pathname === "/api/usage" && req.method === "GET") {
|
|
2749
|
-
return await handleRouteError(
|
|
2750
|
-
() => {
|
|
2751
|
-
const cmsCollections = cmsService ? cmsService.getAllSchemas().size : 0;
|
|
2752
|
-
const allPages = pageService.getAllPagePaths();
|
|
2753
|
-
const pages = allPages.filter((p) => !p.includes("/templates/")).length;
|
|
2754
|
-
const i18n = configService.getI18n();
|
|
2755
|
-
const locales = i18n.locales.length;
|
|
2756
|
-
return Promise.resolve(
|
|
2757
|
-
new Response(JSON.stringify({ cmsCollections, pages, locales }), {
|
|
2758
|
-
headers: { "Content-Type": "application/json" }
|
|
2759
|
-
})
|
|
2760
|
-
);
|
|
2761
|
-
},
|
|
2762
|
-
"Failed to fetch usage stats"
|
|
2763
|
-
);
|
|
2764
|
-
}
|
|
2765
|
-
if (url.pathname === API_ROUTES.COMPONENT_USAGE && req.method === "GET") {
|
|
2766
|
-
return await handleRouteError(
|
|
2767
|
-
() => Promise.resolve(handleComponentUsageRoute(pageService, componentService)),
|
|
2768
|
-
"Failed to fetch component usage"
|
|
2769
|
-
);
|
|
2770
|
-
}
|
|
2771
|
-
if (url.pathname === "/api/slug-mappings" && req.method === "GET") {
|
|
2772
|
-
return await handleRouteError(
|
|
2773
|
-
() => Promise.resolve(handleSlugMappingsRoute(pageService)),
|
|
2774
|
-
"Failed to fetch slug mappings"
|
|
2775
|
-
);
|
|
2776
|
-
}
|
|
2777
|
-
if (cmsService && url.pathname.startsWith("/api/cms/")) {
|
|
2778
|
-
if (url.pathname === "/api/cms/collections" && req.method === "GET") {
|
|
2779
|
-
return await handleRouteError(
|
|
2780
|
-
() => Promise.resolve(handleCollectionsRoute(cmsService)),
|
|
2781
|
-
"Failed to fetch CMS collections"
|
|
2782
|
-
);
|
|
2783
|
-
}
|
|
2784
|
-
const collectionSchemaMatch = url.pathname.match(/^\/api\/cms\/collections\/([^/]+)$/);
|
|
2785
|
-
if (collectionSchemaMatch && req.method === "GET") {
|
|
2786
|
-
const collection = collectionSchemaMatch[1];
|
|
2787
|
-
return await handleRouteError(
|
|
2788
|
-
() => Promise.resolve(handleCollectionSchemaRoute(collection, cmsService)),
|
|
2789
|
-
"Failed to fetch collection schema"
|
|
2790
|
-
);
|
|
2791
|
-
}
|
|
2792
|
-
const cmsPathMatch = url.pathname.match(/^\/api\/cms\/([^/]+)(?:\/([^/]+))?$/);
|
|
2793
|
-
if (cmsPathMatch) {
|
|
2794
|
-
const collection = cmsPathMatch[1];
|
|
2795
|
-
const slug = cmsPathMatch[2];
|
|
2796
|
-
if (collection === "collections") {
|
|
2797
|
-
return void 0;
|
|
2798
|
-
}
|
|
2799
|
-
if (!slug && req.method === "GET") {
|
|
2800
|
-
return await handleRouteError(
|
|
2801
|
-
() => handleItemsRoute(collection, cmsService, url.searchParams),
|
|
2802
|
-
"Failed to fetch CMS items"
|
|
2803
|
-
);
|
|
2804
|
-
}
|
|
2805
|
-
if (slug && req.method === "GET") {
|
|
2806
|
-
return await handleRouteError(
|
|
2807
|
-
() => handleItemRoute(collection, slug, cmsService),
|
|
2808
|
-
"Failed to fetch CMS item"
|
|
2809
|
-
);
|
|
2810
|
-
}
|
|
2811
|
-
}
|
|
2812
|
-
}
|
|
2813
|
-
return void 0;
|
|
2814
|
-
}
|
|
2815
|
-
|
|
2816
|
-
// lib/server/routes/index.ts
|
|
2817
|
-
init_constants();
|
|
2818
|
-
|
|
2819
|
-
// lib/server/routes/api/index.ts
|
|
2820
|
-
async function handleApiRoutes(req, url, pageService, componentService, cmsContext) {
|
|
2821
|
-
try {
|
|
2822
|
-
const coreContext = {
|
|
2823
|
-
pageService,
|
|
2824
|
-
componentService,
|
|
2825
|
-
cmsService: cmsContext?.cmsService
|
|
2826
|
-
};
|
|
2827
|
-
return await handleCoreApiRoutes(req, url, coreContext);
|
|
2828
|
-
} catch (error) {
|
|
2829
|
-
return handleRouteError(
|
|
2830
|
-
() => Promise.reject(error),
|
|
2831
|
-
"API route error"
|
|
2832
|
-
);
|
|
2833
|
-
}
|
|
2834
|
-
}
|
|
2835
|
-
|
|
2836
|
-
// lib/server/routes/pages.ts
|
|
2837
|
-
import { randomBytes } from "crypto";
|
|
2838
|
-
|
|
2839
|
-
// lib/shared/pathUtils.ts
|
|
2840
|
-
function getStaticFilePath(pagePath, distDir = "./dist") {
|
|
2841
|
-
if (pagePath === "/") {
|
|
2842
|
-
return `${distDir}/index.html`;
|
|
2843
|
-
}
|
|
2844
|
-
const pathParts = pagePath.substring(1).split("/");
|
|
2845
|
-
const fileName = pathParts.join("-") + ".html";
|
|
2846
|
-
return `${distDir}/${fileName}`;
|
|
2847
|
-
}
|
|
2848
|
-
|
|
2849
|
-
// lib/server/ssr/errorOverlay.ts
|
|
2850
|
-
function extractErrorInfo(error) {
|
|
2851
|
-
if (error instanceof Error) {
|
|
2852
|
-
return {
|
|
2853
|
-
message: error.message,
|
|
2854
|
-
stack: error.stack
|
|
2855
|
-
};
|
|
2856
|
-
}
|
|
2857
|
-
if (typeof error === "string") {
|
|
2858
|
-
return { message: error };
|
|
2859
|
-
}
|
|
2860
|
-
return { message: String(error) };
|
|
2861
|
-
}
|
|
2862
|
-
function escapeHtml(str) {
|
|
2863
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2864
|
-
}
|
|
2865
|
-
function safeJsonForScript(data) {
|
|
2866
|
-
return JSON.stringify(data).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
|
|
2867
|
-
}
|
|
2868
|
-
function generateErrorPage(error, context, cspNonce) {
|
|
2869
|
-
const errorInfo = extractErrorInfo(error);
|
|
2870
|
-
const friendly = toFriendlyError(errorInfo.message);
|
|
2871
|
-
const errorMessage = escapeHtml(errorInfo.message);
|
|
2872
|
-
const errorStack = errorInfo.stack ? escapeHtml(errorInfo.stack) : "";
|
|
2873
|
-
const renderInline = (s) => escapeHtml(s).replace(/`([^`]+)`/g, "<code>$1</code>");
|
|
2874
|
-
const friendlyTitle = escapeHtml(friendly.title);
|
|
2875
|
-
const friendlyMessage = renderInline(friendly.friendlyMessage);
|
|
2876
|
-
const friendlyHint = friendly.hint ? renderInline(friendly.hint) : "";
|
|
2877
|
-
const nonceAttr = cspNonce ? ` nonce="${cspNonce}"` : "";
|
|
2878
|
-
const errorDataJson = safeJsonForScript({
|
|
2879
|
-
type: "PREVIEW_ERROR",
|
|
2880
|
-
error: {
|
|
2881
|
-
message: friendly.friendlyMessage,
|
|
2882
|
-
title: friendly.title,
|
|
2883
|
-
hint: friendly.hint,
|
|
2884
|
-
raw: errorInfo.message,
|
|
2885
|
-
stack: errorInfo.stack,
|
|
2886
|
-
context
|
|
2887
|
-
}
|
|
2888
|
-
});
|
|
2889
|
-
const fullErrorText = safeJsonForScript(
|
|
2890
|
-
errorInfo.message + (errorInfo.stack ? "\n\n" + errorInfo.stack : "")
|
|
2891
|
-
);
|
|
2892
|
-
return `<!DOCTYPE html>
|
|
2893
|
-
<html lang="en">
|
|
2894
|
-
<head>
|
|
2895
|
-
<meta charset="UTF-8">
|
|
2896
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
2897
|
-
<title>Preview Error</title>
|
|
2898
|
-
<style${nonceAttr}>
|
|
2899
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
2900
|
-
body {
|
|
2901
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
2902
|
-
background: #1a1a1a;
|
|
2903
|
-
color: #fff;
|
|
2904
|
-
min-height: 100vh;
|
|
2905
|
-
display: flex;
|
|
2906
|
-
align-items: center;
|
|
2907
|
-
justify-content: center;
|
|
2908
|
-
padding: 20px;
|
|
2909
|
-
}
|
|
2910
|
-
.error-container {
|
|
2911
|
-
max-width: 800px;
|
|
2912
|
-
width: 100%;
|
|
2913
|
-
background: #2d2d2d;
|
|
2914
|
-
border-radius: 12px;
|
|
2915
|
-
overflow: hidden;
|
|
2916
|
-
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
2917
|
-
}
|
|
2918
|
-
.error-header {
|
|
2919
|
-
background: linear-gradient(135deg, #ff4444 0%, #cc0000 100%);
|
|
2920
|
-
padding: 20px 24px;
|
|
2921
|
-
display: flex;
|
|
2922
|
-
align-items: center;
|
|
2923
|
-
gap: 12px;
|
|
2924
|
-
}
|
|
2925
|
-
.error-icon { font-size: 24px; }
|
|
2926
|
-
.error-title { font-size: 18px; font-weight: 600; }
|
|
2927
|
-
.error-body { padding: 24px; }
|
|
2928
|
-
.error-context {
|
|
2929
|
-
font-size: 12px;
|
|
2930
|
-
color: #888;
|
|
2931
|
-
margin-bottom: 16px;
|
|
2932
|
-
text-transform: uppercase;
|
|
2933
|
-
letter-spacing: 0.5px;
|
|
2934
|
-
}
|
|
2935
|
-
.error-friendly {
|
|
2936
|
-
font-size: 15px;
|
|
2937
|
-
line-height: 1.6;
|
|
2938
|
-
color: #e8e8e8;
|
|
2939
|
-
margin-bottom: 16px;
|
|
2940
|
-
}
|
|
2941
|
-
.error-friendly code {
|
|
2942
|
-
font-family: 'SF Mono', Menlo, Monaco, 'Courier New', monospace;
|
|
2943
|
-
font-size: 13px;
|
|
2944
|
-
background: #1a1a1a;
|
|
2945
|
-
border: 1px solid #444;
|
|
2946
|
-
border-radius: 4px;
|
|
2947
|
-
padding: 1px 5px;
|
|
2948
|
-
color: #ffb86b;
|
|
2949
|
-
}
|
|
2950
|
-
.error-message {
|
|
2951
|
-
background: #1a1a1a;
|
|
2952
|
-
border: 1px solid #444;
|
|
2953
|
-
border-radius: 8px;
|
|
2954
|
-
padding: 16px;
|
|
2955
|
-
font-family: 'SF Mono', Menlo, Monaco, 'Courier New', monospace;
|
|
2956
|
-
font-size: 14px;
|
|
2957
|
-
line-height: 1.6;
|
|
2958
|
-
color: #ff6b6b;
|
|
2959
|
-
overflow-x: auto;
|
|
2960
|
-
white-space: pre-wrap;
|
|
2961
|
-
word-break: break-word;
|
|
2962
|
-
}
|
|
2963
|
-
.error-stack { margin-top: 16px; }
|
|
2964
|
-
.stack-toggle {
|
|
2965
|
-
background: none;
|
|
2966
|
-
border: 1px solid #444;
|
|
2967
|
-
color: #888;
|
|
2968
|
-
padding: 8px 16px;
|
|
2969
|
-
border-radius: 6px;
|
|
2970
|
-
cursor: pointer;
|
|
2971
|
-
font-size: 13px;
|
|
2972
|
-
display: flex;
|
|
2973
|
-
align-items: center;
|
|
2974
|
-
gap: 8px;
|
|
2975
|
-
transition: all 0.15s;
|
|
2976
|
-
}
|
|
2977
|
-
.stack-toggle:hover { background: #333; color: #fff; border-color: #555; }
|
|
2978
|
-
.stack-toggle svg { width: 14px; height: 14px; transition: transform 0.2s; }
|
|
2979
|
-
.stack-toggle.expanded svg { transform: rotate(90deg); }
|
|
2980
|
-
.stack-content {
|
|
2981
|
-
display: none;
|
|
2982
|
-
margin-top: 12px;
|
|
2983
|
-
background: #1a1a1a;
|
|
2984
|
-
border: 1px solid #444;
|
|
2985
|
-
border-radius: 8px;
|
|
2986
|
-
padding: 16px;
|
|
2987
|
-
font-family: 'SF Mono', Menlo, Monaco, 'Courier New', monospace;
|
|
2988
|
-
font-size: 12px;
|
|
2989
|
-
line-height: 1.8;
|
|
2990
|
-
color: #999;
|
|
2991
|
-
overflow-x: auto;
|
|
2992
|
-
white-space: pre;
|
|
2993
|
-
}
|
|
2994
|
-
.stack-content.visible { display: block; }
|
|
2995
|
-
.error-footer {
|
|
2996
|
-
padding: 16px 24px;
|
|
2997
|
-
background: #252525;
|
|
2998
|
-
border-top: 1px solid #333;
|
|
2999
|
-
display: flex;
|
|
3000
|
-
justify-content: space-between;
|
|
3001
|
-
align-items: center;
|
|
3002
|
-
flex-wrap: wrap;
|
|
3003
|
-
gap: 12px;
|
|
3004
|
-
}
|
|
3005
|
-
.error-hint { font-size: 13px; color: #888; }
|
|
3006
|
-
.copy-btn {
|
|
3007
|
-
background: #333;
|
|
3008
|
-
border: 1px solid #444;
|
|
3009
|
-
color: #fff;
|
|
3010
|
-
padding: 8px 16px;
|
|
3011
|
-
border-radius: 6px;
|
|
3012
|
-
cursor: pointer;
|
|
3013
|
-
font-size: 13px;
|
|
3014
|
-
display: flex;
|
|
3015
|
-
align-items: center;
|
|
3016
|
-
gap: 6px;
|
|
3017
|
-
transition: all 0.15s;
|
|
3018
|
-
}
|
|
3019
|
-
.copy-btn:hover { background: #444; border-color: #555; }
|
|
3020
|
-
.copy-btn svg { width: 14px; height: 14px; }
|
|
3021
|
-
</style>
|
|
3022
|
-
</head>
|
|
3023
|
-
<body>
|
|
3024
|
-
<div class="error-container">
|
|
3025
|
-
<div class="error-header">
|
|
3026
|
-
<span class="error-icon">\u26A0\uFE0F</span>
|
|
3027
|
-
<span class="error-title">${friendlyTitle}</span>
|
|
3028
|
-
</div>
|
|
3029
|
-
<div class="error-body">
|
|
3030
|
-
${context ? `<div class="error-context">${escapeHtml(context)}</div>` : ""}
|
|
3031
|
-
<p class="error-friendly">${friendlyMessage}</p>
|
|
3032
|
-
<div class="error-stack">
|
|
3033
|
-
<button class="stack-toggle" id="stackToggle">
|
|
3034
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
3035
|
-
<polyline points="9 18 15 12 9 6"></polyline>
|
|
3036
|
-
</svg>
|
|
3037
|
-
<span>Show technical details</span>
|
|
3038
|
-
</button>
|
|
3039
|
-
<div class="stack-content" id="stackContent"><div class="error-message">${errorMessage}</div>${errorStack ? `
|
|
3040
|
-
${errorStack}` : ""}</div>
|
|
3041
|
-
</div>
|
|
3042
|
-
</div>
|
|
3043
|
-
<div class="error-footer">
|
|
3044
|
-
<span class="error-hint">${friendlyHint}</span>
|
|
3045
|
-
<button class="copy-btn" id="copyBtn">
|
|
3046
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
3047
|
-
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
|
3048
|
-
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
|
3049
|
-
</svg>
|
|
3050
|
-
<span>Copy Error</span>
|
|
3051
|
-
</button>
|
|
3052
|
-
</div>
|
|
3053
|
-
</div>
|
|
3054
|
-
<script${nonceAttr}>
|
|
3055
|
-
(function() {
|
|
3056
|
-
// Send error to parent editor
|
|
3057
|
-
if (window.parent && window.parent !== window) {
|
|
3058
|
-
try {
|
|
3059
|
-
window.parent.postMessage(${errorDataJson}, '*');
|
|
3060
|
-
} catch (e) {
|
|
3061
|
-
console.error('Failed to send error to parent:', e);
|
|
3062
|
-
}
|
|
3063
|
-
}
|
|
3064
|
-
|
|
3065
|
-
// Stack trace toggle
|
|
3066
|
-
var stackToggle = document.getElementById('stackToggle');
|
|
3067
|
-
var stackContent = document.getElementById('stackContent');
|
|
3068
|
-
if (stackToggle && stackContent) {
|
|
3069
|
-
stackToggle.addEventListener('click', function() {
|
|
3070
|
-
var isVisible = stackContent.classList.toggle('visible');
|
|
3071
|
-
stackToggle.classList.toggle('expanded', isVisible);
|
|
3072
|
-
stackToggle.querySelector('span').textContent = isVisible ? 'Hide technical details' : 'Show technical details';
|
|
3073
|
-
});
|
|
3074
|
-
}
|
|
3075
|
-
|
|
3076
|
-
// Copy button
|
|
3077
|
-
var copyBtn = document.getElementById('copyBtn');
|
|
3078
|
-
if (copyBtn) {
|
|
3079
|
-
copyBtn.addEventListener('click', function() {
|
|
3080
|
-
var errorText = ${fullErrorText};
|
|
3081
|
-
navigator.clipboard.writeText(errorText).then(function() {
|
|
3082
|
-
var span = copyBtn.querySelector('span');
|
|
3083
|
-
var original = span.textContent;
|
|
3084
|
-
span.textContent = 'Copied!';
|
|
3085
|
-
setTimeout(function() { span.textContent = original; }, 2000);
|
|
3086
|
-
}).catch(function(err) {
|
|
3087
|
-
console.error('Failed to copy:', err);
|
|
3088
|
-
});
|
|
3089
|
-
});
|
|
3090
|
-
}
|
|
3091
|
-
})();
|
|
3092
|
-
</script>
|
|
3093
|
-
</body>
|
|
3094
|
-
</html>`;
|
|
3095
|
-
}
|
|
3096
|
-
|
|
3097
|
-
// lib/server/scriptCache.ts
|
|
3098
|
-
var scriptCache = /* @__PURE__ */ new Map();
|
|
3099
|
-
function cacheScript(hash, content) {
|
|
3100
|
-
scriptCache.set(hash, content);
|
|
3101
|
-
}
|
|
3102
|
-
function getScript(hash) {
|
|
3103
|
-
return scriptCache.get(hash);
|
|
3104
|
-
}
|
|
3105
|
-
function hashContent2(content) {
|
|
3106
|
-
return hashContent(content);
|
|
3107
|
-
}
|
|
3108
|
-
|
|
3109
|
-
// lib/server/routes/pages.ts
|
|
3110
|
-
var EDITOR_HEADER = "x-meno-editor";
|
|
3111
|
-
var CSP_NONCE_HEADER = "x-meno-csp-nonce";
|
|
3112
|
-
function generateCspNonce() {
|
|
3113
|
-
return randomBytes(16).toString("base64");
|
|
3114
|
-
}
|
|
3115
|
-
async function handlePageRoute(url, context, req) {
|
|
3116
|
-
const { pageService, componentService, cmsService, injectLiveReload, isEditor, serverPort } = context;
|
|
3117
|
-
const pagePath = url.pathname;
|
|
3118
|
-
const injectEditorAttrs = injectLiveReload === true || req?.headers.get(EDITOR_HEADER) === "1";
|
|
3119
|
-
const cspNonce = generateCspNonce();
|
|
3120
|
-
const i18nConfig = await loadI18nConfig();
|
|
3121
|
-
const { locale, pathWithoutLocale } = parseLocaleFromPath(pagePath, i18nConfig);
|
|
3122
|
-
const slugMappings = pageService.getSlugMappings();
|
|
3123
|
-
const slugIndex = buildSlugIndex(slugMappings);
|
|
3124
|
-
if (cmsService) {
|
|
3125
|
-
const cmsMatch = await cmsService.matchRoute(pathWithoutLocale, locale);
|
|
3126
|
-
if (cmsMatch) {
|
|
3127
|
-
if (injectEditorAttrs && url.searchParams.get("previewDraft") === "1" && cmsMatch.item._filename) {
|
|
3128
|
-
const draft = await cmsService.getDraft(cmsMatch.collection, cmsMatch.item._filename);
|
|
3129
|
-
if (draft) {
|
|
3130
|
-
cmsMatch.item = draft;
|
|
3131
|
-
}
|
|
3132
|
-
}
|
|
3133
|
-
const templatePageContent = await loadPageByFilePath(cmsMatch.pagePath);
|
|
3134
|
-
if (templatePageContent) {
|
|
3135
|
-
const templatesDir = projectPaths.templates();
|
|
3136
|
-
const cmsTemplatePath = "/templates" + cmsMatch.pagePath.replace(templatesDir, "").replace(/\\/g, "/").replace(/\.json$/, "");
|
|
3137
|
-
try {
|
|
3138
|
-
const pageData = parseJSON(templatePageContent);
|
|
3139
|
-
const globalComponentsRecord = componentService.getAllComponents();
|
|
3140
|
-
const baseUrl = `${url.protocol}//${url.host}`;
|
|
3141
|
-
const typedPageData = pageData;
|
|
3142
|
-
if (injectLiveReload) {
|
|
3143
|
-
const result = await generateSSRHTML({
|
|
3144
|
-
pageData: typedPageData,
|
|
3145
|
-
globalComponents: globalComponentsRecord,
|
|
3146
|
-
pagePath,
|
|
3147
|
-
baseUrl,
|
|
3148
|
-
useBuiltBundle: true,
|
|
3149
|
-
locale,
|
|
3150
|
-
slugMappings,
|
|
3151
|
-
cmsContext: { cms: cmsMatch.item },
|
|
3152
|
-
cmsService,
|
|
3153
|
-
cmsTemplatePath,
|
|
3154
|
-
pageLibraries: typedPageData.meta?.libraries,
|
|
3155
|
-
pageCustomCode: typedPageData.meta?.customCode,
|
|
3156
|
-
injectLiveReload,
|
|
3157
|
-
injectEditorAttrs,
|
|
3158
|
-
isEditor,
|
|
3159
|
-
serverPort,
|
|
3160
|
-
returnSeparateJS: true,
|
|
3161
|
-
cspNonce
|
|
3162
|
-
});
|
|
3163
|
-
let finalHtml = result.html;
|
|
3164
|
-
if (result.javascript) {
|
|
3165
|
-
const hash = hashContent2(result.javascript);
|
|
3166
|
-
cacheScript(hash, result.javascript);
|
|
3167
|
-
finalHtml = finalHtml.replace("</body>", ` <script src="/_scripts/${hash}.js" defer></script>
|
|
3168
|
-
</body>`);
|
|
3169
|
-
}
|
|
3170
|
-
return new Response(finalHtml, {
|
|
3171
|
-
headers: {
|
|
3172
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
3173
|
-
"Cache-Control": "no-store, max-age=0",
|
|
3174
|
-
"Pragma": "no-cache",
|
|
3175
|
-
"Expires": "0",
|
|
3176
|
-
[CSP_NONCE_HEADER]: cspNonce
|
|
3177
|
-
}
|
|
3178
|
-
});
|
|
3179
|
-
}
|
|
3180
|
-
const ssrHTML = await generateSSRHTML({
|
|
3181
|
-
pageData: typedPageData,
|
|
3182
|
-
globalComponents: globalComponentsRecord,
|
|
3183
|
-
pagePath,
|
|
3184
|
-
baseUrl,
|
|
3185
|
-
useBuiltBundle: false,
|
|
3186
|
-
locale,
|
|
3187
|
-
slugMappings,
|
|
3188
|
-
cmsContext: { cms: cmsMatch.item },
|
|
3189
|
-
cmsService,
|
|
3190
|
-
cmsTemplatePath,
|
|
3191
|
-
pageLibraries: typedPageData.meta?.libraries,
|
|
3192
|
-
pageCustomCode: typedPageData.meta?.customCode,
|
|
3193
|
-
injectEditorAttrs,
|
|
3194
|
-
isEditor,
|
|
3195
|
-
cspNonce
|
|
3196
|
-
});
|
|
3197
|
-
return new Response(ssrHTML, {
|
|
3198
|
-
headers: {
|
|
3199
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
3200
|
-
"Cache-Control": "no-store, max-age=0",
|
|
3201
|
-
"Pragma": "no-cache",
|
|
3202
|
-
"Expires": "0",
|
|
3203
|
-
[CSP_NONCE_HEADER]: cspNonce
|
|
3204
|
-
}
|
|
3205
|
-
});
|
|
3206
|
-
} catch (error) {
|
|
3207
|
-
console.error("Error rendering CMS page:", error);
|
|
3208
|
-
return new Response(generateErrorPage(error, `Error rendering template: ${cmsTemplatePath}`, cspNonce), {
|
|
3209
|
-
headers: {
|
|
3210
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
3211
|
-
"Cache-Control": "no-store",
|
|
3212
|
-
[CSP_NONCE_HEADER]: cspNonce
|
|
3213
|
-
}
|
|
3214
|
-
});
|
|
3215
|
-
}
|
|
3216
|
-
}
|
|
3217
|
-
}
|
|
3218
|
-
}
|
|
3219
|
-
const slug = pathWithoutLocale === "/" ? "" : pathWithoutLocale.substring(1);
|
|
3220
|
-
const pageId = resolveSlugToPageId(slug, locale, slugIndex);
|
|
3221
|
-
let lookupPath;
|
|
3222
|
-
if (pageId) {
|
|
3223
|
-
lookupPath = pageId === "index" ? "/" : `/${pageId}`;
|
|
3224
|
-
} else {
|
|
3225
|
-
lookupPath = pathWithoutLocale;
|
|
3226
|
-
}
|
|
3227
|
-
const isDevelopment = true;
|
|
3228
|
-
if (!isDevelopment) {
|
|
3229
|
-
const staticFilePath = getStaticFilePath(pagePath);
|
|
3230
|
-
if (await fileExists(staticFilePath)) {
|
|
3231
|
-
return serveFile(staticFilePath, {
|
|
3232
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
3233
|
-
"Cache-Control": "public, max-age=31536000"
|
|
3234
|
-
// 1 year cache for static files
|
|
3235
|
-
});
|
|
3236
|
-
}
|
|
3237
|
-
}
|
|
3238
|
-
const pageContent = context.draftPageStore?.get(lookupPath) ?? pageService.getPage(lookupPath);
|
|
3239
|
-
if (pageContent) {
|
|
3240
|
-
try {
|
|
3241
|
-
const pageData = parseJSON(pageContent);
|
|
3242
|
-
const globalComponentsRecord = componentService.getAllComponents();
|
|
3243
|
-
const baseUrl = `${url.protocol}//${url.host}`;
|
|
3244
|
-
if (injectLiveReload) {
|
|
3245
|
-
const result = await generateSSRHTML({
|
|
3246
|
-
pageData,
|
|
3247
|
-
globalComponents: globalComponentsRecord,
|
|
3248
|
-
pagePath,
|
|
3249
|
-
baseUrl,
|
|
3250
|
-
useBuiltBundle: true,
|
|
3251
|
-
locale,
|
|
3252
|
-
slugMappings,
|
|
3253
|
-
cmsService,
|
|
3254
|
-
pageLibraries: pageData.meta?.libraries,
|
|
3255
|
-
pageCustomCode: pageData.meta?.customCode,
|
|
3256
|
-
injectLiveReload,
|
|
3257
|
-
injectEditorAttrs,
|
|
3258
|
-
isEditor,
|
|
3259
|
-
serverPort,
|
|
3260
|
-
returnSeparateJS: true,
|
|
3261
|
-
cspNonce
|
|
3262
|
-
});
|
|
3263
|
-
let finalHtml = result.html;
|
|
3264
|
-
if (result.javascript) {
|
|
3265
|
-
const hash = hashContent2(result.javascript);
|
|
3266
|
-
cacheScript(hash, result.javascript);
|
|
3267
|
-
finalHtml = finalHtml.replace("</body>", ` <script src="/_scripts/${hash}.js" defer></script>
|
|
3268
|
-
</body>`);
|
|
3269
|
-
}
|
|
3270
|
-
return new Response(finalHtml, {
|
|
3271
|
-
headers: {
|
|
3272
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
3273
|
-
"Cache-Control": "no-store, max-age=0",
|
|
3274
|
-
"Pragma": "no-cache",
|
|
3275
|
-
"Expires": "0",
|
|
3276
|
-
[CSP_NONCE_HEADER]: cspNonce
|
|
3277
|
-
}
|
|
3278
|
-
});
|
|
3279
|
-
}
|
|
3280
|
-
const ssrHTML = await generateSSRHTML({
|
|
3281
|
-
pageData,
|
|
3282
|
-
globalComponents: globalComponentsRecord,
|
|
3283
|
-
pagePath,
|
|
3284
|
-
baseUrl,
|
|
3285
|
-
useBuiltBundle: false,
|
|
3286
|
-
locale,
|
|
3287
|
-
slugMappings,
|
|
3288
|
-
cmsService,
|
|
3289
|
-
pageLibraries: pageData.meta?.libraries,
|
|
3290
|
-
pageCustomCode: pageData.meta?.customCode,
|
|
3291
|
-
injectEditorAttrs,
|
|
3292
|
-
isEditor,
|
|
3293
|
-
cspNonce
|
|
3294
|
-
});
|
|
3295
|
-
return new Response(ssrHTML, {
|
|
3296
|
-
headers: {
|
|
3297
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
3298
|
-
"Cache-Control": "no-store, max-age=0",
|
|
3299
|
-
"Pragma": "no-cache",
|
|
3300
|
-
"Expires": "0",
|
|
3301
|
-
[CSP_NONCE_HEADER]: cspNonce
|
|
3302
|
-
}
|
|
3303
|
-
});
|
|
3304
|
-
} catch (error) {
|
|
3305
|
-
console.error("Error rendering page:", error);
|
|
3306
|
-
return new Response(generateErrorPage(error, `Error rendering page: ${lookupPath}`, cspNonce), {
|
|
3307
|
-
headers: {
|
|
3308
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
3309
|
-
"Cache-Control": "no-store",
|
|
3310
|
-
[CSP_NONCE_HEADER]: cspNonce
|
|
3311
|
-
}
|
|
3312
|
-
});
|
|
3313
|
-
}
|
|
3314
|
-
} else {
|
|
3315
|
-
return serveFile(packagePaths.indexHtml());
|
|
3316
|
-
}
|
|
3317
|
-
}
|
|
3318
|
-
async function loadPageByFilePath(filePath) {
|
|
3319
|
-
try {
|
|
3320
|
-
if (await fileExists(filePath)) {
|
|
3321
|
-
return await readTextFile(filePath);
|
|
3322
|
-
}
|
|
3323
|
-
return null;
|
|
3324
|
-
} catch {
|
|
3325
|
-
return null;
|
|
3326
|
-
}
|
|
3327
|
-
}
|
|
3328
|
-
|
|
3329
|
-
// lib/server/routes/static.ts
|
|
3330
|
-
async function handleStaticRoute(url, req) {
|
|
3331
|
-
if (url.pathname === "/client-router.js" || url.pathname === "/client-router.tsx") {
|
|
3332
|
-
const preBundledPath = packagePaths.clientRouter().replace(/\.tsx$/, ".js");
|
|
3333
|
-
if (preBundledPath !== packagePaths.clientRouter() && await fileExists(preBundledPath)) {
|
|
3334
|
-
return serveFile(preBundledPath, {
|
|
3335
|
-
"Content-Type": "application/javascript",
|
|
3336
|
-
"Cache-Control": "no-cache, no-store, must-revalidate"
|
|
3337
|
-
});
|
|
3338
|
-
}
|
|
3339
|
-
const result = await bundleFile(packagePaths.clientRouter(), {
|
|
3340
|
-
target: "browser",
|
|
3341
|
-
format: "esm",
|
|
3342
|
-
minify: false,
|
|
3343
|
-
sourcemap: "inline"
|
|
3344
|
-
});
|
|
3345
|
-
if (result.success) {
|
|
3346
|
-
return new Response(result.code, {
|
|
3347
|
-
headers: {
|
|
3348
|
-
"Content-Type": "application/javascript",
|
|
3349
|
-
"Cache-Control": "no-cache, no-store, must-revalidate"
|
|
3350
|
-
}
|
|
3351
|
-
});
|
|
3352
|
-
}
|
|
3353
|
-
}
|
|
3354
|
-
if (url.pathname.endsWith(".tsx") || url.pathname.endsWith(".ts")) {
|
|
3355
|
-
const entrypoint = resolvePackagePath(url.pathname.slice(1));
|
|
3356
|
-
if (!isPathWithinRoot(entrypoint, getPackageRoot())) {
|
|
3357
|
-
return new Response("Forbidden", { status: 403 });
|
|
3358
|
-
}
|
|
3359
|
-
const result = await bundleFile(entrypoint, {
|
|
3360
|
-
target: "browser",
|
|
3361
|
-
format: "esm",
|
|
3362
|
-
minify: false,
|
|
3363
|
-
sourcemap: "inline"
|
|
3364
|
-
});
|
|
3365
|
-
if (result.success) {
|
|
3366
|
-
return new Response(result.code, {
|
|
3367
|
-
headers: {
|
|
3368
|
-
"Content-Type": "application/javascript",
|
|
3369
|
-
"Cache-Control": "no-cache, no-store, must-revalidate"
|
|
3370
|
-
}
|
|
3371
|
-
});
|
|
3372
|
-
}
|
|
3373
|
-
}
|
|
3374
|
-
const decodedPathname = decodeURIComponent(url.pathname);
|
|
3375
|
-
let filePath;
|
|
3376
|
-
let rootPath;
|
|
3377
|
-
if (decodedPathname.startsWith("/fonts/") || decodedPathname.startsWith("/images/") || decodedPathname.startsWith("/icons/") || decodedPathname.startsWith("/assets/") || decodedPathname.startsWith("/libraries/") || decodedPathname.startsWith("/videos/")) {
|
|
3378
|
-
rootPath = getProjectRoot();
|
|
3379
|
-
filePath = resolveProjectPath(decodedPathname.slice(1));
|
|
3380
|
-
} else {
|
|
3381
|
-
rootPath = getPackageRoot();
|
|
3382
|
-
filePath = resolvePackagePath(decodedPathname.slice(1));
|
|
3383
|
-
}
|
|
3384
|
-
if (!isPathWithinRoot(filePath, rootPath)) {
|
|
3385
|
-
return new Response("Forbidden", { status: 403 });
|
|
3386
|
-
}
|
|
3387
|
-
if (await fileExists(filePath)) {
|
|
3388
|
-
const cacheControl = decodedPathname.startsWith("/libraries/") ? "no-cache, no-store, must-revalidate" : "public, max-age=31536000, immutable";
|
|
3389
|
-
return serveFile(filePath, { "Cache-Control": cacheControl });
|
|
3390
|
-
}
|
|
3391
|
-
return void 0;
|
|
3392
|
-
}
|
|
3393
|
-
|
|
3394
|
-
// lib/server/routes/index.ts
|
|
3395
|
-
async function handleRoutes(req, url, server, context) {
|
|
3396
|
-
const startTime = Date.now();
|
|
3397
|
-
const { pageService, componentService, cmsService, cmsProvider } = context;
|
|
3398
|
-
logRequest(req);
|
|
3399
|
-
const corsResponse = handleCorsPreflight(req);
|
|
3400
|
-
if (corsResponse) {
|
|
3401
|
-
logResponseTime(startTime, req);
|
|
3402
|
-
return corsResponse;
|
|
3403
|
-
}
|
|
3404
|
-
try {
|
|
3405
|
-
if (url.pathname === HMR_ROUTE) {
|
|
3406
|
-
logResponseTime(startTime, req);
|
|
3407
|
-
return void 0;
|
|
3408
|
-
}
|
|
3409
|
-
if (url.pathname === "/__draft-page" && req.method === "POST") {
|
|
3410
|
-
const corsHeaders = { "Access-Control-Allow-Origin": "*" };
|
|
3411
|
-
const store = context.draftPageStore;
|
|
3412
|
-
const ws = context.wsManager;
|
|
3413
|
-
if (!store || !ws || ws.getClientCount() === 0) {
|
|
3414
|
-
logResponseTime(startTime, req);
|
|
3415
|
-
return new Response(null, { status: 204, headers: corsHeaders });
|
|
3416
|
-
}
|
|
3417
|
-
try {
|
|
3418
|
-
const body = await req.json();
|
|
3419
|
-
if (typeof body.path === "string" && typeof body.content === "string" && body.path.startsWith("/")) {
|
|
3420
|
-
store.set(body.path, body.content);
|
|
3421
|
-
ws.broadcastUpdate(body.path);
|
|
3422
|
-
}
|
|
3423
|
-
} catch {
|
|
3424
|
-
}
|
|
3425
|
-
logResponseTime(startTime, req);
|
|
3426
|
-
return new Response(null, { status: 204, headers: corsHeaders });
|
|
3427
|
-
}
|
|
3428
|
-
if (url.pathname === "/__draft-page/clear" && req.method === "POST") {
|
|
3429
|
-
const corsHeaders = { "Access-Control-Allow-Origin": "*" };
|
|
3430
|
-
const store = context.draftPageStore;
|
|
3431
|
-
if (store) {
|
|
3432
|
-
try {
|
|
3433
|
-
const body = await req.json();
|
|
3434
|
-
if (typeof body.path === "string") {
|
|
3435
|
-
store.clear(body.path);
|
|
3436
|
-
context.wsManager?.broadcastUpdate(body.path);
|
|
3437
|
-
}
|
|
3438
|
-
} catch {
|
|
3439
|
-
}
|
|
3440
|
-
}
|
|
3441
|
-
logResponseTime(startTime, req);
|
|
3442
|
-
return new Response(null, { status: 204, headers: corsHeaders });
|
|
3443
|
-
}
|
|
3444
|
-
if (url.pathname === "/__draft-page/clients" && req.method === "GET") {
|
|
3445
|
-
const count = context.wsManager?.getClientCount() ?? 0;
|
|
3446
|
-
logResponseTime(startTime, req);
|
|
3447
|
-
return new Response(JSON.stringify({ count }), {
|
|
3448
|
-
status: 200,
|
|
3449
|
-
headers: {
|
|
3450
|
-
"Content-Type": "application/json",
|
|
3451
|
-
"Cache-Control": "no-store",
|
|
3452
|
-
"Access-Control-Allow-Origin": "*"
|
|
3453
|
-
}
|
|
3454
|
-
});
|
|
3455
|
-
}
|
|
3456
|
-
const cmsContext = cmsService && cmsProvider ? { cmsService, cmsProvider } : void 0;
|
|
3457
|
-
const apiResponse = await handleApiRoutes(
|
|
3458
|
-
req,
|
|
3459
|
-
url,
|
|
3460
|
-
pageService,
|
|
3461
|
-
componentService,
|
|
3462
|
-
cmsContext
|
|
3463
|
-
);
|
|
3464
|
-
if (apiResponse) {
|
|
3465
|
-
logResponseTime(startTime, req);
|
|
3466
|
-
return apiResponse;
|
|
3467
|
-
}
|
|
3468
|
-
if (url.pathname.startsWith("/data/") && url.pathname.endsWith("/index.json") && cmsService) {
|
|
3469
|
-
const match = url.pathname.match(/^\/data\/([^/]+)\/index\.json$/);
|
|
3470
|
-
if (match) {
|
|
3471
|
-
const collectionId = match[1];
|
|
3472
|
-
try {
|
|
3473
|
-
const i18nConfig = await loadI18nConfig();
|
|
3474
|
-
let refererPath = "/";
|
|
3475
|
-
try {
|
|
3476
|
-
const referer = req.headers.get("referer");
|
|
3477
|
-
if (referer) refererPath = new URL(referer).pathname;
|
|
3478
|
-
} catch {
|
|
3479
|
-
}
|
|
3480
|
-
const { locale } = parseLocaleFromPath(refererPath, i18nConfig);
|
|
3481
|
-
const items = await cmsService.queryItems({ collection: collectionId, excludeDraftLocale: locale });
|
|
3482
|
-
logResponseTime(startTime, req);
|
|
3483
|
-
return new Response(JSON.stringify(items), {
|
|
3484
|
-
headers: {
|
|
3485
|
-
"Content-Type": "application/json",
|
|
3486
|
-
"Cache-Control": "no-cache"
|
|
3487
|
-
}
|
|
3488
|
-
});
|
|
3489
|
-
} catch {
|
|
3490
|
-
logResponseTime(startTime, req);
|
|
3491
|
-
return new Response("[]", {
|
|
3492
|
-
headers: { "Content-Type": "application/json" }
|
|
3493
|
-
});
|
|
3494
|
-
}
|
|
3495
|
-
}
|
|
3496
|
-
}
|
|
3497
|
-
if (url.pathname.startsWith("/_scripts/") && url.pathname.endsWith(".js")) {
|
|
3498
|
-
const hash = url.pathname.slice(10, -3);
|
|
3499
|
-
const script = getScript(hash);
|
|
3500
|
-
if (script) {
|
|
3501
|
-
logResponseTime(startTime, req);
|
|
3502
|
-
return new Response(script, {
|
|
3503
|
-
headers: {
|
|
3504
|
-
"Content-Type": "application/javascript",
|
|
3505
|
-
"Cache-Control": "no-cache"
|
|
3506
|
-
}
|
|
3507
|
-
});
|
|
3508
|
-
}
|
|
3509
|
-
}
|
|
3510
|
-
if (url.pathname.startsWith("/images/") || url.pathname.startsWith("/videos/") || url.pathname.startsWith("/assets/") || url.pathname.startsWith("/public/") || url.pathname.startsWith("/icons/")) {
|
|
3511
|
-
const response2 = await handleStaticRoute(url, req);
|
|
3512
|
-
if (response2) {
|
|
3513
|
-
logResponseTime(startTime, req);
|
|
3514
|
-
return response2;
|
|
3515
|
-
}
|
|
3516
|
-
}
|
|
3517
|
-
if (url.pathname.startsWith("/api/")) {
|
|
3518
|
-
logResponseTime(startTime, req);
|
|
3519
|
-
return new Response(
|
|
3520
|
-
JSON.stringify({ error: "Not Found", path: url.pathname }),
|
|
3521
|
-
{ status: 404, headers: { "Content-Type": "application/json" } }
|
|
3522
|
-
);
|
|
3523
|
-
}
|
|
3524
|
-
if (url.pathname === "/" || url.pathname.startsWith("/") && !url.pathname.includes(".")) {
|
|
3525
|
-
const response2 = await handlePageRoute(url, context, req);
|
|
3526
|
-
logResponseTime(startTime, req);
|
|
3527
|
-
return response2;
|
|
3528
|
-
}
|
|
3529
|
-
const response = await handleStaticRoute(url, req);
|
|
3530
|
-
logResponseTime(startTime, req);
|
|
3531
|
-
return response;
|
|
3532
|
-
} catch (error) {
|
|
3533
|
-
logResponseTime(startTime, req);
|
|
3534
|
-
return handleRouteError(
|
|
3535
|
-
() => Promise.reject(error),
|
|
3536
|
-
`Error handling route: ${url.pathname}`
|
|
3537
|
-
);
|
|
3538
|
-
}
|
|
3539
|
-
}
|
|
3540
|
-
|
|
3541
|
-
// lib/server/createServer.ts
|
|
3542
|
-
init_constants();
|
|
3543
|
-
var COLLAB_ROUTE = "/__collab";
|
|
3544
|
-
async function createServer(config) {
|
|
3545
|
-
const {
|
|
3546
|
-
port: requestedPort = SERVER_PORT,
|
|
3547
|
-
pageService,
|
|
3548
|
-
componentService,
|
|
3549
|
-
wsManager,
|
|
3550
|
-
cmsService,
|
|
3551
|
-
cmsProvider,
|
|
3552
|
-
draftPageStore,
|
|
3553
|
-
additionalRoutes = [],
|
|
3554
|
-
onWSMessage,
|
|
3555
|
-
injectLiveReload,
|
|
3556
|
-
isEditor,
|
|
3557
|
-
onCollabWSMessage,
|
|
3558
|
-
onCollabWSOpen,
|
|
3559
|
-
onCollabWSClose
|
|
3560
|
-
} = config;
|
|
3561
|
-
const collabClients = /* @__PURE__ */ new WeakSet();
|
|
3562
|
-
const routeContext = {
|
|
3563
|
-
pageService,
|
|
3564
|
-
componentService,
|
|
3565
|
-
cmsService,
|
|
3566
|
-
cmsProvider,
|
|
3567
|
-
injectLiveReload,
|
|
3568
|
-
isEditor,
|
|
3569
|
-
wsManager,
|
|
3570
|
-
draftPageStore
|
|
3571
|
-
};
|
|
3572
|
-
let boundPort;
|
|
3573
|
-
let lastError;
|
|
3574
|
-
for (let portAttempt = 0; portAttempt < MAX_PORT_ATTEMPTS; portAttempt++) {
|
|
3575
|
-
const currentPort = requestedPort + portAttempt;
|
|
3576
|
-
try {
|
|
3577
|
-
const server = await createRuntimeServer({
|
|
3578
|
-
port: currentPort,
|
|
3579
|
-
hostname: "localhost",
|
|
3580
|
-
wsPath: HMR_ROUTE,
|
|
3581
|
-
async fetch(req, upgradeWebSocket) {
|
|
3582
|
-
const url = new URL(req.url);
|
|
3583
|
-
for (const handler of additionalRoutes) {
|
|
3584
|
-
const response2 = await handler(req, url, routeContext);
|
|
3585
|
-
if (response2) {
|
|
3586
|
-
return response2;
|
|
3587
|
-
}
|
|
3588
|
-
}
|
|
3589
|
-
if (url.pathname === HMR_ROUTE) {
|
|
3590
|
-
const success = upgradeWebSocket(req);
|
|
3591
|
-
return success ? void 0 : new Response("WebSocket upgrade failed", { status: 500 });
|
|
3592
|
-
}
|
|
3593
|
-
if (url.pathname === COLLAB_ROUTE && onCollabWSMessage) {
|
|
3594
|
-
const success = upgradeWebSocket(req);
|
|
3595
|
-
return success ? void 0 : new Response("WebSocket upgrade failed", { status: 500 });
|
|
3596
|
-
}
|
|
3597
|
-
const response = await handleRoutes(req, url, null, routeContext);
|
|
3598
|
-
return response || new Response("Not Found", { status: 404 });
|
|
3599
|
-
},
|
|
3600
|
-
websocket: {
|
|
3601
|
-
open(ws) {
|
|
3602
|
-
wsManager.addClient(ws);
|
|
3603
|
-
onCollabWSOpen?.(ws);
|
|
3604
|
-
},
|
|
3605
|
-
message(ws, message) {
|
|
3606
|
-
try {
|
|
3607
|
-
const data = JSON.parse(message);
|
|
3608
|
-
if (data.type === "ping") {
|
|
3609
|
-
ws.send("pong");
|
|
3610
|
-
return;
|
|
3611
|
-
}
|
|
3612
|
-
if (onCollabWSMessage && typeof data.type === "string" && data.type.startsWith("collab:")) {
|
|
3613
|
-
collabClients.add(ws);
|
|
3614
|
-
onCollabWSMessage(ws, data);
|
|
3615
|
-
return;
|
|
3616
|
-
}
|
|
3617
|
-
if (onWSMessage) {
|
|
3618
|
-
onWSMessage(ws, data);
|
|
3619
|
-
}
|
|
3620
|
-
} catch (error) {
|
|
3621
|
-
}
|
|
3622
|
-
},
|
|
3623
|
-
close(ws) {
|
|
3624
|
-
wsManager.removeClient(ws);
|
|
3625
|
-
if (collabClients.has(ws)) {
|
|
3626
|
-
onCollabWSClose?.(ws);
|
|
3627
|
-
collabClients.delete(ws);
|
|
3628
|
-
}
|
|
3629
|
-
}
|
|
3630
|
-
}
|
|
3631
|
-
});
|
|
3632
|
-
if (portAttempt > 0) {
|
|
3633
|
-
console.log(`Warning: Port ${requestedPort} was busy, using port ${server.port} instead`);
|
|
3634
|
-
}
|
|
3635
|
-
routeContext.serverPort = server.port;
|
|
3636
|
-
return { server, port: server.port };
|
|
3637
|
-
} catch (error) {
|
|
3638
|
-
lastError = error;
|
|
3639
|
-
const err = error;
|
|
3640
|
-
if (err?.code === "EADDRINUSE") {
|
|
3641
|
-
if (portAttempt >= MAX_PORT_ATTEMPTS - 1) {
|
|
3642
|
-
console.error(
|
|
3643
|
-
`Failed to start server: All ports from ${requestedPort} to ${requestedPort + MAX_PORT_ATTEMPTS - 1} are busy`
|
|
3644
|
-
);
|
|
3645
|
-
throw error;
|
|
3646
|
-
}
|
|
3647
|
-
continue;
|
|
3648
|
-
}
|
|
3649
|
-
throw error;
|
|
3650
|
-
}
|
|
3651
|
-
}
|
|
3652
|
-
throw lastError || new Error("Failed to start server");
|
|
3653
|
-
}
|
|
3654
|
-
|
|
3655
|
-
// lib/server/providers/fileSystemPageProvider.ts
|
|
3656
|
-
import { existsSync as existsSync4, readdirSync as readdirSync3, mkdirSync as mkdirSync3, rmdirSync as rmdirSync3 } from "fs";
|
|
3657
|
-
import { join as join4, dirname as dirname2 } from "path";
|
|
3658
|
-
|
|
3659
|
-
// lib/shared/utils/fileUtils.ts
|
|
3660
|
-
var isJSONFile = (name) => name.endsWith(".json");
|
|
3661
|
-
var stripExtension = (name) => {
|
|
3662
|
-
const lastDotIndex = name.lastIndexOf(".");
|
|
3663
|
-
return lastDotIndex > 0 ? name.substring(0, lastDotIndex) : name;
|
|
3664
|
-
};
|
|
3665
|
-
var mapPageNameToPath2 = (pageName) => {
|
|
3666
|
-
return pageName === "index" ? "/" : `/${pageName}`;
|
|
3667
|
-
};
|
|
3668
|
-
var mapPathToPageName = (path2) => {
|
|
3669
|
-
return path2 === "/" ? "index" : path2.substring(1);
|
|
3670
|
-
};
|
|
3671
|
-
|
|
3672
|
-
// lib/server/providers/fileSystemPageProvider.ts
|
|
3673
|
-
async function loadJSONFile2(filePath) {
|
|
3674
|
-
try {
|
|
3675
|
-
if (await fileExists(filePath)) {
|
|
3676
|
-
return await readTextFile(filePath);
|
|
3677
|
-
}
|
|
3678
|
-
return null;
|
|
3679
|
-
} catch {
|
|
3680
|
-
return null;
|
|
3681
|
-
}
|
|
3682
|
-
}
|
|
3683
|
-
var FileSystemPageProvider = class {
|
|
3684
|
-
constructor(pagesDir, templatesDir) {
|
|
3685
|
-
this.pagesDir = pagesDir;
|
|
3686
|
-
this.templatesDir = templatesDir;
|
|
3687
|
-
}
|
|
3688
|
-
async loadAll() {
|
|
3689
|
-
const pages = /* @__PURE__ */ new Map();
|
|
3690
|
-
if (existsSync4(this.pagesDir)) {
|
|
3691
|
-
await this.scanDirectory(this.pagesDir, "", pages);
|
|
3692
|
-
}
|
|
3693
|
-
if (this.templatesDir && existsSync4(this.templatesDir)) {
|
|
3694
|
-
await this.scanDirectory(this.templatesDir, "templates", pages);
|
|
3695
|
-
}
|
|
3696
|
-
return pages;
|
|
3697
|
-
}
|
|
3698
|
-
/**
|
|
3699
|
-
* Recursively scan a directory for JSON page files
|
|
3700
|
-
* @param dir - Absolute directory path to scan
|
|
3701
|
-
* @param prefix - Relative prefix for page names (e.g., '' for root, 'blog' for blog/)
|
|
3702
|
-
*/
|
|
3703
|
-
async scanDirectory(dir, prefix, pages) {
|
|
3704
|
-
const entries = readdirSync3(dir, { withFileTypes: true });
|
|
3705
|
-
const fileEntries = entries.filter((e) => e.isFile() && isJSONFile(e.name));
|
|
3706
|
-
const dirEntries = entries.filter((e) => e.isDirectory());
|
|
3707
|
-
await Promise.all([
|
|
3708
|
-
...fileEntries.map(async (entry) => {
|
|
3709
|
-
const pageName = prefix ? `${prefix}/${stripExtension(entry.name)}` : stripExtension(entry.name);
|
|
3710
|
-
const content = await loadJSONFile2(join4(dir, entry.name));
|
|
3711
|
-
if (content) {
|
|
3712
|
-
const pagePath = mapPageNameToPath2(pageName);
|
|
3713
|
-
pages.set(pagePath, content);
|
|
3714
|
-
}
|
|
3715
|
-
}),
|
|
3716
|
-
...dirEntries.map((entry) => {
|
|
3717
|
-
const subPrefix = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
3718
|
-
return this.scanDirectory(join4(dir, entry.name), subPrefix, pages);
|
|
3719
|
-
})
|
|
3720
|
-
]);
|
|
3721
|
-
}
|
|
3722
|
-
/**
|
|
3723
|
-
* Resolve a page path to its filesystem file path
|
|
3724
|
-
* Routes /templates/* paths to the templates directory
|
|
3725
|
-
*/
|
|
3726
|
-
resolveFilePath(path2) {
|
|
3727
|
-
const pageName = mapPathToPageName(path2);
|
|
3728
|
-
if (this.templatesDir && pageName.startsWith("templates/")) {
|
|
3729
|
-
const templateName = pageName.substring("templates/".length);
|
|
3730
|
-
return join4(this.templatesDir, `${templateName}.json`);
|
|
3731
|
-
}
|
|
3732
|
-
return join4(this.pagesDir, `${pageName}.json`);
|
|
3733
|
-
}
|
|
3734
|
-
/**
|
|
3735
|
-
* Resolve the root directory for cleanup operations
|
|
3736
|
-
* Returns the appropriate root dir (templates or pages) for a given path
|
|
3737
|
-
*/
|
|
3738
|
-
resolveRootDir(path2) {
|
|
3739
|
-
const pageName = mapPathToPageName(path2);
|
|
3740
|
-
if (this.templatesDir && pageName.startsWith("templates/")) {
|
|
3741
|
-
return this.templatesDir;
|
|
3742
|
-
}
|
|
3743
|
-
return this.pagesDir;
|
|
3744
|
-
}
|
|
3745
|
-
async get(path2) {
|
|
3746
|
-
const filePath = this.resolveFilePath(path2);
|
|
3747
|
-
return loadJSONFile2(filePath);
|
|
3748
|
-
}
|
|
3749
|
-
async save(path2, content) {
|
|
3750
|
-
const { writeFile: writeFile2 } = await import("fs/promises");
|
|
3751
|
-
const filePath = this.resolveFilePath(path2);
|
|
3752
|
-
const dir = dirname2(filePath);
|
|
3753
|
-
if (!existsSync4(dir)) {
|
|
3754
|
-
mkdirSync3(dir, { recursive: true });
|
|
3755
|
-
}
|
|
3756
|
-
await writeFile2(filePath, content, "utf-8");
|
|
3757
|
-
}
|
|
3758
|
-
async delete(path2) {
|
|
3759
|
-
const { unlink } = await import("fs/promises");
|
|
3760
|
-
const filePath = this.resolveFilePath(path2);
|
|
3761
|
-
const rootDir = this.resolveRootDir(path2);
|
|
3762
|
-
if (existsSync4(filePath)) {
|
|
3763
|
-
await unlink(filePath);
|
|
3764
|
-
let dir = dirname2(filePath);
|
|
3765
|
-
while (dir !== rootDir) {
|
|
3766
|
-
try {
|
|
3767
|
-
const remaining = readdirSync3(dir);
|
|
3768
|
-
if (remaining.length === 0) {
|
|
3769
|
-
rmdirSync3(dir);
|
|
3770
|
-
} else {
|
|
3771
|
-
break;
|
|
3772
|
-
}
|
|
3773
|
-
} catch {
|
|
3774
|
-
break;
|
|
3775
|
-
}
|
|
3776
|
-
dir = dirname2(dir);
|
|
3777
|
-
}
|
|
3778
|
-
}
|
|
3779
|
-
}
|
|
3780
|
-
async exists(path2) {
|
|
3781
|
-
const filePath = this.resolveFilePath(path2);
|
|
3782
|
-
return existsSync4(filePath);
|
|
3783
|
-
}
|
|
3784
|
-
baseDir() {
|
|
3785
|
-
return this.pagesDir;
|
|
3786
|
-
}
|
|
3787
|
-
extension() {
|
|
3788
|
-
return ".json";
|
|
3789
|
-
}
|
|
3790
|
-
};
|
|
3791
|
-
|
|
3792
|
-
export {
|
|
3793
|
-
PageCache,
|
|
3794
|
-
WebSocketManager,
|
|
3795
|
-
buildLineMap,
|
|
3796
|
-
lineMapToObject,
|
|
3797
|
-
PageService,
|
|
3798
|
-
ComponentService,
|
|
3799
|
-
EnumService,
|
|
3800
|
-
enumService,
|
|
3801
|
-
FileWatcherService,
|
|
3802
|
-
createCorsHeaders,
|
|
3803
|
-
handleCorsPreflight,
|
|
3804
|
-
createErrorResponse,
|
|
3805
|
-
handleRouteError,
|
|
3806
|
-
withErrorHandling,
|
|
3807
|
-
logRequest,
|
|
3808
|
-
logError,
|
|
3809
|
-
logResponseTime,
|
|
3810
|
-
withLogging,
|
|
3811
|
-
jsonResponse,
|
|
3812
|
-
errorResponse,
|
|
3813
|
-
handleCollectionsRoute,
|
|
3814
|
-
handleCollectionSchemaRoute,
|
|
3815
|
-
handleItemsRoute,
|
|
3816
|
-
handleItemRoute,
|
|
3817
|
-
handleCoreApiRoutes,
|
|
3818
|
-
createServer,
|
|
3819
|
-
FileSystemPageProvider
|
|
3820
|
-
};
|
|
3821
|
-
//# sourceMappingURL=chunk-R6XHAFBF.js.map
|