create-jen-app 1.2.3 → 1.2.4
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/dist/colors.js +0 -17
- package/dist/create.js +5 -17
- package/dist/generator.js +14 -31
- package/dist/index.js +6 -1
- package/package.json +1 -1
- package/templates/ssr-isr/README.md +77 -0
- package/templates/ssr-isr/build.js +118 -0
- package/templates/ssr-isr/jen.config.ts +109 -0
- package/templates/ssr-isr/jenjs.d.ts +22 -0
- package/templates/ssr-isr/lib/api/(hello).js +9 -0
- package/templates/ssr-isr/lib/auth/cookie-utils.js +79 -0
- package/templates/ssr-isr/lib/auth/index.js +2 -0
- package/templates/ssr-isr/lib/auth/jwt.js +57 -0
- package/templates/ssr-isr/lib/auth/session.js +92 -0
- package/templates/ssr-isr/lib/build/asset-hashing.d.ts +10 -0
- package/templates/ssr-isr/lib/build/asset-hashing.js +25 -0
- package/templates/ssr-isr/lib/build/asset-manifest.d.ts +11 -0
- package/templates/ssr-isr/lib/build/asset-manifest.js +21 -0
- package/templates/{static → ssr-isr}/lib/build/build.d.ts +1 -1
- package/templates/ssr-isr/lib/build/build.js +141 -0
- package/templates/{static → ssr-isr}/lib/build/island-hydration.d.ts +8 -5
- package/templates/ssr-isr/lib/build/island-hydration.js +44 -0
- package/templates/ssr-isr/lib/build/minifier.d.ts +20 -0
- package/templates/ssr-isr/lib/build/minifier.js +46 -0
- package/templates/ssr-isr/lib/build/page-renderer.d.ts +17 -0
- package/templates/ssr-isr/lib/build/page-renderer.js +28 -0
- package/templates/ssr-isr/lib/build/production-build.d.ts +10 -0
- package/templates/ssr-isr/lib/build/production-build.js +13 -0
- package/templates/ssr-isr/lib/build/ssg-pipeline.d.ts +15 -0
- package/templates/ssr-isr/lib/build/ssg-pipeline.js +113 -0
- package/templates/ssr-isr/lib/build-tools/build-site.js +36 -0
- package/templates/ssr-isr/lib/cache/index.js +10 -0
- package/templates/ssr-isr/lib/cache/memory.js +40 -0
- package/templates/ssr-isr/lib/cache/redis.js +61 -0
- package/templates/ssr-isr/lib/cli/banner.js +28 -0
- package/templates/ssr-isr/lib/cli/templates/ssg/jen.config.js +32 -0
- package/templates/ssr-isr/lib/cli/templates/ssg/site/index.js +9 -0
- package/templates/ssr-isr/lib/cli/templates/ssr/jen.config.js +32 -0
- package/templates/ssr-isr/lib/cli/templates/ssr/site/index.js +9 -0
- package/templates/ssr-isr/lib/compilers/esbuild-plugins.js +111 -0
- package/templates/ssr-isr/lib/compilers/svelte.js +44 -0
- package/templates/ssr-isr/lib/compilers/vue.js +90 -0
- package/templates/ssr-isr/lib/core/http.js +71 -0
- package/templates/ssr-isr/lib/core/middleware-hooks.js +97 -0
- package/templates/ssr-isr/lib/core/paths.js +39 -0
- package/templates/ssr-isr/lib/core/routes/match.js +47 -0
- package/templates/ssr-isr/lib/core/routes/scan.js +190 -0
- package/templates/ssr-isr/lib/core/types.js +1 -0
- package/templates/ssr-isr/lib/css/compiler.js +74 -0
- package/templates/ssr-isr/lib/db/connector.js +42 -0
- package/templates/ssr-isr/lib/db/drivers/jdb.js +44 -0
- package/templates/ssr-isr/lib/db/drivers/sql.js +182 -0
- package/templates/ssr-isr/lib/db/index.js +48 -0
- package/templates/ssr-isr/lib/db/types.js +1 -0
- package/templates/ssr-isr/lib/graphql/index.js +52 -0
- package/templates/ssr-isr/lib/graphql/resolvers.js +25 -0
- package/templates/ssr-isr/lib/graphql/schema.js +35 -0
- package/templates/ssr-isr/lib/i18n/en.json +4 -0
- package/templates/ssr-isr/lib/i18n/es.json +4 -0
- package/templates/ssr-isr/lib/i18n/index.js +15 -0
- package/templates/ssr-isr/lib/import/jen-import.js +161 -0
- package/templates/ssr-isr/lib/index.js +116 -0
- package/templates/ssr-isr/lib/jdb/engine.js +275 -0
- package/templates/ssr-isr/lib/jdb/index.js +34 -0
- package/templates/ssr-isr/lib/jdb/types.js +1 -0
- package/templates/ssr-isr/lib/jdb/utils.js +176 -0
- package/templates/{static → ssr-isr}/lib/middleware/builtins/body-parser.js +0 -17
- package/templates/ssr-isr/lib/middleware/builtins/cors.js +54 -0
- package/templates/{static → ssr-isr}/lib/middleware/builtins/logger.js +0 -17
- package/templates/{static → ssr-isr}/lib/middleware/builtins/rate-limit.js +0 -17
- package/templates/{static → ssr-isr}/lib/middleware/builtins/request-id.js +0 -17
- package/templates/{static → ssr-isr}/lib/middleware/builtins/security-headers.js +0 -17
- package/templates/ssr-isr/lib/middleware/context.js +124 -0
- package/templates/{static → ssr-isr}/lib/middleware/decorators.js +0 -17
- package/templates/{static → ssr-isr}/lib/middleware/errors/handler.js +0 -17
- package/templates/ssr-isr/lib/middleware/errors/http-error.js +10 -0
- package/templates/ssr-isr/lib/middleware/kernel.js +85 -0
- package/templates/ssr-isr/lib/middleware/pipeline.js +148 -0
- package/templates/ssr-isr/lib/middleware/registry.js +85 -0
- package/templates/ssr-isr/lib/middleware/response.js +107 -0
- package/templates/ssr-isr/lib/middleware/types.d.ts +1 -0
- package/templates/ssr-isr/lib/middleware/types.js +1 -0
- package/templates/ssr-isr/lib/middleware/utils/matcher.js +13 -0
- package/templates/{static → ssr-isr}/lib/native/bundle.js +0 -17
- package/templates/{static → ssr-isr}/lib/native/dev-server.js +0 -17
- package/templates/{static → ssr-isr}/lib/native/index.js +0 -17
- package/templates/{static → ssr-isr}/lib/native/optimizer.js +0 -17
- package/templates/ssr-isr/lib/native/style-compiler.js +19 -0
- package/templates/ssr-isr/lib/plugin/loader.js +36 -0
- package/templates/ssr-isr/lib/runtime/client-runtime.js +25 -0
- package/templates/ssr-isr/lib/runtime/hmr.js +59 -0
- package/templates/ssr-isr/lib/runtime/hydrate.js +55 -0
- package/templates/ssr-isr/lib/runtime/island-hydration-client.js +146 -0
- package/templates/ssr-isr/lib/runtime/islands.js +110 -0
- package/templates/ssr-isr/lib/runtime/render.js +244 -0
- package/templates/ssr-isr/lib/server/api-routes.js +237 -0
- package/templates/ssr-isr/lib/server/api.js +108 -0
- package/templates/ssr-isr/lib/server/app.js +438 -0
- package/templates/ssr-isr/lib/server/runtimeServe.js +169 -0
- package/templates/ssr-isr/lib/server/ssr.js +202 -0
- package/templates/ssr-isr/lib/shared/log.js +64 -0
- package/templates/ssr-isr/package.json +23 -0
- package/templates/ssr-isr/server.js +128 -0
- package/templates/ssr-isr/site/pages/(index).tsx +11 -0
- package/templates/ssr-isr/site/styles/global.scss +37 -0
- package/templates/ssr-isr/tsconfig.json +39 -0
- package/templates/static/build.js +30 -18
- package/templates/static/jen.config.ts +0 -18
- package/templates/static/jenjs.d.ts +0 -18
- package/templates/static/lib/api/(hello).js +0 -17
- package/templates/static/lib/api/examples/files/[...slug].js +22 -0
- package/templates/static/lib/api/examples/hello.js +11 -0
- package/templates/static/lib/api/examples/posts/[id].js +37 -0
- package/templates/static/lib/api/examples/posts.js +37 -0
- package/templates/static/lib/api/examples/search.js +23 -0
- package/templates/static/lib/api/index.js +41 -0
- package/templates/static/lib/api/loader.js +234 -0
- package/templates/static/lib/api/router.js +259 -0
- package/templates/static/lib/assets/types.js +1 -0
- package/templates/static/lib/auth/cookie-utils.js +3 -16
- package/templates/static/lib/auth/index.js +0 -17
- package/templates/static/lib/auth/jwt.js +0 -17
- package/templates/static/lib/auth/session.js +0 -17
- package/templates/static/lib/build/asset-hashing.js +44 -36
- package/templates/static/lib/build/asset-manifest.js +16 -33
- package/templates/static/lib/build/build.js +270 -125
- package/templates/static/lib/build/bundle-analyzer-ui.js +417 -0
- package/templates/static/lib/build/bundle-analyzer.js +945 -0
- package/templates/static/lib/build/code-splitter.js +194 -0
- package/templates/static/lib/build/feature-analyzer.js +190 -0
- package/templates/static/lib/build/feature-gate.js +257 -0
- package/templates/static/lib/build/island-hydration.js +17 -35
- package/templates/static/lib/build/lazy-loader.js +322 -0
- package/templates/static/lib/build/minifier.js +40 -59
- package/templates/static/lib/build/page-renderer.js +23 -40
- package/templates/static/lib/build/production-build.js +9 -26
- package/templates/static/lib/build/rust-hashing.js +71 -0
- package/templates/static/lib/build/script-optimizer.js +285 -0
- package/templates/static/lib/build/ssg-pipeline.js +100 -106
- package/templates/static/lib/build/vercel-output.js +298 -0
- package/templates/static/lib/build-tools/build-site.js +0 -17
- package/templates/static/lib/cache/index.js +0 -17
- package/templates/static/lib/cache/memory.js +0 -17
- package/templates/static/lib/cache/redis.js +0 -17
- package/templates/static/lib/cli/banner.js +0 -17
- package/templates/static/lib/cli/templates/ssg/jen.config.js +0 -17
- package/templates/static/lib/cli/templates/ssr/jen.config.js +0 -17
- package/templates/static/lib/client/Image.js +42 -0
- package/templates/static/lib/client/Link.js +190 -0
- package/templates/static/lib/client/PWA.js +46 -0
- package/templates/static/lib/client/Seo.js +97 -0
- package/templates/static/lib/client/index.js +9 -0
- package/templates/static/lib/client/useNavigation.js +25 -0
- package/templates/static/lib/client/useRouter.js +64 -0
- package/templates/static/lib/client-routing/Link.js +17 -0
- package/templates/static/lib/client-routing/index.js +19 -0
- package/templates/static/lib/client-routing/router.js +151 -0
- package/templates/static/lib/client-routing/signal.js +147 -0
- package/templates/static/lib/compilers/esbuild-plugins.js +0 -17
- package/templates/static/lib/compilers/svelte.js +0 -17
- package/templates/static/lib/compilers/vue.js +0 -17
- package/templates/static/lib/core/config.js +0 -17
- package/templates/static/lib/core/feature-guard.js +136 -0
- package/templates/static/lib/core/features.js +99 -0
- package/templates/static/lib/core/http.js +0 -17
- package/templates/static/lib/core/layouts/index.js +10 -0
- package/templates/static/lib/core/layouts/render.js +158 -0
- package/templates/static/lib/core/layouts/scan.js +112 -0
- package/templates/static/lib/core/layouts/types.js +1 -0
- package/templates/static/lib/core/lifecycle.js +129 -0
- package/templates/static/lib/core/loader-schema.js +81 -0
- package/templates/static/lib/core/middleware-hooks.js +0 -17
- package/templates/static/lib/core/paths.js +0 -17
- package/templates/static/lib/core/routes/advanced.js +114 -0
- package/templates/static/lib/core/routes/handlers.js +181 -0
- package/templates/static/lib/core/routes/match.js +89 -17
- package/templates/static/lib/core/routes/orchestrator.js +171 -0
- package/templates/static/lib/core/routes/rendering-config.js +131 -0
- package/templates/static/lib/core/routes/scan.js +0 -17
- package/templates/static/lib/core/types.js +0 -17
- package/templates/static/lib/css/compiler.js +1 -18
- package/templates/static/lib/data-fetching/cache.js +223 -0
- package/templates/static/lib/data-fetching/client.js +202 -0
- package/templates/static/lib/data-fetching/feature-guard.js +29 -0
- package/templates/static/lib/data-fetching/graphql.js +265 -0
- package/templates/static/lib/data-fetching/index.js +57 -0
- package/templates/static/lib/data-fetching/rest.js +256 -0
- package/templates/static/lib/data-fetching/server.js +182 -0
- package/templates/static/lib/data-fetching/types.js +5 -0
- package/templates/static/lib/db/connector.js +0 -17
- package/templates/static/lib/db/drivers/jdb.js +0 -17
- package/templates/static/lib/db/drivers/sql.js +0 -17
- package/templates/static/lib/db/index.js +0 -17
- package/templates/static/lib/db/types.js +0 -17
- package/templates/static/lib/devtools/component-tree.js +106 -0
- package/templates/static/lib/devtools/devtools.js +638 -0
- package/templates/static/lib/devtools/event-bus.js +29 -0
- package/templates/static/lib/devtools/event-logger.js +67 -0
- package/templates/static/lib/devtools/index.js +9 -0
- package/templates/static/lib/devtools/integration.js +149 -0
- package/templates/static/lib/devtools/performance.js +84 -0
- package/templates/static/lib/devtools/persistence.js +57 -0
- package/templates/static/lib/devtools/plugins.js +97 -0
- package/templates/static/lib/devtools/search.js +89 -0
- package/templates/static/lib/devtools/ui.js +769 -0
- package/templates/static/lib/features/api/handler.js +10 -0
- package/templates/static/lib/features/api/index.js +5 -0
- package/templates/static/lib/features/api/types.js +4 -0
- package/templates/static/lib/features/middleware/compiled.js +7 -0
- package/templates/static/lib/features/middleware/index.js +5 -0
- package/templates/static/lib/features/middleware/types.js +4 -0
- package/templates/static/lib/fonts/index.js +46 -0
- package/templates/static/lib/fonts/inject.js +125 -0
- package/templates/static/lib/fonts/loader.js +196 -0
- package/templates/static/lib/fonts/types.js +1 -0
- package/templates/static/lib/graphql/index.js +1 -18
- package/templates/static/lib/graphql/resolvers.js +20 -13
- package/templates/static/lib/graphql/schema.js +0 -17
- package/templates/static/lib/i18n/index.js +7 -19
- package/templates/static/lib/import/jen-import.js +1 -18
- package/templates/static/lib/index.js +79 -125
- package/templates/static/lib/jdb/engine.js +0 -17
- package/templates/static/lib/jdb/index.js +1 -18
- package/templates/static/lib/jdb/types.js +0 -17
- package/templates/static/lib/jdb/utils.js +0 -17
- package/templates/static/lib/middleware/builtins/cors.js +3 -16
- package/templates/static/lib/middleware/context.js +0 -17
- package/templates/static/lib/middleware/kernel.js +117 -25
- package/templates/static/lib/middleware/pipeline.js +0 -17
- package/templates/static/lib/middleware/registry.js +0 -17
- package/templates/static/lib/middleware/response.js +0 -17
- package/templates/static/lib/plugin/examples/analytics-plugin.js +183 -0
- package/templates/static/lib/plugin/examples/cdn-upload-plugin.js +94 -0
- package/templates/static/lib/plugin/loader.js +0 -17
- package/templates/static/lib/plugin/plugin-manager.js +177 -0
- package/templates/static/lib/plugin/types.js +28 -0
- package/templates/static/lib/runtime/client-runtime.js +0 -17
- package/templates/static/lib/runtime/hmr.js +0 -17
- package/templates/static/lib/runtime/hydrate.js +0 -17
- package/templates/static/lib/runtime/island-hydration-client.js +0 -17
- package/templates/static/lib/runtime/islands.js +0 -17
- package/templates/static/lib/runtime/render.js +208 -50
- package/templates/static/lib/security/security-config.js +60 -0
- package/templates/static/lib/security/security-middleware.js +229 -0
- package/templates/static/lib/server/api-routes.js +153 -43
- package/templates/static/lib/server/api.js +0 -17
- package/templates/static/lib/server/app.js +539 -223
- package/templates/static/lib/server/isr.js +365 -0
- package/templates/static/lib/server/runtimeServe.js +31 -24
- package/templates/static/lib/server/ssr.js +98 -22
- package/templates/static/lib/server-actions/handler.js +180 -0
- package/templates/static/lib/server-actions/index.js +19 -0
- package/templates/static/lib/server-actions/middleware.js +146 -0
- package/templates/static/lib/server-actions/scan.js +152 -0
- package/templates/static/lib/server-actions/types.js +1 -0
- package/templates/static/lib/server-actions/validators.js +156 -0
- package/templates/static/lib/shared/log.js +19 -20
- package/templates/static/lib/telemetry/api/rate-limiter.js +32 -0
- package/templates/static/lib/telemetry/api/validator.js +67 -0
- package/templates/static/lib/telemetry/client.js +121 -0
- package/templates/static/lib/telemetry/tests/rate-limiter.test.js +46 -0
- package/templates/static/lib/telemetry/tests/validator.test.js +62 -0
- package/templates/static/lib/vendor/glob/glob.js +4766 -0
- package/templates/static/lib/vendor/preact/LICENSE +21 -0
- package/templates/static/lib/vendor/preact/preact.module.js +797 -0
- package/templates/static/lib/vendor/sass/sass.node.mjs +212 -0
- package/templates/static/package.json +4 -0
- package/templates/static/server.js +22 -22
- package/templates/static/site/(home).tsx +0 -18
- package/templates/static/tsconfig.json +5 -1
- package/templates/static/.esbuild/jen.config.js +0 -19
- package/templates/static/lib/build/asset-hashing.d.ts +0 -10
- package/templates/static/lib/build/asset-manifest.d.ts +0 -11
- package/templates/static/lib/build/minifier.d.ts +0 -20
- package/templates/static/lib/build/page-renderer.d.ts +0 -17
- package/templates/static/lib/build/production-build.d.ts +0 -10
- package/templates/static/lib/build/ssg-pipeline.d.ts +0 -15
- package/templates/static/lib/middleware/errors/http-error.js +0 -27
- package/templates/static/lib/middleware/types.js +0 -18
- package/templates/static/lib/middleware/utils/matcher.js +0 -30
- package/templates/static/lib/native/style-compiler.js +0 -36
- /package/templates/{static → ssr-isr}/lib/api/(hello).d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/auth/cookie-utils.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/auth/index.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/auth/jwt.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/auth/session.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/build-tools/build-site.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/cache/index.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/cache/memory.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/cache/redis.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/cli/banner.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/cli/templates/ssg/jen.config.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/cli/templates/ssg/site/index.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/cli/templates/ssr/jen.config.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/cli/templates/ssr/site/index.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/compilers/esbuild-plugins.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/compilers/svelte.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/compilers/vue.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/core/config.d.ts +0 -0
- /package/templates/{static/lib/middleware/types.d.ts → ssr-isr/lib/core/config.js} +0 -0
- /package/templates/{static → ssr-isr}/lib/core/http.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/core/middleware-hooks.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/core/paths.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/core/routes/match.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/core/routes/scan.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/core/types.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/css/compiler.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/db/connector.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/db/drivers/jdb.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/db/drivers/sql.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/db/index.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/db/types.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/graphql/index.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/graphql/resolvers.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/graphql/schema.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/i18n/index.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/import/jen-import.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/index.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/jdb/engine.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/jdb/index.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/jdb/types.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/jdb/utils.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/middleware/builtins/body-parser.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/middleware/builtins/cors.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/middleware/builtins/logger.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/middleware/builtins/rate-limit.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/middleware/builtins/request-id.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/middleware/builtins/security-headers.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/middleware/context.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/middleware/decorators.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/middleware/errors/handler.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/middleware/errors/http-error.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/middleware/kernel.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/middleware/pipeline.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/middleware/registry.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/middleware/response.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/middleware/utils/matcher.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/native/bundle.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/native/dev-server.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/native/index.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/native/optimizer.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/native/style-compiler.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/plugin/loader.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/runtime/client-runtime.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/runtime/hmr.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/runtime/hydrate.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/runtime/island-hydration-client.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/runtime/islands.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/runtime/render.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/server/api-routes.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/server/api.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/server/app.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/server/runtimeServe.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/server/ssr.d.ts +0 -0
- /package/templates/{static → ssr-isr}/lib/shared/log.d.ts +0 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-safe loader data schema pattern.
|
|
3
|
+
* Ensures loader return type matches component props type at compile time.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* type PageData = { posts: Post[]; count: number };
|
|
8
|
+
*
|
|
9
|
+
* export const loader = defineLoader<PageData>(async (ctx) => {
|
|
10
|
+
* return {
|
|
11
|
+
* posts: await fetchPosts(),
|
|
12
|
+
* count: 42,
|
|
13
|
+
* };
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* export default function Page({ data }: { data: PageData }) {
|
|
17
|
+
* return <div>{data.count} posts</div>;
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export function defineLoader(handler) {
|
|
22
|
+
return async (ctx) => {
|
|
23
|
+
const result = handler(ctx);
|
|
24
|
+
return Promise.resolve(result);
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Validates that loader data matches expected schema.
|
|
29
|
+
* Runtime check to catch type mismatches in development.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* const data = validateLoaderData(result, { posts: 'array', count: 'number' });
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export function validateLoaderData(data, schema) {
|
|
37
|
+
if (typeof data !== "object" || data === null) {
|
|
38
|
+
throw new Error(`Expected object, got ${typeof data}`);
|
|
39
|
+
}
|
|
40
|
+
const obj = data;
|
|
41
|
+
for (const [key, type] of Object.entries(schema)) {
|
|
42
|
+
const value = obj[key];
|
|
43
|
+
// Determine actual type (arrays are objects, so treat both as "object")
|
|
44
|
+
let actualType = typeof value;
|
|
45
|
+
if (value === null) {
|
|
46
|
+
actualType = "object"; // null is typeof 'object' in JavaScript
|
|
47
|
+
} else if (Array.isArray(value)) {
|
|
48
|
+
actualType = type === "array" ? "array" : "object";
|
|
49
|
+
}
|
|
50
|
+
if (actualType !== type) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`Loader data mismatch: expected ${key} to be ${type}, got ${actualType}`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return obj;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Middleware data schema for type-safe middleware → loader flow.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```typescript
|
|
63
|
+
* type MiddlewareData = { userId: number; isAdmin: boolean };
|
|
64
|
+
* type PageData = { user: User; canDelete: boolean };
|
|
65
|
+
*
|
|
66
|
+
* export const middleware = defineMiddleware<MiddlewareData>(async (ctx) => {
|
|
67
|
+
* return { userId: 42, isAdmin: true };
|
|
68
|
+
* });
|
|
69
|
+
*
|
|
70
|
+
* export const loader = defineLoader<PageData>(async (ctx) => {
|
|
71
|
+
* const { userId, isAdmin } = ctx.data as MiddlewareData;
|
|
72
|
+
* return { user: await getUser(userId), canDelete: isAdmin };
|
|
73
|
+
* });
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export function defineMiddleware(handler) {
|
|
77
|
+
return async (ctx) => {
|
|
78
|
+
const result = handler(ctx);
|
|
79
|
+
return Promise.resolve(result);
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -1,20 +1,3 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* This file is part of Jen.js.
|
|
3
|
-
* Copyright (C) 2026 oopsio
|
|
4
|
-
*
|
|
5
|
-
* This program is free software: you can redistribute it and/or modify
|
|
6
|
-
* it under the terms of the GNU General Public License as published by
|
|
7
|
-
* the Free Software Foundation, either version 3 of the License, or
|
|
8
|
-
* (at your option) any later version.
|
|
9
|
-
*
|
|
10
|
-
* This program is distributed in the hope that it will be useful,
|
|
11
|
-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
-
* GNU General Public License for more details.
|
|
14
|
-
*
|
|
15
|
-
* You should have received a copy of the GNU General Public License
|
|
16
|
-
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
-
*/
|
|
18
1
|
/**
|
|
19
2
|
* Type definitions note:
|
|
20
3
|
* Route module types (middleware field, hydrate field) are defined in core/types.ts.
|
|
@@ -1,20 +1,3 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* This file is part of Jen.js.
|
|
3
|
-
* Copyright (C) 2026 oopsio
|
|
4
|
-
*
|
|
5
|
-
* This program is free software: you can redistribute it and/or modify
|
|
6
|
-
* it under the terms of the GNU General Public License as published by
|
|
7
|
-
* the Free Software Foundation, either version 3 of the License, or
|
|
8
|
-
* (at your option) any later version.
|
|
9
|
-
*
|
|
10
|
-
* This program is distributed in the hope that it will be useful,
|
|
11
|
-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
-
* GNU General Public License for more details.
|
|
14
|
-
*
|
|
15
|
-
* You should have received a copy of the GNU General Public License
|
|
16
|
-
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
-
*/
|
|
18
1
|
import { join } from "node:path";
|
|
19
2
|
/**
|
|
20
3
|
* Resolve an absolute path relative to the configured site directory.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache for storing evaluated route configurations
|
|
3
|
+
*/
|
|
4
|
+
const advancedConfigCache = new Map();
|
|
5
|
+
/**
|
|
6
|
+
* Extract advanced routing config from a route module
|
|
7
|
+
*
|
|
8
|
+
* @param module Route module
|
|
9
|
+
* @returns Advanced route configuration
|
|
10
|
+
*/
|
|
11
|
+
export function extractAdvancedConfig(module) {
|
|
12
|
+
return module?.routeConfig ?? {};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Get advanced route config with caching
|
|
16
|
+
*
|
|
17
|
+
* @param filePath Route file path
|
|
18
|
+
* @returns Cached or newly evaluated config
|
|
19
|
+
*/
|
|
20
|
+
export async function getAdvancedRouteConfig(filePath) {
|
|
21
|
+
if (advancedConfigCache.has(filePath)) {
|
|
22
|
+
return advancedConfigCache.get(filePath);
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const module = await import(`file://${filePath}`);
|
|
26
|
+
const config = extractAdvancedConfig(module);
|
|
27
|
+
advancedConfigCache.set(filePath, config);
|
|
28
|
+
return config;
|
|
29
|
+
} catch {
|
|
30
|
+
const config = {};
|
|
31
|
+
advancedConfigCache.set(filePath, config);
|
|
32
|
+
return config;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Synchronously get advanced route config
|
|
37
|
+
*
|
|
38
|
+
* @param filePath Route file path
|
|
39
|
+
* @returns Configuration (empty if unavailable)
|
|
40
|
+
*/
|
|
41
|
+
export function getAdvancedRouteConfigSync(filePath) {
|
|
42
|
+
if (advancedConfigCache.has(filePath)) {
|
|
43
|
+
return advancedConfigCache.get(filePath);
|
|
44
|
+
}
|
|
45
|
+
const config = {};
|
|
46
|
+
advancedConfigCache.set(filePath, config);
|
|
47
|
+
return config;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Clear the advanced config cache
|
|
51
|
+
*/
|
|
52
|
+
export function clearAdvancedConfigCache() {
|
|
53
|
+
advancedConfigCache.clear();
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Validate query parameters against a schema
|
|
57
|
+
*
|
|
58
|
+
* @param query Parsed query parameters
|
|
59
|
+
* @param schema Validation rules
|
|
60
|
+
* @returns { valid: boolean; errors: string[] }
|
|
61
|
+
*/
|
|
62
|
+
export function validateQueryParams(query, schema) {
|
|
63
|
+
const errors = [];
|
|
64
|
+
for (const [key, rule] of Object.entries(schema)) {
|
|
65
|
+
const value = query[key];
|
|
66
|
+
// Check required
|
|
67
|
+
if (rule.required && !value) {
|
|
68
|
+
errors.push(`Query parameter '${key}' is required`);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (!value) continue;
|
|
72
|
+
// Check type
|
|
73
|
+
if (rule.type === "number" && isNaN(Number(value))) {
|
|
74
|
+
errors.push(`Query parameter '${key}' must be a number`);
|
|
75
|
+
}
|
|
76
|
+
if (rule.type === "boolean" && !["true", "false"].includes(value)) {
|
|
77
|
+
errors.push(`Query parameter '${key}' must be 'true' or 'false'`);
|
|
78
|
+
}
|
|
79
|
+
// Check enum
|
|
80
|
+
if (rule.enum && !rule.enum.includes(value)) {
|
|
81
|
+
errors.push(
|
|
82
|
+
`Query parameter '${key}' must be one of: ${rule.enum.join(", ")}`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return { valid: errors.length === 0, errors };
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Apply defaults and coerce query parameters
|
|
90
|
+
*
|
|
91
|
+
* @param query Raw query parameters
|
|
92
|
+
* @param schema Validation schema
|
|
93
|
+
* @returns Processed query parameters
|
|
94
|
+
*/
|
|
95
|
+
export function processQueryParams(query, schema) {
|
|
96
|
+
const processed = { ...query };
|
|
97
|
+
for (const [key, rule] of Object.entries(schema)) {
|
|
98
|
+
let value = processed[key];
|
|
99
|
+
// Apply default
|
|
100
|
+
if (!value && rule.default !== undefined) {
|
|
101
|
+
value = rule.default;
|
|
102
|
+
processed[key] = value;
|
|
103
|
+
}
|
|
104
|
+
// Coerce type
|
|
105
|
+
if (value) {
|
|
106
|
+
if (rule.type === "number") {
|
|
107
|
+
processed[key] = Number(value);
|
|
108
|
+
} else if (rule.type === "boolean") {
|
|
109
|
+
processed[key] = value === "true";
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return processed;
|
|
114
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { scanRoutes } from "./scan.js";
|
|
2
|
+
/**
|
|
3
|
+
* Handles redirects configured at the application level
|
|
4
|
+
*
|
|
5
|
+
* @param pathname URL path to check
|
|
6
|
+
* @param redirects Array of redirect configurations
|
|
7
|
+
* @returns Redirect target or null
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* const redirect = getRedirect("/old-page", [
|
|
11
|
+
* { from: "/old-page", to: "/new-page", status: 301 }
|
|
12
|
+
* ]);
|
|
13
|
+
* // Returns: { to: "/new-page", status: 301 }
|
|
14
|
+
*/
|
|
15
|
+
export function getRedirect(pathname, redirects) {
|
|
16
|
+
for (const r of redirects) {
|
|
17
|
+
// Exact match
|
|
18
|
+
if (r.from === pathname) {
|
|
19
|
+
return { to: r.to, status: r.status ?? 301 };
|
|
20
|
+
}
|
|
21
|
+
// Prefix match (for pattern-based redirects)
|
|
22
|
+
if (r.from.endsWith("*")) {
|
|
23
|
+
const prefix = r.from.slice(0, -1);
|
|
24
|
+
if (pathname.startsWith(prefix)) {
|
|
25
|
+
// Preserve the remaining path
|
|
26
|
+
const remaining = pathname.slice(prefix.length);
|
|
27
|
+
const target = r.to.endsWith("*")
|
|
28
|
+
? r.to.slice(0, -1) + remaining
|
|
29
|
+
: r.to;
|
|
30
|
+
return { to: target, status: r.status ?? 301 };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Send a redirect response
|
|
38
|
+
*
|
|
39
|
+
* @param res ServerResponse object
|
|
40
|
+
* @param location Target URL
|
|
41
|
+
* @param status HTTP status code (default 301)
|
|
42
|
+
*/
|
|
43
|
+
export function sendRedirect(res, location, status = 301) {
|
|
44
|
+
res.statusCode = status;
|
|
45
|
+
res.setHeader("Location", location);
|
|
46
|
+
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
47
|
+
res.end(`Redirecting to ${location}`);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Send a 404 response with optional custom handler
|
|
51
|
+
*
|
|
52
|
+
* @param res ServerResponse object
|
|
53
|
+
* @param config 404 handler configuration
|
|
54
|
+
* @param pathname The path that was not found
|
|
55
|
+
*/
|
|
56
|
+
export function send404(res, config, pathname) {
|
|
57
|
+
res.statusCode = 404;
|
|
58
|
+
// Prefer JSON response if available
|
|
59
|
+
if (config.json) {
|
|
60
|
+
res.setHeader("content-type", "application/json; charset=utf-8");
|
|
61
|
+
res.end(JSON.stringify(config.json));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
// Fallback to HTML
|
|
65
|
+
if (config.html) {
|
|
66
|
+
res.setHeader("content-type", "text/html; charset=utf-8");
|
|
67
|
+
res.end(config.html);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// Default 404 response
|
|
71
|
+
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
72
|
+
res.end(`404 Not Found: ${pathname}`);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Find a custom 404 handler for a given pathname
|
|
76
|
+
* Searches the route tree for a matching 404 handler route
|
|
77
|
+
*
|
|
78
|
+
* @param config Framework configuration
|
|
79
|
+
* @param pathname URL path that was not found
|
|
80
|
+
* @returns NotFoundConfig with appropriate handler or null
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* // With routes:
|
|
84
|
+
* // - (home).tsx
|
|
85
|
+
* // - /docs/(...rest).tsx (handles docs with custom 404)
|
|
86
|
+
* // - /api/(...rest).tsx (handles api with custom 404)
|
|
87
|
+
*
|
|
88
|
+
* // getNotFoundHandler(config, "/api/unknown") returns handler for /api/(...rest)
|
|
89
|
+
*/
|
|
90
|
+
export function getNotFoundHandler(config, pathname) {
|
|
91
|
+
const routes = scanRoutes(config);
|
|
92
|
+
// Find catch-all routes that match this pathname
|
|
93
|
+
let bestMatch = null;
|
|
94
|
+
let bestMatchLength = 0;
|
|
95
|
+
for (const route of routes) {
|
|
96
|
+
// Check if this is a catch-all route
|
|
97
|
+
if (!route.urlPath.includes("*")) continue;
|
|
98
|
+
// Extract the prefix before the catch-all
|
|
99
|
+
const prefix = route.urlPath.split("*")[0];
|
|
100
|
+
if (!prefix) continue; // Skip root catch-all for now
|
|
101
|
+
// Check if pathname matches this prefix
|
|
102
|
+
if (pathname.startsWith(prefix)) {
|
|
103
|
+
if (prefix.length > bestMatchLength) {
|
|
104
|
+
bestMatch = route;
|
|
105
|
+
bestMatchLength = prefix.length;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (bestMatch) {
|
|
110
|
+
return { handler: bestMatch };
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Create a default 404 HTML page
|
|
116
|
+
*
|
|
117
|
+
* @param pathname Path that was not found
|
|
118
|
+
* @returns HTML string
|
|
119
|
+
*/
|
|
120
|
+
export function createDefault404Html(pathname) {
|
|
121
|
+
return `<!DOCTYPE html>
|
|
122
|
+
<html lang="en">
|
|
123
|
+
<head>
|
|
124
|
+
<meta charset="UTF-8">
|
|
125
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
126
|
+
<title>404 Not Found</title>
|
|
127
|
+
<style>
|
|
128
|
+
body {
|
|
129
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
130
|
+
display: flex;
|
|
131
|
+
align-items: center;
|
|
132
|
+
justify-content: center;
|
|
133
|
+
min-height: 100vh;
|
|
134
|
+
margin: 0;
|
|
135
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
136
|
+
}
|
|
137
|
+
.container {
|
|
138
|
+
text-align: center;
|
|
139
|
+
background: white;
|
|
140
|
+
padding: 2rem;
|
|
141
|
+
border-radius: 8px;
|
|
142
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
143
|
+
}
|
|
144
|
+
h1 {
|
|
145
|
+
font-size: 3rem;
|
|
146
|
+
margin: 0;
|
|
147
|
+
color: #333;
|
|
148
|
+
}
|
|
149
|
+
p {
|
|
150
|
+
margin: 1rem 0 0 0;
|
|
151
|
+
color: #666;
|
|
152
|
+
}
|
|
153
|
+
.path {
|
|
154
|
+
font-family: monospace;
|
|
155
|
+
background: #f5f5f5;
|
|
156
|
+
padding: 0.5rem 1rem;
|
|
157
|
+
margin: 1rem 0;
|
|
158
|
+
border-radius: 4px;
|
|
159
|
+
color: #333;
|
|
160
|
+
}
|
|
161
|
+
a {
|
|
162
|
+
color: #667eea;
|
|
163
|
+
text-decoration: none;
|
|
164
|
+
margin-top: 1rem;
|
|
165
|
+
display: inline-block;
|
|
166
|
+
}
|
|
167
|
+
a:hover {
|
|
168
|
+
text-decoration: underline;
|
|
169
|
+
}
|
|
170
|
+
</style>
|
|
171
|
+
</head>
|
|
172
|
+
<body>
|
|
173
|
+
<div class="container">
|
|
174
|
+
<h1>404</h1>
|
|
175
|
+
<p>Page Not Found</p>
|
|
176
|
+
<div class="path">${pathname}</div>
|
|
177
|
+
<a href="/">Go Home</a>
|
|
178
|
+
</div>
|
|
179
|
+
</body>
|
|
180
|
+
</html>`;
|
|
181
|
+
}
|
|
@@ -1,20 +1,78 @@
|
|
|
1
|
-
|
|
2
|
-
*
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Custom error type for invalid route parameters
|
|
3
|
+
*/
|
|
4
|
+
export class InvalidRouteParamError extends Error {
|
|
5
|
+
constructor(paramName, paramValue, reason) {
|
|
6
|
+
super(`Invalid route parameter "${paramName}": ${reason}`);
|
|
7
|
+
this.name = "InvalidRouteParamError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Validates a route parameter to prevent path traversal and injection attacks.
|
|
9
12
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
13
|
+
* Security checks:
|
|
14
|
+
* - Rejects ".." (directory traversal)
|
|
15
|
+
* - Rejects leading "/" (absolute paths)
|
|
16
|
+
* - Rejects null bytes (\0)
|
|
17
|
+
* - Rejects unicode encoding tricks (e.g., %2e%2e are decoded by decodeURIComponent first)
|
|
18
|
+
* - For catch-all routes (*rest), allows "/" and other separators but still rejects traversal
|
|
19
|
+
* - For regular params, only allows alphanumeric, underscore, hyphen, dot
|
|
14
20
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
21
|
+
* @param paramName Parameter name (e.g., "id", "rest")
|
|
22
|
+
* @param paramValue The decoded parameter value
|
|
23
|
+
* @param isCatchAll Whether this is a catch-all route parameter (allows more characters)
|
|
24
|
+
* @throws InvalidRouteParamError if validation fails
|
|
17
25
|
*/
|
|
26
|
+
export function validateRouteParam(paramName, paramValue, isCatchAll = false) {
|
|
27
|
+
// Check for null bytes
|
|
28
|
+
if (paramValue.includes("\0")) {
|
|
29
|
+
throw new InvalidRouteParamError(
|
|
30
|
+
paramName,
|
|
31
|
+
paramValue,
|
|
32
|
+
"contains null bytes",
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
// Check for leading forward slash (absolute path)
|
|
36
|
+
if (paramValue.startsWith("/")) {
|
|
37
|
+
throw new InvalidRouteParamError(
|
|
38
|
+
paramName,
|
|
39
|
+
paramValue,
|
|
40
|
+
"cannot start with /",
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
// Check for directory traversal: ".." as a complete component or at any position
|
|
44
|
+
if (paramValue.includes("..")) {
|
|
45
|
+
throw new InvalidRouteParamError(
|
|
46
|
+
paramName,
|
|
47
|
+
paramValue,
|
|
48
|
+
"contains .. (directory traversal)",
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
// Check for backslash (Windows path separator) to prevent escaping
|
|
52
|
+
if (paramValue.includes("\\")) {
|
|
53
|
+
throw new InvalidRouteParamError(
|
|
54
|
+
paramName,
|
|
55
|
+
paramValue,
|
|
56
|
+
"contains backslash",
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
// For catch-all parameters, allow more flexible paths with /, but still reject dangerous patterns
|
|
60
|
+
if (isCatchAll) {
|
|
61
|
+
// Additional check: if someone passes %2e%2e or other encoded traversal, it's already decoded
|
|
62
|
+
// by decodeURIComponent, so the ".." check above will catch it. Just verify no empty path components
|
|
63
|
+
// that could indicate traversal (though ".." is already blocked above)
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
// For regular route parameters, only allow safe characters
|
|
67
|
+
// Allow: alphanumeric, underscore, hyphen, dot
|
|
68
|
+
if (!/^[a-zA-Z0-9_.-]+$/.test(paramValue)) {
|
|
69
|
+
throw new InvalidRouteParamError(
|
|
70
|
+
paramName,
|
|
71
|
+
paramValue,
|
|
72
|
+
"contains invalid characters (only alphanumeric, underscore, hyphen, dot allowed)",
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
18
76
|
/**
|
|
19
77
|
* Matches a URL pathname against a list of routes and returns the matching route with extracted parameters.
|
|
20
78
|
*
|
|
@@ -23,7 +81,8 @@
|
|
|
23
81
|
* 2. For each route, compile its regex pattern and test against pathname
|
|
24
82
|
* 3. On first match, extract captured groups as parameters
|
|
25
83
|
* 4. URL-decode parameter values to handle special characters
|
|
26
|
-
* 5.
|
|
84
|
+
* 5. Validate all parameters to prevent path traversal attacks
|
|
85
|
+
* 6. Return matched route and parameters
|
|
27
86
|
*
|
|
28
87
|
* Route specificity:
|
|
29
88
|
* - Static routes (no parameters) are tried before dynamic routes
|
|
@@ -34,6 +93,7 @@
|
|
|
34
93
|
* @param pathname URL path to match, e.g., "/posts/42" or "/docs/api/reference"
|
|
35
94
|
*
|
|
36
95
|
* @returns MatchResult object with matched route and parameters, or null if no route matches
|
|
96
|
+
* @throws InvalidRouteParamError if any parameter contains invalid/dangerous content
|
|
37
97
|
*
|
|
38
98
|
* @example
|
|
39
99
|
* const routes = scanRoutes(config);
|
|
@@ -47,16 +107,28 @@ export function matchRoute(routes, pathname) {
|
|
|
47
107
|
for (const r of routes) {
|
|
48
108
|
const re = new RegExp(r.pattern);
|
|
49
109
|
const m = pathname.match(re);
|
|
50
|
-
if (!m)
|
|
110
|
+
if (!m) {
|
|
111
|
+
console.error(
|
|
112
|
+
`[Route Match] Pattern "${r.pattern}" did not match "${pathname}"`,
|
|
113
|
+
);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
51
116
|
/**
|
|
52
117
|
* Extract captured groups from the regex match.
|
|
53
118
|
* Captured group 0 is the entire match; groups 1+ are the parameters.
|
|
54
119
|
* Each parameter name from r.paramNames corresponds to its regex group by index.
|
|
55
120
|
* URL-decode parameter values since they came from a URL path.
|
|
121
|
+
* Then validate each parameter to prevent path traversal attacks.
|
|
56
122
|
*/
|
|
57
123
|
const params = {};
|
|
58
124
|
for (let i = 0; i < r.paramNames.length; i++) {
|
|
59
|
-
|
|
125
|
+
const paramName = r.paramNames[i];
|
|
126
|
+
const paramValue = decodeURIComponent(m[i + 1] ?? "");
|
|
127
|
+
// Determine if this is a catch-all parameter (typically named "rest" or ends with "*")
|
|
128
|
+
const isCatchAll = paramName === "rest" || r.pattern.includes("*");
|
|
129
|
+
// Validate the parameter to prevent path traversal and injection attacks
|
|
130
|
+
validateRouteParam(paramName, paramValue, isCatchAll);
|
|
131
|
+
params[paramName] = paramValue;
|
|
60
132
|
}
|
|
61
133
|
return { route: r, params };
|
|
62
134
|
}
|