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
|
@@ -1,35 +1,36 @@
|
|
|
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 {
|
|
19
2
|
createRouteMiddlewareContext,
|
|
20
3
|
executeRouteMiddleware,
|
|
21
4
|
} from "../core/middleware-hooks.js";
|
|
5
|
+
import {
|
|
6
|
+
scanLayouts,
|
|
7
|
+
buildLayoutHierarchy,
|
|
8
|
+
resolveLayoutStack,
|
|
9
|
+
renderWithLayoutStack,
|
|
10
|
+
collectLayoutHeads,
|
|
11
|
+
} from "../core/layouts/index.js";
|
|
22
12
|
import { createIslandMarker } from "./islands.js";
|
|
23
|
-
import { h } from "preact";
|
|
24
13
|
import renderToString from "preact-render-to-string";
|
|
25
14
|
import { pathToFileURL } from "node:url";
|
|
26
15
|
import { join } from "node:path";
|
|
27
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
mkdirSync,
|
|
18
|
+
existsSync,
|
|
19
|
+
readFileSync,
|
|
20
|
+
statSync,
|
|
21
|
+
writeFileSync,
|
|
22
|
+
} from "node:fs";
|
|
23
|
+
import { createHash } from "node:crypto";
|
|
28
24
|
import esbuild from "esbuild";
|
|
29
25
|
import {
|
|
30
26
|
vueEsbuildPlugin,
|
|
31
27
|
svelteEsbuildPlugin,
|
|
32
28
|
} from "../compilers/esbuild-plugins.js";
|
|
29
|
+
/**
|
|
30
|
+
* Maximum data size for serialization (1MB).
|
|
31
|
+
* Prevents DoS attacks via extremely large payloads.
|
|
32
|
+
*/
|
|
33
|
+
const MAX_DATA_SIZE = 1024 * 1024; // 1MB
|
|
33
34
|
/**
|
|
34
35
|
* Escapes HTML special characters to prevent injection attacks.
|
|
35
36
|
* Used when serializing user data or dynamic values into HTML attributes or script content.
|
|
@@ -45,23 +46,134 @@ function escapeHtml(s) {
|
|
|
45
46
|
.replaceAll('"', """)
|
|
46
47
|
.replaceAll("'", "'");
|
|
47
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Recursively escapes all strings in an object to prevent XSS attacks.
|
|
51
|
+
* Traverses nested objects, arrays, and all string values.
|
|
52
|
+
* Non-string, non-object values are returned as-is.
|
|
53
|
+
*
|
|
54
|
+
* @param value The value to escape (can be any type).
|
|
55
|
+
* @returns A new object with all strings escaped.
|
|
56
|
+
*/
|
|
57
|
+
function recursivelyEscapeStrings(value) {
|
|
58
|
+
if (typeof value === "string") {
|
|
59
|
+
return escapeHtml(value);
|
|
60
|
+
}
|
|
61
|
+
if (Array.isArray(value)) {
|
|
62
|
+
return value.map(recursivelyEscapeStrings);
|
|
63
|
+
}
|
|
64
|
+
if (value !== null && typeof value === "object") {
|
|
65
|
+
const escaped = {};
|
|
66
|
+
for (const [key, val] of Object.entries(value)) {
|
|
67
|
+
escaped[key] = recursivelyEscapeStrings(val);
|
|
68
|
+
}
|
|
69
|
+
return escaped;
|
|
70
|
+
}
|
|
71
|
+
// Return primitives (numbers, booleans, null, undefined) as-is
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* In-memory deduplication map for concurrent transpile requests.
|
|
76
|
+
* Prevents race conditions where multiple concurrent requests transpile the same file.
|
|
77
|
+
* Maps from cache key to a Promise that resolves to the output file path.
|
|
78
|
+
*/
|
|
79
|
+
const transpileInProgress = new Map();
|
|
80
|
+
/**
|
|
81
|
+
* Computes a hash of the file content to track changes.
|
|
82
|
+
* Uses SHA-256 to create a unique hash of the source file.
|
|
83
|
+
* This enables cache invalidation when the file changes.
|
|
84
|
+
*
|
|
85
|
+
* @param filePath The absolute path to the file.
|
|
86
|
+
* @returns A short hash of the file content (first 8 chars).
|
|
87
|
+
*/
|
|
88
|
+
function getFileHash(filePath) {
|
|
89
|
+
try {
|
|
90
|
+
const content = readFileSync(filePath, "utf-8");
|
|
91
|
+
const hash = createHash("sha256").update(content).digest("hex");
|
|
92
|
+
return hash.slice(0, 8);
|
|
93
|
+
} catch {
|
|
94
|
+
return "unknown";
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Gets the modification time of a file.
|
|
99
|
+
* Used to detect stale cache entries.
|
|
100
|
+
*
|
|
101
|
+
* @param filePath The absolute path to the file.
|
|
102
|
+
* @returns The modification time in milliseconds, or 0 if file doesn't exist.
|
|
103
|
+
*/
|
|
104
|
+
function getFileMtime(filePath) {
|
|
105
|
+
try {
|
|
106
|
+
return statSync(filePath).mtimeMs;
|
|
107
|
+
} catch {
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
48
111
|
/**
|
|
49
112
|
* Resolves the cache directory path for compiled route modules.
|
|
50
|
-
*
|
|
113
|
+
* Cache key includes file hash to track changes.
|
|
51
114
|
* Cache is stored in node_modules/.jen/cache to leverage .gitignore and keep the workspace clean.
|
|
52
115
|
* Path names are flattened to avoid nested directory creation issues on Windows.
|
|
53
116
|
*
|
|
117
|
+
* Cache metadata file (.meta) stores:
|
|
118
|
+
* - Original file modification time
|
|
119
|
+
* - File content hash
|
|
120
|
+
* - Cache creation time
|
|
121
|
+
*
|
|
54
122
|
* @param filePath The absolute path to the original source file.
|
|
55
|
-
* @returns
|
|
123
|
+
* @returns Object with cache file path and metadata file path.
|
|
56
124
|
*/
|
|
57
125
|
function getCachePath(filePath) {
|
|
58
126
|
const cacheDir = join(process.cwd(), "node_modules", ".jen", "cache");
|
|
59
127
|
if (!existsSync(cacheDir)) {
|
|
60
128
|
mkdirSync(cacheDir, { recursive: true });
|
|
61
129
|
}
|
|
62
|
-
// Flatten path
|
|
130
|
+
// Flatten path and append file hash for uniqueness
|
|
63
131
|
const flatName = filePath.replace(/[\\/:]/g, "_").replace(/^_+/, "");
|
|
64
|
-
|
|
132
|
+
const fileHash = getFileHash(filePath);
|
|
133
|
+
const cacheName = `${flatName}_${fileHash}`;
|
|
134
|
+
const cachePath = join(cacheDir, cacheName + ".mjs");
|
|
135
|
+
const metaPath = join(cacheDir, cacheName + ".meta");
|
|
136
|
+
return { cachePath, metaPath };
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Checks if cache is still valid (file hasn't changed).
|
|
140
|
+
* Compares file hash and modification time from metadata.
|
|
141
|
+
*
|
|
142
|
+
* @param filePath The absolute path to the source file.
|
|
143
|
+
* @param metaPath The absolute path to the metadata file.
|
|
144
|
+
* @returns true if cache is valid, false if file changed or metadata missing.
|
|
145
|
+
*/
|
|
146
|
+
function isCacheValid(filePath, metaPath) {
|
|
147
|
+
try {
|
|
148
|
+
const meta = JSON.parse(readFileSync(metaPath, "utf-8"));
|
|
149
|
+
const currentHash = getFileHash(filePath);
|
|
150
|
+
const currentMtime = getFileMtime(filePath);
|
|
151
|
+
// Check if file hash or mtime changed
|
|
152
|
+
return meta.fileHash === currentHash && meta.fileMtime === currentMtime;
|
|
153
|
+
} catch {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Writes cache metadata to track file state.
|
|
159
|
+
* Stores file hash, mtime, and cache time for future validation.
|
|
160
|
+
*
|
|
161
|
+
* @param filePath The absolute path to the source file.
|
|
162
|
+
* @param metaPath The absolute path to the metadata file.
|
|
163
|
+
*/
|
|
164
|
+
function writeCacheMeta(filePath, metaPath) {
|
|
165
|
+
try {
|
|
166
|
+
const meta = {
|
|
167
|
+
filePath,
|
|
168
|
+
fileHash: getFileHash(filePath),
|
|
169
|
+
fileMtime: getFileMtime(filePath),
|
|
170
|
+
cacheTime: Date.now(),
|
|
171
|
+
};
|
|
172
|
+
writeFileSync(metaPath, JSON.stringify(meta, null, 2));
|
|
173
|
+
} catch (err) {
|
|
174
|
+
// Non-critical: log but don't fail transpilation
|
|
175
|
+
console.warn(`Failed to write cache metadata: ${metaPath}`, err);
|
|
176
|
+
}
|
|
65
177
|
}
|
|
66
178
|
/**
|
|
67
179
|
* Renders a route module to a complete HTML document.
|
|
@@ -92,6 +204,14 @@ function getCachePath(filePath) {
|
|
|
92
204
|
*/
|
|
93
205
|
export async function renderRouteToHtml(opts) {
|
|
94
206
|
const { config, route, url, params, query, headers, cookies } = opts;
|
|
207
|
+
// Scan and build layout hierarchy for this route
|
|
208
|
+
const layoutEntries = scanLayouts(config);
|
|
209
|
+
const applicableLayouts = buildLayoutHierarchy(
|
|
210
|
+
layoutEntries,
|
|
211
|
+
route.filePath,
|
|
212
|
+
config.siteDir,
|
|
213
|
+
);
|
|
214
|
+
const layoutStack = await resolveLayoutStack(applicableLayouts);
|
|
95
215
|
// Transpile route file if needed. TypeScript and JSX require compilation to JavaScript.
|
|
96
216
|
// Vue and Svelte components also need transpilation to Preact-compatible JavaScript.
|
|
97
217
|
let moduleUrl = route.filePath;
|
|
@@ -100,19 +220,43 @@ export async function renderRouteToHtml(opts) {
|
|
|
100
220
|
route.filePath.toLowerCase().endsWith(e),
|
|
101
221
|
);
|
|
102
222
|
if (requiresTranspile) {
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
223
|
+
const { cachePath, metaPath } = getCachePath(route.filePath);
|
|
224
|
+
// Check if valid cache exists (file hasn't changed)
|
|
225
|
+
if (existsSync(cachePath) && isCacheValid(route.filePath, metaPath)) {
|
|
226
|
+
moduleUrl = cachePath;
|
|
227
|
+
} else {
|
|
228
|
+
// Use deduplication map to prevent concurrent transpile races
|
|
229
|
+
const cacheKey = cachePath;
|
|
230
|
+
if (transpileInProgress.has(cacheKey)) {
|
|
231
|
+
// Another request is already transpiling this file, wait for it
|
|
232
|
+
moduleUrl = await transpileInProgress.get(cacheKey);
|
|
233
|
+
} else {
|
|
234
|
+
// Start transpilation and store promise for deduplication
|
|
235
|
+
const transpilePromise = (async () => {
|
|
236
|
+
try {
|
|
237
|
+
await esbuild.build({
|
|
238
|
+
entryPoints: [route.filePath],
|
|
239
|
+
outfile: cachePath,
|
|
240
|
+
format: "esm",
|
|
241
|
+
platform: "node", // Node platform for SSR supports built-ins like fs, path, etc.
|
|
242
|
+
target: "es2022",
|
|
243
|
+
bundle: true, // Bundle all local imports into a single file for simplicity.
|
|
244
|
+
external: ["preact", "preact-render-to-string", "jenjs"], // Keep framework imports external.
|
|
245
|
+
write: true,
|
|
246
|
+
plugins: [vueEsbuildPlugin(), svelteEsbuildPlugin()],
|
|
247
|
+
});
|
|
248
|
+
// Write metadata after successful transpilation
|
|
249
|
+
writeCacheMeta(route.filePath, metaPath);
|
|
250
|
+
return cachePath;
|
|
251
|
+
} finally {
|
|
252
|
+
// Remove from in-progress map when done
|
|
253
|
+
transpileInProgress.delete(cacheKey);
|
|
254
|
+
}
|
|
255
|
+
})();
|
|
256
|
+
transpileInProgress.set(cacheKey, transpilePromise);
|
|
257
|
+
moduleUrl = await transpilePromise;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
116
260
|
}
|
|
117
261
|
// Cache busting via query parameter ensures fresh module evaluation even if file is unchanged.
|
|
118
262
|
// This is critical because esbuild may use cached builds and we need the latest code for SSR.
|
|
@@ -181,8 +325,13 @@ export async function renderRouteToHtml(opts) {
|
|
|
181
325
|
const Page = mod.default;
|
|
182
326
|
// Check if hydration is disabled. Set to false for purely static pages with no client-side interactivity.
|
|
183
327
|
const shouldHydrate = mod.hydrate !== false;
|
|
184
|
-
|
|
185
|
-
|
|
328
|
+
// Render with layout hierarchy wrapping the page component
|
|
329
|
+
const app = renderWithLayoutStack(layoutStack, Page, {
|
|
330
|
+
data,
|
|
331
|
+
params,
|
|
332
|
+
query,
|
|
333
|
+
});
|
|
334
|
+
// Render the page component (with layouts) to a static HTML string.
|
|
186
335
|
// Preact rendering at this stage is purely static; hydration happens on the client.
|
|
187
336
|
let bodyHtml = renderToString(app);
|
|
188
337
|
// Check for island components that need selective hydration on the client.
|
|
@@ -202,18 +351,23 @@ export async function renderRouteToHtml(opts) {
|
|
|
202
351
|
bodyHtml = bodyHtml.replace("</div>", `${marker}</div>`);
|
|
203
352
|
}
|
|
204
353
|
}
|
|
205
|
-
// Collect all head elements from configuration and the route's Head component.
|
|
206
|
-
// Head components
|
|
354
|
+
// Collect all head elements from configuration, layout components, and the route's Head component.
|
|
355
|
+
// Head components are collected from root layout to page, allowing each layer to contribute meta tags.
|
|
207
356
|
const headParts = [];
|
|
208
357
|
headParts.push(...config.inject.head);
|
|
209
|
-
|
|
358
|
+
// Collect heads from layout stack
|
|
359
|
+
const layoutHeads = collectLayoutHeads(layoutStack, mod.Head, {
|
|
360
|
+
data,
|
|
361
|
+
params,
|
|
362
|
+
query,
|
|
363
|
+
});
|
|
364
|
+
for (const headNode of layoutHeads) {
|
|
210
365
|
try {
|
|
211
|
-
const headNode = h(mod.Head, { data, params, query });
|
|
212
366
|
const headHtml = renderToString(headNode);
|
|
213
367
|
headParts.push(headHtml);
|
|
214
368
|
} catch (err) {
|
|
215
369
|
console.error(
|
|
216
|
-
`Failed to render Head component
|
|
370
|
+
`Failed to render Head component:`,
|
|
217
371
|
err instanceof Error ? err.message : String(err),
|
|
218
372
|
);
|
|
219
373
|
}
|
|
@@ -232,13 +386,17 @@ ${headParts.join("\n")}
|
|
|
232
386
|
// Only inject hydration script if enabled for this route.
|
|
233
387
|
// Hydration-disabled routes are purely static and require no JavaScript.
|
|
234
388
|
if (shouldHydrate) {
|
|
235
|
-
//
|
|
236
|
-
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
389
|
+
// Validate data size before serialization to prevent DoS attacks.
|
|
390
|
+
const dataToSerialize = { data, params, query };
|
|
391
|
+
const frameworkData = recursivelyEscapeStrings(dataToSerialize);
|
|
392
|
+
const frameworkDataStr = JSON.stringify(frameworkData, null, 2);
|
|
393
|
+
// Check size of serialized data
|
|
394
|
+
if (frameworkDataStr.length > MAX_DATA_SIZE) {
|
|
395
|
+
throw new Error(
|
|
396
|
+
`Framework data exceeds maximum size of ${MAX_DATA_SIZE} bytes. ` +
|
|
397
|
+
`Current size: ${frameworkDataStr.length} bytes. This may indicate a DoS attempt or excessive data in loader/middleware.`,
|
|
398
|
+
);
|
|
399
|
+
}
|
|
242
400
|
const hydrateFile = `/__hydrate?file=${encodeURIComponent(route.filePath)}`;
|
|
243
401
|
html += `
|
|
244
402
|
<script id="__FRAMEWORK_DATA__" type="application/json">
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default security configuration (sensible defaults for production).
|
|
3
|
+
*/
|
|
4
|
+
export const DEFAULT_SECURITY_CONFIG = {
|
|
5
|
+
headers: {
|
|
6
|
+
csp: {
|
|
7
|
+
enabled: true,
|
|
8
|
+
directives: {
|
|
9
|
+
"default-src": ["'self'"],
|
|
10
|
+
"script-src": ["'self'", "'unsafe-inline'"],
|
|
11
|
+
"style-src": ["'self'", "'unsafe-inline'"],
|
|
12
|
+
"img-src": ["'self'", "data:", "https:"],
|
|
13
|
+
"font-src": ["'self'", "data:"],
|
|
14
|
+
"connect-src": ["'self'"],
|
|
15
|
+
},
|
|
16
|
+
reportOnly: false,
|
|
17
|
+
},
|
|
18
|
+
hsts: {
|
|
19
|
+
enabled: true,
|
|
20
|
+
maxAge: 31536000,
|
|
21
|
+
includeSubDomains: true,
|
|
22
|
+
preload: false,
|
|
23
|
+
},
|
|
24
|
+
cors: {
|
|
25
|
+
enabled: false,
|
|
26
|
+
origins: ["*"],
|
|
27
|
+
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
|
28
|
+
credentials: false,
|
|
29
|
+
},
|
|
30
|
+
frameOptions: {
|
|
31
|
+
enabled: true,
|
|
32
|
+
value: "SAMEORIGIN",
|
|
33
|
+
},
|
|
34
|
+
contentTypeOptions: true,
|
|
35
|
+
referrerPolicy: "strict-origin-when-cross-origin",
|
|
36
|
+
permissionsPolicy: {
|
|
37
|
+
enabled: true,
|
|
38
|
+
directives: {
|
|
39
|
+
geolocation: ["none"],
|
|
40
|
+
microphone: ["none"],
|
|
41
|
+
camera: ["none"],
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
csrf: {
|
|
46
|
+
enabled: true,
|
|
47
|
+
cookieName: "__jen_csrf",
|
|
48
|
+
headerName: "X-CSRF-Token",
|
|
49
|
+
},
|
|
50
|
+
validation: {
|
|
51
|
+
enabled: true,
|
|
52
|
+
maxBodySize: 10 * 1024 * 1024, // 10MB
|
|
53
|
+
},
|
|
54
|
+
rateLimit: {
|
|
55
|
+
enabled: false,
|
|
56
|
+
maxRequests: 100,
|
|
57
|
+
windowSeconds: 60,
|
|
58
|
+
skipPaths: ["/health"],
|
|
59
|
+
},
|
|
60
|
+
};
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { DEFAULT_SECURITY_CONFIG } from "./security-config.js";
|
|
2
|
+
/**
|
|
3
|
+
* Security headers middleware for Jen.js.
|
|
4
|
+
*
|
|
5
|
+
* Applies security HTTP headers to responses based on configuration.
|
|
6
|
+
* Includes CSP, HSTS, CORS, and other protection headers.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { securityHeadersMiddleware } from "jenjs";
|
|
11
|
+
*
|
|
12
|
+
* // Use in middleware pipeline
|
|
13
|
+
* app.use(securityHeadersMiddleware(securityConfig));
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export function securityHeadersMiddleware(config) {
|
|
17
|
+
const mergedConfig = {
|
|
18
|
+
...DEFAULT_SECURITY_CONFIG,
|
|
19
|
+
...config,
|
|
20
|
+
headers: {
|
|
21
|
+
...DEFAULT_SECURITY_CONFIG.headers,
|
|
22
|
+
...config?.headers,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
return async (context) => {
|
|
26
|
+
const headers = mergedConfig.headers || {};
|
|
27
|
+
// Content Security Policy
|
|
28
|
+
if (headers.csp?.enabled) {
|
|
29
|
+
const cspValue = buildCSPHeader(headers.csp);
|
|
30
|
+
const headerName = headers.csp.reportOnly
|
|
31
|
+
? "Content-Security-Policy-Report-Only"
|
|
32
|
+
: "Content-Security-Policy";
|
|
33
|
+
context.res.setHeader(headerName, cspValue);
|
|
34
|
+
}
|
|
35
|
+
// HTTP Strict Transport Security
|
|
36
|
+
if (headers.hsts?.enabled) {
|
|
37
|
+
const hstsValue = buildHSTSHeader(headers.hsts);
|
|
38
|
+
context.res.setHeader("Strict-Transport-Security", hstsValue);
|
|
39
|
+
}
|
|
40
|
+
// X-Frame-Options
|
|
41
|
+
if (headers.frameOptions?.enabled) {
|
|
42
|
+
const frameValue =
|
|
43
|
+
headers.frameOptions.value === "ALLOW-FROM"
|
|
44
|
+
? `${headers.frameOptions.value} ${headers.frameOptions.uri}`
|
|
45
|
+
: headers.frameOptions.value || "SAMEORIGIN";
|
|
46
|
+
context.res.setHeader("X-Frame-Options", frameValue);
|
|
47
|
+
}
|
|
48
|
+
// X-Content-Type-Options
|
|
49
|
+
if (headers.contentTypeOptions) {
|
|
50
|
+
context.res.setHeader("X-Content-Type-Options", "nosniff");
|
|
51
|
+
}
|
|
52
|
+
// Referrer-Policy
|
|
53
|
+
if (headers.referrerPolicy) {
|
|
54
|
+
context.res.setHeader("Referrer-Policy", headers.referrerPolicy);
|
|
55
|
+
}
|
|
56
|
+
// Permissions-Policy
|
|
57
|
+
if (headers.permissionsPolicy?.enabled) {
|
|
58
|
+
const policyValue = buildPermissionsPolicyHeader(
|
|
59
|
+
headers.permissionsPolicy,
|
|
60
|
+
);
|
|
61
|
+
if (policyValue) {
|
|
62
|
+
context.res.setHeader("Permissions-Policy", policyValue);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// CORS Headers
|
|
66
|
+
if (headers.cors?.enabled) {
|
|
67
|
+
applyCORSHeaders(context, headers.cors);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Build Content Security Policy header value.
|
|
73
|
+
*/
|
|
74
|
+
function buildCSPHeader(config) {
|
|
75
|
+
if (!config.directives) {
|
|
76
|
+
return "";
|
|
77
|
+
}
|
|
78
|
+
return Object.entries(config.directives)
|
|
79
|
+
.map(([key, values]) => `${key} ${values.join(" ")}`)
|
|
80
|
+
.join("; ");
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Build Strict-Transport-Security header value.
|
|
84
|
+
*/
|
|
85
|
+
function buildHSTSHeader(config) {
|
|
86
|
+
const parts = [];
|
|
87
|
+
if (config.maxAge) {
|
|
88
|
+
parts.push(`max-age=${config.maxAge}`);
|
|
89
|
+
}
|
|
90
|
+
if (config.includeSubDomains) {
|
|
91
|
+
parts.push("includeSubDomains");
|
|
92
|
+
}
|
|
93
|
+
if (config.preload) {
|
|
94
|
+
parts.push("preload");
|
|
95
|
+
}
|
|
96
|
+
return parts.join("; ");
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Build Permissions-Policy header value.
|
|
100
|
+
*/
|
|
101
|
+
function buildPermissionsPolicyHeader(config) {
|
|
102
|
+
if (!config.directives) {
|
|
103
|
+
return "";
|
|
104
|
+
}
|
|
105
|
+
return Object.entries(config.directives)
|
|
106
|
+
.map(([key, value]) => {
|
|
107
|
+
if (typeof value === "string") {
|
|
108
|
+
return `${key}=(${value})`;
|
|
109
|
+
}
|
|
110
|
+
return `${key}=(${value.join(" ")})`;
|
|
111
|
+
})
|
|
112
|
+
.join(", ");
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Apply CORS headers to response.
|
|
116
|
+
*/
|
|
117
|
+
function applyCORSHeaders(context, config) {
|
|
118
|
+
const origin = context.req.headers.origin || "";
|
|
119
|
+
const allowedOrigins = Array.isArray(config.origins)
|
|
120
|
+
? config.origins
|
|
121
|
+
: [config.origins || "*"];
|
|
122
|
+
if (allowedOrigins.includes("*") || allowedOrigins.includes(origin)) {
|
|
123
|
+
context.res.setHeader("Access-Control-Allow-Origin", origin || "*");
|
|
124
|
+
}
|
|
125
|
+
if (config.credentials) {
|
|
126
|
+
context.res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
127
|
+
}
|
|
128
|
+
if (config.methods) {
|
|
129
|
+
context.res.setHeader(
|
|
130
|
+
"Access-Control-Allow-Methods",
|
|
131
|
+
config.methods.join(", "),
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
if (config.allowedHeaders) {
|
|
135
|
+
context.res.setHeader(
|
|
136
|
+
"Access-Control-Allow-Headers",
|
|
137
|
+
config.allowedHeaders.join(", "),
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
if (config.exposedHeaders) {
|
|
141
|
+
context.res.setHeader(
|
|
142
|
+
"Access-Control-Expose-Headers",
|
|
143
|
+
config.exposedHeaders.join(", "),
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
if (config.maxAge) {
|
|
147
|
+
context.res.setHeader("Access-Control-Max-Age", config.maxAge.toString());
|
|
148
|
+
}
|
|
149
|
+
// Handle preflight requests
|
|
150
|
+
if (context.req.method === "OPTIONS") {
|
|
151
|
+
context.res.writeHead(204);
|
|
152
|
+
context.res.end();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* CSRF token generation and validation middleware.
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```typescript
|
|
160
|
+
* const csrfMiddleware = createCSRFMiddleware();
|
|
161
|
+
* app.use(csrfMiddleware);
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
export function createCSRFMiddleware(config) {
|
|
165
|
+
const cookieName = config?.cookieName || "__jen_csrf";
|
|
166
|
+
const headerName = config?.headerName || "X-CSRF-Token";
|
|
167
|
+
return async (context) => {
|
|
168
|
+
// Generate CSRF token if not present
|
|
169
|
+
const existingToken = context.req.headers.cookie
|
|
170
|
+
?.split(";")
|
|
171
|
+
.find((c) => c.trim().startsWith(`${cookieName}=`));
|
|
172
|
+
if (!existingToken) {
|
|
173
|
+
const token = generateCSRFToken();
|
|
174
|
+
context.res.setHeader(
|
|
175
|
+
"Set-Cookie",
|
|
176
|
+
`${cookieName}=${token}; HttpOnly; Path=/; SameSite=Strict`,
|
|
177
|
+
);
|
|
178
|
+
context.req.csrfToken = token;
|
|
179
|
+
}
|
|
180
|
+
// Validate CSRF token on state-changing requests
|
|
181
|
+
if (["POST", "PUT", "DELETE", "PATCH"].includes(context.req.method || "")) {
|
|
182
|
+
const tokenFromHeader = context.req.headers[headerName.toLowerCase()];
|
|
183
|
+
const tokenFromCookie = getCookieValue(
|
|
184
|
+
context.req.headers.cookie,
|
|
185
|
+
cookieName,
|
|
186
|
+
);
|
|
187
|
+
if (
|
|
188
|
+
tokenFromHeader &&
|
|
189
|
+
tokenFromCookie &&
|
|
190
|
+
tokenFromHeader === tokenFromCookie
|
|
191
|
+
) {
|
|
192
|
+
// Token is valid, continue
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
// Token is missing or invalid
|
|
196
|
+
context.res.writeHead(403, { "Content-Type": "application/json" });
|
|
197
|
+
context.res.end(
|
|
198
|
+
JSON.stringify({ error: "CSRF token validation failed" }),
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Generate a random CSRF token.
|
|
205
|
+
*/
|
|
206
|
+
function generateCSRFToken() {
|
|
207
|
+
const array = new Uint8Array(32);
|
|
208
|
+
if (typeof globalThis !== "undefined" && globalThis.crypto?.getRandomValues) {
|
|
209
|
+
globalThis.crypto.getRandomValues(array);
|
|
210
|
+
} else {
|
|
211
|
+
// Fallback for environments without crypto
|
|
212
|
+
for (let i = 0; i < array.length; i++) {
|
|
213
|
+
array[i] = Math.floor(Math.random() * 256);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join(
|
|
217
|
+
"",
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Extract cookie value by name.
|
|
222
|
+
*/
|
|
223
|
+
function getCookieValue(cookieHeader, name) {
|
|
224
|
+
if (!cookieHeader) return undefined;
|
|
225
|
+
const cookie = cookieHeader
|
|
226
|
+
.split(";")
|
|
227
|
+
.find((c) => c.trim().startsWith(`${name}=`));
|
|
228
|
+
return cookie ? cookie.trim().substring(name.length + 1) : undefined;
|
|
229
|
+
}
|