@jasonshimmy/vite-plugin-cer-app 0.23.2 → 0.24.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/dist/cli/checks/lighthouse.d.ts +26 -0
- package/dist/cli/checks/lighthouse.d.ts.map +1 -0
- package/dist/cli/checks/lighthouse.js +121 -0
- package/dist/cli/checks/lighthouse.js.map +1 -0
- package/dist/cli/checks/links.d.ts +12 -0
- package/dist/cli/checks/links.d.ts.map +1 -0
- package/dist/cli/checks/links.js +89 -0
- package/dist/cli/checks/links.js.map +1 -0
- package/dist/cli/checks/performance.d.ts +17 -0
- package/dist/cli/checks/performance.d.ts.map +1 -0
- package/dist/cli/checks/performance.js +61 -0
- package/dist/cli/checks/performance.js.map +1 -0
- package/dist/cli/commands/check.d.ts +3 -0
- package/dist/cli/commands/check.d.ts.map +1 -0
- package/dist/cli/commands/check.js +85 -0
- package/dist/cli/commands/check.js.map +1 -0
- package/dist/cli/create/index.d.ts +11 -1
- package/dist/cli/create/index.d.ts.map +1 -1
- package/dist/cli/create/index.js +135 -65
- package/dist/cli/create/index.js.map +1 -1
- package/dist/cli/create/templates/spa/package.json.tpl +5 -3
- package/dist/cli/create/templates/ssg/cer.config.ts.tpl +2 -0
- package/dist/cli/create/templates/ssg/package.json.tpl +5 -3
- package/dist/cli/create/templates/ssr/package.json.tpl +5 -3
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/plugin/build-ssg.d.ts +22 -6
- package/dist/plugin/build-ssg.d.ts.map +1 -1
- package/dist/plugin/build-ssg.js +227 -17
- package/dist/plugin/build-ssg.js.map +1 -1
- package/dist/plugin/content/parser.d.ts.map +1 -1
- package/dist/plugin/content/parser.js +54 -10
- package/dist/plugin/content/parser.js.map +1 -1
- package/dist/plugin/dev-server.d.ts +4 -1
- package/dist/plugin/dev-server.d.ts.map +1 -1
- package/dist/plugin/dev-server.js.map +1 -1
- package/dist/plugin/dts-generator.d.ts.map +1 -1
- package/dist/plugin/dts-generator.js +50 -8
- package/dist/plugin/dts-generator.js.map +1 -1
- package/dist/plugin/generated-dir.d.ts +2 -0
- package/dist/plugin/generated-dir.d.ts.map +1 -1
- package/dist/plugin/generated-dir.js +17 -6
- package/dist/plugin/generated-dir.js.map +1 -1
- package/dist/plugin/html-post-process.d.ts +6 -0
- package/dist/plugin/html-post-process.d.ts.map +1 -1
- package/dist/plugin/html-post-process.js +24 -0
- package/dist/plugin/html-post-process.js.map +1 -1
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +60 -13
- package/dist/plugin/index.js.map +1 -1
- package/dist/plugin/transforms/auto-import.d.ts.map +1 -1
- package/dist/plugin/transforms/auto-import.js +7 -0
- package/dist/plugin/transforms/auto-import.js.map +1 -1
- package/dist/plugin/transforms/prune-runtime-css.d.ts +8 -0
- package/dist/plugin/transforms/prune-runtime-css.d.ts.map +1 -0
- package/dist/plugin/transforms/prune-runtime-css.js +40 -0
- package/dist/plugin/transforms/prune-runtime-css.js.map +1 -0
- package/dist/runtime/app-template.d.ts +1 -1
- package/dist/runtime/app-template.d.ts.map +1 -1
- package/dist/runtime/app-template.js +206 -36
- package/dist/runtime/app-template.js.map +1 -1
- package/dist/runtime/composables/index.d.ts +4 -0
- package/dist/runtime/composables/index.d.ts.map +1 -1
- package/dist/runtime/composables/index.js +2 -0
- package/dist/runtime/composables/index.js.map +1 -1
- package/dist/runtime/composables/use-active-headings.d.ts +24 -0
- package/dist/runtime/composables/use-active-headings.d.ts.map +1 -0
- package/dist/runtime/composables/use-active-headings.js +112 -0
- package/dist/runtime/composables/use-active-headings.js.map +1 -0
- package/dist/runtime/composables/use-content-page.d.ts +45 -0
- package/dist/runtime/composables/use-content-page.d.ts.map +1 -0
- package/dist/runtime/composables/use-content-page.js +179 -0
- package/dist/runtime/composables/use-content-page.js.map +1 -0
- package/dist/runtime/composables/use-content-search.d.ts.map +1 -1
- package/dist/runtime/composables/use-content-search.js +1 -5
- package/dist/runtime/composables/use-content-search.js.map +1 -1
- package/dist/runtime/composables/use-fetch.d.ts +2 -1
- package/dist/runtime/composables/use-fetch.d.ts.map +1 -1
- package/dist/runtime/composables/use-fetch.js +112 -43
- package/dist/runtime/composables/use-fetch.js.map +1 -1
- package/dist/runtime/composables/use-route.d.ts.map +1 -1
- package/dist/runtime/composables/use-route.js +39 -11
- package/dist/runtime/composables/use-route.js.map +1 -1
- package/dist/runtime/content/client.js +2 -2
- package/dist/runtime/content/client.js.map +1 -1
- package/dist/runtime/entry-server-template.d.ts +16 -1
- package/dist/runtime/entry-server-template.d.ts.map +1 -1
- package/dist/runtime/entry-server-template.js +120 -13
- package/dist/runtime/entry-server-template.js.map +1 -1
- package/dist/types/config.d.ts +45 -4
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js.map +1 -1
- package/dist/types/middleware.d.ts +2 -2
- package/dist/types/middleware.d.ts.map +1 -1
- package/package.json +22 -19
- package/.github/copilot-instructions.md +0 -169
- package/.github/workflows/publish.yml +0 -257
- package/.nvmrc +0 -1
- package/CHANGELOG.md +0 -236
- package/CLAUDE.md +0 -113
- package/commits.txt +0 -1
- package/cypress.config.ts +0 -16
- package/docs/authentication.md +0 -285
- package/docs/cli.md +0 -324
- package/docs/components.md +0 -237
- package/docs/composables.md +0 -907
- package/docs/configuration.md +0 -696
- package/docs/content.md +0 -570
- package/docs/data-loading.md +0 -336
- package/docs/getting-started.md +0 -237
- package/docs/head-management.md +0 -212
- package/docs/i18n.md +0 -159
- package/docs/layouts.md +0 -224
- package/docs/middleware.md +0 -234
- package/docs/plugins.md +0 -170
- package/docs/rendering-modes.md +0 -377
- package/docs/routing.md +0 -485
- package/docs/server-api.md +0 -253
- package/docs/testing.md +0 -455
- package/docs/use-fetch.md +0 -183
- package/e2e/cypress/e2e/api.cy.ts +0 -123
- package/e2e/cypress/e2e/async-component.cy.ts +0 -27
- package/e2e/cypress/e2e/component-splitting.cy.ts +0 -50
- package/e2e/cypress/e2e/content.cy.ts +0 -425
- package/e2e/cypress/e2e/cookie.cy.ts +0 -85
- package/e2e/cypress/e2e/data.cy.ts +0 -158
- package/e2e/cypress/e2e/error-boundary.cy.ts +0 -88
- package/e2e/cypress/e2e/fouc.cy.ts +0 -254
- package/e2e/cypress/e2e/group-meta.cy.ts +0 -36
- package/e2e/cypress/e2e/head.cy.ts +0 -89
- package/e2e/cypress/e2e/i18n.cy.ts +0 -79
- package/e2e/cypress/e2e/interactive.cy.ts +0 -137
- package/e2e/cypress/e2e/isr-nested-runtime.cy.ts +0 -102
- package/e2e/cypress/e2e/jit-css-custom-colors.cy.ts +0 -43
- package/e2e/cypress/e2e/middleware-chain.cy.ts +0 -89
- package/e2e/cypress/e2e/middleware-status.cy.ts +0 -30
- package/e2e/cypress/e2e/middleware.cy.ts +0 -45
- package/e2e/cypress/e2e/navigate.cy.ts +0 -39
- package/e2e/cypress/e2e/observability.cy.ts +0 -77
- package/e2e/cypress/e2e/per-route-error.cy.ts +0 -42
- package/e2e/cypress/e2e/per-route-render.cy.ts +0 -70
- package/e2e/cypress/e2e/preview-hardening.cy.ts +0 -88
- package/e2e/cypress/e2e/routes.cy.ts +0 -128
- package/e2e/cypress/e2e/seo-meta.cy.ts +0 -108
- package/e2e/cypress/e2e/server-middleware.cy.ts +0 -46
- package/e2e/cypress/e2e/session-rotation.cy.ts +0 -25
- package/e2e/cypress/e2e/session.cy.ts +0 -73
- package/e2e/cypress/e2e/ssr-render-error.cy.ts +0 -68
- package/e2e/cypress/e2e/synthetic-404.cy.ts +0 -45
- package/e2e/cypress/e2e/use-page-data.cy.ts +0 -122
- package/e2e/cypress/e2e/use-state.cy.ts +0 -138
- package/e2e/cypress/support/commands.ts +0 -120
- package/e2e/cypress/support/e2e.ts +0 -10
- package/e2e/cypress/tsconfig.json +0 -14
- package/e2e/kitchen-sink/app/components/ks-badge.ts +0 -8
- package/e2e/kitchen-sink/app/composables/useKsCounter.ts +0 -9
- package/e2e/kitchen-sink/app/error.ts +0 -18
- package/e2e/kitchen-sink/app/layouts/admin.ts +0 -13
- package/e2e/kitchen-sink/app/layouts/default.ts +0 -25
- package/e2e/kitchen-sink/app/layouts/group.ts +0 -9
- package/e2e/kitchen-sink/app/layouts/minimal.ts +0 -7
- package/e2e/kitchen-sink/app/loading.ts +0 -9
- package/e2e/kitchen-sink/app/middleware/auth.ts +0 -9
- package/e2e/kitchen-sink/app/middleware/custom-log.ts +0 -4
- package/e2e/kitchen-sink/app/middleware/group-auth.ts +0 -7
- package/e2e/kitchen-sink/app/middleware/logger.ts +0 -14
- package/e2e/kitchen-sink/app/pages/(auth)/login.ts +0 -13
- package/e2e/kitchen-sink/app/pages/(auth)/protected.ts +0 -16
- package/e2e/kitchen-sink/app/pages/404.ts +0 -9
- package/e2e/kitchen-sink/app/pages/about.ts +0 -17
- package/e2e/kitchen-sink/app/pages/admin/_layout.ts +0 -1
- package/e2e/kitchen-sink/app/pages/admin/dashboard.ts +0 -11
- package/e2e/kitchen-sink/app/pages/async-component-error-test.ts +0 -18
- package/e2e/kitchen-sink/app/pages/async-component-test.ts +0 -18
- package/e2e/kitchen-sink/app/pages/blog/[slug].ts +0 -59
- package/e2e/kitchen-sink/app/pages/blog/index.ts +0 -51
- package/e2e/kitchen-sink/app/pages/content-blog.ts +0 -37
- package/e2e/kitchen-sink/app/pages/content-doc.ts +0 -42
- package/e2e/kitchen-sink/app/pages/content-fallback.ts +0 -36
- package/e2e/kitchen-sink/app/pages/content-guides.ts +0 -35
- package/e2e/kitchen-sink/app/pages/content-index.ts +0 -39
- package/e2e/kitchen-sink/app/pages/content-search.ts +0 -36
- package/e2e/kitchen-sink/app/pages/cookie-test.ts +0 -22
- package/e2e/kitchen-sink/app/pages/counter.ts +0 -17
- package/e2e/kitchen-sink/app/pages/group-auth-blocked.ts +0 -9
- package/e2e/kitchen-sink/app/pages/group-meta-test/_layout.ts +0 -6
- package/e2e/kitchen-sink/app/pages/group-meta-test/custom-mw.ts +0 -14
- package/e2e/kitchen-sink/app/pages/group-meta-test/page.ts +0 -9
- package/e2e/kitchen-sink/app/pages/group-meta-test/protected.ts +0 -8
- package/e2e/kitchen-sink/app/pages/head.ts +0 -20
- package/e2e/kitchen-sink/app/pages/i18n-test.ts +0 -14
- package/e2e/kitchen-sink/app/pages/index.ts +0 -27
- package/e2e/kitchen-sink/app/pages/isr-test.ts +0 -17
- package/e2e/kitchen-sink/app/pages/items/[id].ts +0 -20
- package/e2e/kitchen-sink/app/pages/jit-css-custom-colors-test.ts +0 -14
- package/e2e/kitchen-sink/app/pages/loader-error-test.ts +0 -13
- package/e2e/kitchen-sink/app/pages/loader-props-test.ts +0 -19
- package/e2e/kitchen-sink/app/pages/loader-response-error-test.ts +0 -13
- package/e2e/kitchen-sink/app/pages/middleware-chain-test.ts +0 -12
- package/e2e/kitchen-sink/app/pages/middleware-status-test.ts +0 -9
- package/e2e/kitchen-sink/app/pages/navigate-test.ts +0 -16
- package/e2e/kitchen-sink/app/pages/observability-test.ts +0 -25
- package/e2e/kitchen-sink/app/pages/per-route-error-test.error.ts +0 -13
- package/e2e/kitchen-sink/app/pages/per-route-error-test.ts +0 -15
- package/e2e/kitchen-sink/app/pages/plugin-dsd-test.ts +0 -10
- package/e2e/kitchen-sink/app/pages/render-error-test.ts +0 -12
- package/e2e/kitchen-sink/app/pages/render-server-test.ts +0 -12
- package/e2e/kitchen-sink/app/pages/render-spa-test.ts +0 -12
- package/e2e/kitchen-sink/app/pages/route-info.ts +0 -14
- package/e2e/kitchen-sink/app/pages/seo-test.ts +0 -23
- package/e2e/kitchen-sink/app/pages/session-rotation-test.ts +0 -25
- package/e2e/kitchen-sink/app/pages/use-state-test.ts +0 -37
- package/e2e/kitchen-sink/app/plugins/01.setup.ts +0 -7
- package/e2e/kitchen-sink/app/plugins/02.ks-plugin-components.ts +0 -27
- package/e2e/kitchen-sink/cer-auto-imports.d.ts +0 -51
- package/e2e/kitchen-sink/cer-env.d.ts +0 -35
- package/e2e/kitchen-sink/cer-tsconfig.json +0 -30
- package/e2e/kitchen-sink/cer.config.ts +0 -43
- package/e2e/kitchen-sink/content/01.guides/01.index.md +0 -8
- package/e2e/kitchen-sink/content/01.guides/02.intro.md +0 -8
- package/e2e/kitchen-sink/content/01.guides/10.advanced.md +0 -8
- package/e2e/kitchen-sink/content/blog/2026-04-01-hello.md +0 -26
- package/e2e/kitchen-sink/content/blog/2026-04-02-draft.md +0 -10
- package/e2e/kitchen-sink/content/blog/index.md +0 -8
- package/e2e/kitchen-sink/content/blog/no-frontmatter.md +0 -7
- package/e2e/kitchen-sink/content/docs/getting-started.md +0 -48
- package/e2e/kitchen-sink/content/index.md +0 -16
- package/e2e/kitchen-sink/server/api/echo.ts +0 -12
- package/e2e/kitchen-sink/server/api/health.ts +0 -3
- package/e2e/kitchen-sink/server/api/posts/[slug].ts +0 -11
- package/e2e/kitchen-sink/server/api/posts/index.ts +0 -5
- package/e2e/kitchen-sink/server/api/session.ts +0 -23
- package/e2e/kitchen-sink/server/data/posts.ts +0 -21
- package/e2e/kitchen-sink/server/middleware/01-headers.ts +0 -6
- package/e2e/kitchen-sink/server/middleware/02-status-test.ts +0 -10
- package/e2e/kitchen-sink/tsconfig.json +0 -3
- package/e2e/scripts/clean.mjs +0 -14
- package/eslint.config.ts +0 -22
- package/src/__tests__/cli/adapters/cloudflare-worker.integration.test.ts +0 -132
- package/src/__tests__/cli/adapters/cloudflare.test.ts +0 -332
- package/src/__tests__/cli/adapters/netlify-bridge.integration.test.ts +0 -139
- package/src/__tests__/cli/adapters/netlify.test.ts +0 -286
- package/src/__tests__/cli/adapters/vercel-launcher.integration.test.ts +0 -129
- package/src/__tests__/cli/adapters/vercel.test.ts +0 -263
- package/src/__tests__/cli/preview-hardening.test.ts +0 -265
- package/src/__tests__/cli/preview-isr.test.ts +0 -320
- package/src/__tests__/index.test.ts +0 -21
- package/src/__tests__/plugin/app-template.test.ts +0 -166
- package/src/__tests__/plugin/build-ssg-render.test.ts +0 -155
- package/src/__tests__/plugin/build-ssg.test.ts +0 -598
- package/src/__tests__/plugin/build-ssr.test.ts +0 -192
- package/src/__tests__/plugin/cer-app-plugin.test.ts +0 -552
- package/src/__tests__/plugin/content/emitter.test.ts +0 -117
- package/src/__tests__/plugin/content/loader.test.ts +0 -154
- package/src/__tests__/plugin/content/parser.test.ts +0 -381
- package/src/__tests__/plugin/content/path-utils.test.ts +0 -69
- package/src/__tests__/plugin/content/search.test.ts +0 -119
- package/src/__tests__/plugin/dev-server.test.ts +0 -813
- package/src/__tests__/plugin/dts-generator.test.ts +0 -394
- package/src/__tests__/plugin/entry-server-template.test.ts +0 -385
- package/src/__tests__/plugin/generated-dir.test.ts +0 -144
- package/src/__tests__/plugin/html-post-process.test.ts +0 -146
- package/src/__tests__/plugin/path-utils.test.ts +0 -411
- package/src/__tests__/plugin/resolve-config.test.ts +0 -233
- package/src/__tests__/plugin/scanner.test.ts +0 -186
- package/src/__tests__/plugin/transforms/auto-import.test.ts +0 -510
- package/src/__tests__/plugin/transforms/head-inject.test.ts +0 -178
- package/src/__tests__/plugin/virtual/composables.test.ts +0 -57
- package/src/__tests__/plugin/virtual/content-components.test.ts +0 -66
- package/src/__tests__/plugin/virtual/error.test.ts +0 -71
- package/src/__tests__/plugin/virtual/layouts.test.ts +0 -70
- package/src/__tests__/plugin/virtual/loading.test.ts +0 -72
- package/src/__tests__/plugin/virtual/middleware.test.ts +0 -83
- package/src/__tests__/plugin/virtual/plugins.test.ts +0 -129
- package/src/__tests__/plugin/virtual/routes.test.ts +0 -884
- package/src/__tests__/plugin/virtual/server-api.test.ts +0 -164
- package/src/__tests__/plugin/virtual/server-middleware.test.ts +0 -102
- package/src/__tests__/runtime/app-template.test.ts +0 -208
- package/src/__tests__/runtime/define-middleware.test.ts +0 -64
- package/src/__tests__/runtime/define-server-middleware.test.ts +0 -54
- package/src/__tests__/runtime/entry-client-template.test.ts +0 -61
- package/src/__tests__/runtime/isr-handler.test.ts +0 -338
- package/src/__tests__/runtime/oauth/handler.test.ts +0 -358
- package/src/__tests__/runtime/oauth/providers.test.ts +0 -47
- package/src/__tests__/runtime/oauth-index.test.ts +0 -43
- package/src/__tests__/runtime/use-auth.test.ts +0 -169
- package/src/__tests__/runtime/use-content-search-composable.test.ts +0 -405
- package/src/__tests__/runtime/use-content-search.test.ts +0 -163
- package/src/__tests__/runtime/use-content.test.ts +0 -226
- package/src/__tests__/runtime/use-cookie.test.ts +0 -277
- package/src/__tests__/runtime/use-fetch-component.test.ts +0 -214
- package/src/__tests__/runtime/use-fetch.test.ts +0 -377
- package/src/__tests__/runtime/use-head.test.ts +0 -311
- package/src/__tests__/runtime/use-inject-client.test.ts +0 -67
- package/src/__tests__/runtime/use-inject.test.ts +0 -66
- package/src/__tests__/runtime/use-locale.test.ts +0 -152
- package/src/__tests__/runtime/use-navigate.test.ts +0 -121
- package/src/__tests__/runtime/use-page-data.test.ts +0 -298
- package/src/__tests__/runtime/use-route.test.ts +0 -156
- package/src/__tests__/runtime/use-runtime-config.test.ts +0 -182
- package/src/__tests__/runtime/use-seo-meta.test.ts +0 -109
- package/src/__tests__/runtime/use-session.test.ts +0 -320
- package/src/__tests__/runtime/use-state.test.ts +0 -340
- package/src/__tests__/types/config.test.ts +0 -18
- package/src/cli/adapters/cloudflare.ts +0 -335
- package/src/cli/adapters/netlify.ts +0 -326
- package/src/cli/adapters/vercel.ts +0 -311
- package/src/cli/commands/adapt.ts +0 -84
- package/src/cli/commands/build.ts +0 -165
- package/src/cli/commands/dev.ts +0 -125
- package/src/cli/commands/generate.ts +0 -91
- package/src/cli/commands/preview-isr.ts +0 -172
- package/src/cli/commands/preview.ts +0 -523
- package/src/cli/create/index.ts +0 -275
- package/src/cli/create/templates/shared/.gitignore.tpl +0 -25
- package/src/cli/create/templates/shared/app/layouts/default.ts.tpl +0 -15
- package/src/cli/create/templates/shared/app/pages/index.ts.tpl +0 -8
- package/src/cli/create/templates/shared/index.html.tpl +0 -12
- package/src/cli/create/templates/shared/tsconfig.json.tpl +0 -6
- package/src/cli/create/templates/spa/cer.config.ts.tpl +0 -6
- package/src/cli/create/templates/spa/package.json.tpl +0 -18
- package/src/cli/create/templates/ssg/cer.config.ts.tpl +0 -10
- package/src/cli/create/templates/ssg/package.json.tpl +0 -19
- package/src/cli/create/templates/ssr/cer.config.ts.tpl +0 -6
- package/src/cli/create/templates/ssr/package.json.tpl +0 -18
- package/src/cli/index.ts +0 -22
- package/src/index.ts +0 -20
- package/src/plugin/build-ssg.ts +0 -403
- package/src/plugin/build-ssr.ts +0 -134
- package/src/plugin/content/emitter.ts +0 -50
- package/src/plugin/content/index.ts +0 -233
- package/src/plugin/content/parser.ts +0 -259
- package/src/plugin/content/path-utils.ts +0 -55
- package/src/plugin/content/scanner.ts +0 -26
- package/src/plugin/content/search.ts +0 -28
- package/src/plugin/dev-server.ts +0 -379
- package/src/plugin/dts-generator.ts +0 -273
- package/src/plugin/generated-dir.ts +0 -121
- package/src/plugin/html-post-process.ts +0 -96
- package/src/plugin/index.ts +0 -584
- package/src/plugin/path-utils.ts +0 -186
- package/src/plugin/scanner.ts +0 -65
- package/src/plugin/transforms/auto-import.ts +0 -206
- package/src/plugin/transforms/head-inject.ts +0 -161
- package/src/plugin/virtual/composables.ts +0 -28
- package/src/plugin/virtual/content-components.ts +0 -83
- package/src/plugin/virtual/error.ts +0 -34
- package/src/plugin/virtual/layouts.ts +0 -41
- package/src/plugin/virtual/loading.ts +0 -32
- package/src/plugin/virtual/middleware.ts +0 -45
- package/src/plugin/virtual/plugins.ts +0 -46
- package/src/plugin/virtual/routes.ts +0 -514
- package/src/plugin/virtual/server-api.ts +0 -85
- package/src/plugin/virtual/server-middleware.ts +0 -44
- package/src/runtime/app-template.ts +0 -377
- package/src/runtime/composables/define-middleware.ts +0 -17
- package/src/runtime/composables/define-server-middleware.ts +0 -18
- package/src/runtime/composables/index.ts +0 -27
- package/src/runtime/composables/use-auth.ts +0 -125
- package/src/runtime/composables/use-content-search.ts +0 -205
- package/src/runtime/composables/use-content.ts +0 -146
- package/src/runtime/composables/use-cookie.ts +0 -135
- package/src/runtime/composables/use-fetch.ts +0 -438
- package/src/runtime/composables/use-head.ts +0 -234
- package/src/runtime/composables/use-inject.ts +0 -49
- package/src/runtime/composables/use-locale.ts +0 -109
- package/src/runtime/composables/use-navigate.ts +0 -58
- package/src/runtime/composables/use-page-data.ts +0 -93
- package/src/runtime/composables/use-route.ts +0 -71
- package/src/runtime/composables/use-runtime-config.ts +0 -123
- package/src/runtime/composables/use-seo-meta.ts +0 -80
- package/src/runtime/composables/use-session.ts +0 -186
- package/src/runtime/composables/use-state.ts +0 -92
- package/src/runtime/content/client.ts +0 -168
- package/src/runtime/entry-client-template.ts +0 -39
- package/src/runtime/entry-server-template.ts +0 -493
- package/src/runtime/isr-handler.ts +0 -184
- package/src/runtime/oauth/handler.ts +0 -407
- package/src/runtime/oauth/index.ts +0 -49
- package/src/runtime/oauth/providers.ts +0 -38
- package/src/types/api.ts +0 -40
- package/src/types/config.ts +0 -379
- package/src/types/content.ts +0 -66
- package/src/types/index.ts +0 -7
- package/src/types/middleware.ts +0 -63
- package/src/types/page.ts +0 -117
- package/src/types/plugin.ts +0 -34
- package/tsconfig.build.json +0 -10
- package/tsconfig.json +0 -19
- package/vitest.config.ts +0 -33
|
@@ -1,523 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander'
|
|
2
|
-
import { createServer as createHttpServer, type IncomingMessage, type ServerResponse } from 'node:http'
|
|
3
|
-
import { createReadStream, existsSync, statSync } from 'node:fs'
|
|
4
|
-
import { createGzip } from 'node:zlib'
|
|
5
|
-
import { resolve, join, extname } from 'pathe'
|
|
6
|
-
import { pathToFileURL } from 'node:url'
|
|
7
|
-
import {
|
|
8
|
-
type IsrCacheEntry,
|
|
9
|
-
type SsrHandlerFn,
|
|
10
|
-
isPathBounded,
|
|
11
|
-
findRevalidate,
|
|
12
|
-
findRenderMode,
|
|
13
|
-
renderForIsr,
|
|
14
|
-
serveFromIsrCache,
|
|
15
|
-
} from './preview-isr.js'
|
|
16
|
-
|
|
17
|
-
// ─── API route matching & body parsing ───────────────────────────────────────
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Matches an API route pattern (e.g. '/api/items/:id') against a URL path.
|
|
21
|
-
* Returns a params object on match, or null if the pattern does not match.
|
|
22
|
-
*/
|
|
23
|
-
function matchApiPattern(pattern: string, urlPath: string): Record<string, string> | null {
|
|
24
|
-
const patternParts = pattern.split('/')
|
|
25
|
-
const urlParts = urlPath.split('/')
|
|
26
|
-
if (patternParts.length !== urlParts.length) return null
|
|
27
|
-
const params: Record<string, string> = {}
|
|
28
|
-
for (let i = 0; i < patternParts.length; i++) {
|
|
29
|
-
const p = patternParts[i]
|
|
30
|
-
const u = urlParts[i]
|
|
31
|
-
if (p.startsWith(':')) {
|
|
32
|
-
params[p.slice(1)] = decodeURIComponent(u)
|
|
33
|
-
} else if (p !== u) {
|
|
34
|
-
return null
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return params
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const MIME_TYPES: Record<string, string> = {
|
|
41
|
-
'.html': 'text/html; charset=utf-8',
|
|
42
|
-
'.js': 'application/javascript; charset=utf-8',
|
|
43
|
-
'.mjs': 'application/javascript; charset=utf-8',
|
|
44
|
-
'.css': 'text/css; charset=utf-8',
|
|
45
|
-
'.json': 'application/json; charset=utf-8',
|
|
46
|
-
'.png': 'image/png',
|
|
47
|
-
'.jpg': 'image/jpeg',
|
|
48
|
-
'.jpeg': 'image/jpeg',
|
|
49
|
-
'.gif': 'image/gif',
|
|
50
|
-
'.svg': 'image/svg+xml',
|
|
51
|
-
'.ico': 'image/x-icon',
|
|
52
|
-
'.woff': 'font/woff',
|
|
53
|
-
'.woff2': 'font/woff2',
|
|
54
|
-
'.ttf': 'font/ttf',
|
|
55
|
-
'.eot': 'application/vnd.ms-fontobject',
|
|
56
|
-
'.map': 'application/json',
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Reads the raw body from an IncomingMessage as a Buffer.
|
|
61
|
-
*/
|
|
62
|
-
function readBody(req: IncomingMessage): Promise<Buffer> {
|
|
63
|
-
return new Promise((resolve, reject) => {
|
|
64
|
-
const chunks: Buffer[] = []
|
|
65
|
-
req.on('data', (chunk: Buffer) => chunks.push(chunk))
|
|
66
|
-
req.on('end', () => resolve(Buffer.concat(chunks)))
|
|
67
|
-
req.on('error', reject)
|
|
68
|
-
})
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Parses URL query string into a plain object.
|
|
73
|
-
*/
|
|
74
|
-
function parseQuery(url: string): Record<string, string> {
|
|
75
|
-
const qIndex = url.indexOf('?')
|
|
76
|
-
if (qIndex === -1) return {}
|
|
77
|
-
const qs = url.slice(qIndex + 1)
|
|
78
|
-
const result: Record<string, string> = {}
|
|
79
|
-
for (const part of qs.split('&')) {
|
|
80
|
-
if (!part) continue
|
|
81
|
-
const eqIdx = part.indexOf('=')
|
|
82
|
-
if (eqIdx === -1) {
|
|
83
|
-
result[decodeURIComponent(part)] = ''
|
|
84
|
-
} else {
|
|
85
|
-
result[decodeURIComponent(part.slice(0, eqIdx))] = decodeURIComponent(part.slice(eqIdx + 1))
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return result
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Parses the request body for JSON content types.
|
|
93
|
-
* Returns the parsed object for application/json, raw Buffer for other
|
|
94
|
-
* bodies, or undefined for methods that carry no body.
|
|
95
|
-
*/
|
|
96
|
-
async function parseBody(req: IncomingMessage): Promise<unknown> {
|
|
97
|
-
const contentType = req.headers['content-type'] ?? ''
|
|
98
|
-
const method = req.method?.toUpperCase() ?? 'GET'
|
|
99
|
-
if (!['POST', 'PUT', 'PATCH'].includes(method)) return undefined
|
|
100
|
-
const buf = await readBody(req)
|
|
101
|
-
if (contentType.includes('application/json')) {
|
|
102
|
-
try { return JSON.parse(buf.toString('utf-8')) } catch { return undefined }
|
|
103
|
-
}
|
|
104
|
-
return buf
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function getMimeType(filePath: string): string {
|
|
108
|
-
const ext = extname(filePath).toLowerCase()
|
|
109
|
-
return MIME_TYPES[ext] ?? 'application/octet-stream'
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// MIME types that benefit from gzip compression. Binary formats (woff2, images)
|
|
113
|
-
// are already compressed and should not be re-compressed.
|
|
114
|
-
const GZIP_TYPES = new Set([
|
|
115
|
-
'text/html; charset=utf-8',
|
|
116
|
-
'application/javascript; charset=utf-8',
|
|
117
|
-
'text/css; charset=utf-8',
|
|
118
|
-
'application/json; charset=utf-8',
|
|
119
|
-
'image/svg+xml',
|
|
120
|
-
'application/json',
|
|
121
|
-
])
|
|
122
|
-
|
|
123
|
-
function acceptsGzip(req: IncomingMessage): boolean {
|
|
124
|
-
const ae = req.headers['accept-encoding'] ?? ''
|
|
125
|
-
return ae.includes('gzip')
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Returns the appropriate Cache-Control header value for a file.
|
|
130
|
-
* Vite content-hashes assets placed in the /assets/ directory, so they
|
|
131
|
-
* can be cached indefinitely. Everything else (HTML, etc.) must not be cached.
|
|
132
|
-
*/
|
|
133
|
-
function getCacheControl(filePath: string): string {
|
|
134
|
-
if (filePath.includes('/assets/')) return 'public, max-age=31536000, immutable'
|
|
135
|
-
return 'no-cache'
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Sets standard security headers on every response.
|
|
140
|
-
*/
|
|
141
|
-
function setSecurityHeaders(res: ServerResponse): void {
|
|
142
|
-
res.setHeader('X-Content-Type-Options', 'nosniff')
|
|
143
|
-
res.setHeader('X-Frame-Options', 'DENY')
|
|
144
|
-
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin')
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Serves a static file from the dist directory.
|
|
149
|
-
* Applies gzip compression for compressible text types when the client
|
|
150
|
-
* signals support via the Accept-Encoding request header.
|
|
151
|
-
* Returns true if the file was served, false otherwise.
|
|
152
|
-
*/
|
|
153
|
-
function serveStaticFile(
|
|
154
|
-
req: IncomingMessage,
|
|
155
|
-
res: ServerResponse,
|
|
156
|
-
distDir: string,
|
|
157
|
-
): boolean {
|
|
158
|
-
const urlPath = (req.url ?? '/').split('?')[0]
|
|
159
|
-
|
|
160
|
-
// Guard against path traversal: resolved path must stay within distDir.
|
|
161
|
-
if (!isPathBounded(distDir, urlPath)) {
|
|
162
|
-
res.statusCode = 400
|
|
163
|
-
res.end('Bad Request')
|
|
164
|
-
return true
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Try exact file path
|
|
168
|
-
let filePath = join(distDir, urlPath)
|
|
169
|
-
|
|
170
|
-
if (!existsSync(filePath) || statSync(filePath).isDirectory()) {
|
|
171
|
-
// Try index.html in the directory
|
|
172
|
-
const indexPath = join(distDir, urlPath, 'index.html')
|
|
173
|
-
if (existsSync(indexPath)) {
|
|
174
|
-
filePath = indexPath
|
|
175
|
-
} else if (existsSync(join(distDir, 'index.html'))) {
|
|
176
|
-
// SPA fallback: serve root index.html
|
|
177
|
-
filePath = join(distDir, 'index.html')
|
|
178
|
-
} else {
|
|
179
|
-
return false
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const mimeType = getMimeType(filePath)
|
|
184
|
-
res.setHeader('Content-Type', mimeType)
|
|
185
|
-
res.setHeader('Cache-Control', getCacheControl(filePath))
|
|
186
|
-
setSecurityHeaders(res)
|
|
187
|
-
|
|
188
|
-
const stream = createReadStream(filePath)
|
|
189
|
-
if (GZIP_TYPES.has(mimeType) && acceptsGzip(req)) {
|
|
190
|
-
res.setHeader('Content-Encoding', 'gzip')
|
|
191
|
-
stream.pipe(createGzip()).pipe(res)
|
|
192
|
-
} else {
|
|
193
|
-
stream.pipe(res)
|
|
194
|
-
}
|
|
195
|
-
return true
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Registers SIGTERM/SIGINT handlers that gracefully drain the server before exiting.
|
|
200
|
-
* Waits up to 10 seconds for in-flight requests to complete, then force-exits.
|
|
201
|
-
*/
|
|
202
|
-
function registerGracefulShutdown(server: ReturnType<typeof createHttpServer>): void {
|
|
203
|
-
function shutdown(signal: string): void {
|
|
204
|
-
console.log(`[cer-app] Received ${signal}, shutting down gracefully...`)
|
|
205
|
-
server.close(() => {
|
|
206
|
-
console.log('[cer-app] Server closed.')
|
|
207
|
-
process.exit(0)
|
|
208
|
-
})
|
|
209
|
-
// Force exit if connections haven't drained within 10 seconds.
|
|
210
|
-
const t = setTimeout(() => {
|
|
211
|
-
console.error('[cer-app] Forced shutdown after 10 s — connections did not drain.')
|
|
212
|
-
process.exit(1)
|
|
213
|
-
}, 10_000)
|
|
214
|
-
// Allow the Node.js event loop to exit before the timeout fires if all other work is done.
|
|
215
|
-
t.unref()
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
process.on('SIGTERM', () => shutdown('SIGTERM'))
|
|
219
|
-
process.on('SIGINT', () => shutdown('SIGINT'))
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
export function previewCommand(): Command {
|
|
223
|
-
return new Command('preview')
|
|
224
|
-
.description('Preview the production build')
|
|
225
|
-
.option('-p, --port <port>', 'Port to listen on', '4173')
|
|
226
|
-
.option('--host <host>', 'Host to bind to', 'localhost')
|
|
227
|
-
.option('--root <root>', 'Project root directory', process.cwd())
|
|
228
|
-
.option('--ssr', 'Serve using SSR handler from dist/server/server.js')
|
|
229
|
-
.action(async (options) => {
|
|
230
|
-
const root = resolve(options.root)
|
|
231
|
-
const port = parseInt(options.port, 10)
|
|
232
|
-
const distDir = join(root, 'dist')
|
|
233
|
-
const serverBundle = join(distDir, 'server/server.js')
|
|
234
|
-
|
|
235
|
-
// Check if SSR server bundle exists.
|
|
236
|
-
// An SSG build also produces a server bundle, but previewing SSG means serving
|
|
237
|
-
// the pre-rendered static HTML — detect the SSG manifest to avoid switching to
|
|
238
|
-
// live SSR mode for SSG builds.
|
|
239
|
-
const hasServerBundle = existsSync(serverBundle)
|
|
240
|
-
const hasSsgManifest = existsSync(join(distDir, 'ssg-manifest.json'))
|
|
241
|
-
const useSSR = options.ssr || (hasServerBundle && !hasSsgManifest)
|
|
242
|
-
|
|
243
|
-
if (useSSR && hasServerBundle) {
|
|
244
|
-
console.log('[cer-app] Starting SSR preview server...')
|
|
245
|
-
|
|
246
|
-
// Load the server bundle
|
|
247
|
-
let serverMod: {
|
|
248
|
-
handler?: SsrHandlerFn
|
|
249
|
-
default?: SsrHandlerFn
|
|
250
|
-
runServerMiddleware?: (req: IncomingMessage, res: ServerResponse) => Promise<boolean>
|
|
251
|
-
runWithRequestContext?: <T>(req: IncomingMessage, res: ServerResponse, fn: () => Promise<T>) => Promise<T>
|
|
252
|
-
apiRoutes?: Array<{ path: string; handlers: Record<string, unknown> }>
|
|
253
|
-
}
|
|
254
|
-
try {
|
|
255
|
-
// Expose the app root so the server bundle can resolve content files at runtime.
|
|
256
|
-
process.env.__CER_APP_ROOT__ = root
|
|
257
|
-
serverMod = await import(pathToFileURL(serverBundle).href)
|
|
258
|
-
} catch (err) {
|
|
259
|
-
console.error('[cer-app] Failed to load server bundle:', err)
|
|
260
|
-
process.exit(1)
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const handler = serverMod.handler ?? serverMod.default
|
|
264
|
-
if (typeof handler !== 'function') {
|
|
265
|
-
console.error('[cer-app] Server bundle does not export a handler function.')
|
|
266
|
-
process.exit(1)
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Server middleware chain exported by the server bundle
|
|
270
|
-
const runServerMiddleware = serverMod.runServerMiddleware
|
|
271
|
-
// Request context wrapper — runs fn inside _cerReqStore so useCookie / useSession work in API handlers
|
|
272
|
-
const runWithRequestContext = serverMod.runWithRequestContext
|
|
273
|
-
|
|
274
|
-
// API route array exported by the server bundle: [{ path, handlers }]
|
|
275
|
-
const apiRoutes: Array<{ path: string; handlers: Record<string, unknown> }> =
|
|
276
|
-
Array.isArray(serverMod.apiRoutes) ? serverMod.apiRoutes : []
|
|
277
|
-
|
|
278
|
-
// Page routes exported by the server bundle (used for ISR revalidate lookup).
|
|
279
|
-
const pageRoutes: Array<{ path: string; meta?: Record<string, unknown> }> =
|
|
280
|
-
Array.isArray((serverMod as { routes?: unknown }).routes)
|
|
281
|
-
? (serverMod as { routes: Array<{ path: string; meta?: Record<string, unknown> }> }).routes
|
|
282
|
-
: []
|
|
283
|
-
|
|
284
|
-
// ISR cache: path → cached render entry.
|
|
285
|
-
const isrCache = new Map<string, IsrCacheEntry>()
|
|
286
|
-
|
|
287
|
-
const server = createHttpServer(async (req: IncomingMessage, res: ServerResponse) => {
|
|
288
|
-
setSecurityHeaders(res)
|
|
289
|
-
// Default: HTML and API responses must not be cached. Asset paths below
|
|
290
|
-
// override this with getCacheControl() for content-hashed files.
|
|
291
|
-
res.setHeader('Cache-Control', 'no-cache')
|
|
292
|
-
|
|
293
|
-
const url = req.url ?? '/'
|
|
294
|
-
const urlPath = url.split('?')[0]
|
|
295
|
-
const method = req.method ?? 'GET'
|
|
296
|
-
|
|
297
|
-
// Run server middleware before routing — stops chain if it returns false
|
|
298
|
-
if (runServerMiddleware && !(await runServerMiddleware(req, res))) return
|
|
299
|
-
|
|
300
|
-
// Route /api/* requests to the server bundle's API handlers
|
|
301
|
-
if (urlPath.startsWith('/api/')) {
|
|
302
|
-
for (const route of apiRoutes) {
|
|
303
|
-
const matched = matchApiPattern(route.path, urlPath)
|
|
304
|
-
if (matched !== null) {
|
|
305
|
-
const augReq = req as IncomingMessage & { params: Record<string, string>; query: Record<string, string>; body: unknown }
|
|
306
|
-
augReq.params = matched
|
|
307
|
-
augReq.query = parseQuery(url)
|
|
308
|
-
augReq.body = await parseBody(req)
|
|
309
|
-
const augRes = res as ServerResponse & {
|
|
310
|
-
json(data: unknown): void
|
|
311
|
-
status(code: number): typeof augRes
|
|
312
|
-
}
|
|
313
|
-
augRes.json = function (data) {
|
|
314
|
-
this.setHeader('Content-Type', 'application/json; charset=utf-8')
|
|
315
|
-
this.end(JSON.stringify(data))
|
|
316
|
-
}
|
|
317
|
-
augRes.status = function (code) { this.statusCode = code; return this }
|
|
318
|
-
|
|
319
|
-
type ApiHandlerFn = (req: typeof augReq, res: typeof augRes) => void | Promise<void>
|
|
320
|
-
const handlerFn =
|
|
321
|
-
(route.handlers[method.toLowerCase()] as ApiHandlerFn | undefined) ??
|
|
322
|
-
(route.handlers[method.toUpperCase()] as ApiHandlerFn | undefined) ??
|
|
323
|
-
(route.handlers['default'] as ApiHandlerFn | undefined)
|
|
324
|
-
|
|
325
|
-
if (typeof handlerFn === 'function') {
|
|
326
|
-
try {
|
|
327
|
-
const invoke = () => Promise.resolve(handlerFn(augReq, augRes))
|
|
328
|
-
await (runWithRequestContext ? runWithRequestContext(req, res, invoke) : invoke())
|
|
329
|
-
} catch (err) {
|
|
330
|
-
console.error(`[cer-app] API handler error at ${route.path}:`, err)
|
|
331
|
-
res.statusCode = 500
|
|
332
|
-
res.setHeader('Content-Type', 'application/json')
|
|
333
|
-
res.end(JSON.stringify({ error: 'Internal Server Error' }))
|
|
334
|
-
}
|
|
335
|
-
return
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
res.statusCode = 404
|
|
340
|
-
res.end('Not Found')
|
|
341
|
-
return
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// Serve static assets from dist/client first
|
|
345
|
-
const clientDist = join(distDir, 'client')
|
|
346
|
-
if (existsSync(clientDist) && url !== '/' && url.includes('.')) {
|
|
347
|
-
const served = serveStaticFile(req, res, clientDist)
|
|
348
|
-
if (served) return
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// Per-route render strategy — checked before ISR.
|
|
352
|
-
const renderMode = findRenderMode(pageRoutes, urlPath)
|
|
353
|
-
|
|
354
|
-
// render: 'spa' — skip SSR, serve the client index.html shell.
|
|
355
|
-
if (renderMode === 'spa') {
|
|
356
|
-
const spaIndex = join(distDir, 'client/index.html')
|
|
357
|
-
if (existsSync(spaIndex)) {
|
|
358
|
-
res.setHeader('Content-Type', 'text/html; charset=utf-8')
|
|
359
|
-
res.setHeader('Cache-Control', 'no-cache')
|
|
360
|
-
createReadStream(spaIndex).pipe(res)
|
|
361
|
-
} else {
|
|
362
|
-
res.statusCode = 404
|
|
363
|
-
res.setHeader('Content-Type', 'text/plain')
|
|
364
|
-
res.end('Not Found')
|
|
365
|
-
}
|
|
366
|
-
return
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// render: 'static' — try pre-rendered HTML from dist/, fall through to SSR.
|
|
370
|
-
if (renderMode === 'static') {
|
|
371
|
-
const staticFile = urlPath === '/'
|
|
372
|
-
? join(distDir, 'index.html')
|
|
373
|
-
: join(distDir, urlPath.replace(/^\//, ''), 'index.html')
|
|
374
|
-
if (existsSync(staticFile)) {
|
|
375
|
-
res.setHeader('Content-Type', 'text/html; charset=utf-8')
|
|
376
|
-
res.setHeader('Cache-Control', 'no-cache')
|
|
377
|
-
createReadStream(staticFile).pipe(res)
|
|
378
|
-
return
|
|
379
|
-
}
|
|
380
|
-
// Fall through to SSR if no pre-rendered file exists.
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// render: 'server' — always SSR, bypass ISR cache.
|
|
384
|
-
if (renderMode === 'server') {
|
|
385
|
-
try {
|
|
386
|
-
await handler(req, res)
|
|
387
|
-
} catch (err) {
|
|
388
|
-
console.error('[cer-app] SSR handler error:', err)
|
|
389
|
-
res.statusCode = 500
|
|
390
|
-
res.setHeader('Content-Type', 'text/plain')
|
|
391
|
-
res.end('Internal Server Error')
|
|
392
|
-
}
|
|
393
|
-
return
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// ISR: check whether this route has a revalidate TTL.
|
|
397
|
-
const revalidate = findRevalidate(pageRoutes, urlPath)
|
|
398
|
-
if (revalidate !== null) {
|
|
399
|
-
const cached = isrCache.get(urlPath)
|
|
400
|
-
const now = Date.now()
|
|
401
|
-
|
|
402
|
-
if (cached) {
|
|
403
|
-
const ageSeconds = (now - cached.builtAt) / 1000
|
|
404
|
-
if (ageSeconds < cached.revalidate) {
|
|
405
|
-
// Fresh — serve from cache.
|
|
406
|
-
serveFromIsrCache(cached, res, 'HIT')
|
|
407
|
-
return
|
|
408
|
-
}
|
|
409
|
-
// Stale — serve stale immediately, revalidate in background.
|
|
410
|
-
if (!cached.revalidating) {
|
|
411
|
-
cached.revalidating = true
|
|
412
|
-
serveFromIsrCache(cached, res, 'STALE')
|
|
413
|
-
const revalidateTimeout = setTimeout(() => {
|
|
414
|
-
if (cached) cached.revalidating = false
|
|
415
|
-
}, 30_000)
|
|
416
|
-
renderForIsr(urlPath, handler, revalidate).then((entry) => {
|
|
417
|
-
clearTimeout(revalidateTimeout)
|
|
418
|
-
if (entry) isrCache.set(urlPath, entry)
|
|
419
|
-
else if (cached) cached.revalidating = false
|
|
420
|
-
}).catch(() => {
|
|
421
|
-
clearTimeout(revalidateTimeout)
|
|
422
|
-
if (cached) cached.revalidating = false
|
|
423
|
-
})
|
|
424
|
-
return
|
|
425
|
-
}
|
|
426
|
-
// Already revalidating — serve stale without spawning another render.
|
|
427
|
-
serveFromIsrCache(cached, res, 'STALE')
|
|
428
|
-
return
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// Cache miss — render, cache, then serve.
|
|
432
|
-
try {
|
|
433
|
-
const entry = await renderForIsr(urlPath, handler, revalidate)
|
|
434
|
-
if (entry) {
|
|
435
|
-
isrCache.set(urlPath, entry)
|
|
436
|
-
serveFromIsrCache(entry, res, 'HIT')
|
|
437
|
-
} else {
|
|
438
|
-
await handler(req, res)
|
|
439
|
-
}
|
|
440
|
-
} catch (err) {
|
|
441
|
-
console.error('[cer-app] ISR render error:', err)
|
|
442
|
-
res.statusCode = 500
|
|
443
|
-
res.end('Internal Server Error')
|
|
444
|
-
}
|
|
445
|
-
return
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// Non-ISR: fall through to SSR handler directly.
|
|
449
|
-
try {
|
|
450
|
-
await handler(req, res)
|
|
451
|
-
} catch (err) {
|
|
452
|
-
console.error('[cer-app] SSR handler error:', err)
|
|
453
|
-
res.statusCode = 500
|
|
454
|
-
res.end('Internal Server Error')
|
|
455
|
-
}
|
|
456
|
-
})
|
|
457
|
-
|
|
458
|
-
// Protect against slow-send attacks: abort requests that stall during
|
|
459
|
-
// header delivery or that take too long to complete.
|
|
460
|
-
server.headersTimeout = 10_000 // 10 s to receive all request headers
|
|
461
|
-
server.requestTimeout = 30_000 // 30 s for the full request/response cycle
|
|
462
|
-
|
|
463
|
-
server.listen(port, options.host, () => {
|
|
464
|
-
console.log(`[cer-app] SSR preview running at http://${options.host}:${port}`)
|
|
465
|
-
})
|
|
466
|
-
|
|
467
|
-
registerGracefulShutdown(server)
|
|
468
|
-
} else {
|
|
469
|
-
// Static file server (SPA / SSG)
|
|
470
|
-
console.log('[cer-app] Starting static preview server...')
|
|
471
|
-
|
|
472
|
-
if (!existsSync(distDir)) {
|
|
473
|
-
console.error(`[cer-app] No dist/ directory found at ${distDir}. Run 'cer-app build' first.`)
|
|
474
|
-
process.exit(1)
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
const server = createHttpServer((req: IncomingMessage, res: ServerResponse) => {
|
|
478
|
-
setSecurityHeaders(res)
|
|
479
|
-
res.setHeader('Cache-Control', 'no-cache')
|
|
480
|
-
|
|
481
|
-
const urlPath = (req.url ?? '/').split('?')[0]
|
|
482
|
-
// SSG builds put assets in dist/client/ while HTML lives in dist/.
|
|
483
|
-
// For requests with a non-HTML file extension, check dist/client/ first
|
|
484
|
-
// so the static server finds the Vite-built JS/CSS bundles.
|
|
485
|
-
const clientDist = join(distDir, 'client')
|
|
486
|
-
const ext = extname(urlPath).toLowerCase()
|
|
487
|
-
if (ext && ext !== '.html' && existsSync(clientDist)) {
|
|
488
|
-
const assetPath = join(clientDist, urlPath)
|
|
489
|
-
if (
|
|
490
|
-
isPathBounded(clientDist, urlPath) &&
|
|
491
|
-
existsSync(assetPath) && !statSync(assetPath).isDirectory()
|
|
492
|
-
) {
|
|
493
|
-
const mimeType = getMimeType(assetPath)
|
|
494
|
-
res.setHeader('Content-Type', mimeType)
|
|
495
|
-
res.setHeader('Cache-Control', getCacheControl(assetPath))
|
|
496
|
-
const stream = createReadStream(assetPath)
|
|
497
|
-
if (GZIP_TYPES.has(mimeType) && acceptsGzip(req)) {
|
|
498
|
-
res.setHeader('Content-Encoding', 'gzip')
|
|
499
|
-
stream.pipe(createGzip()).pipe(res)
|
|
500
|
-
} else {
|
|
501
|
-
stream.pipe(res)
|
|
502
|
-
}
|
|
503
|
-
return
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
const served = serveStaticFile(req, res, distDir)
|
|
507
|
-
if (!served) {
|
|
508
|
-
res.statusCode = 404
|
|
509
|
-
res.end('Not Found')
|
|
510
|
-
}
|
|
511
|
-
})
|
|
512
|
-
|
|
513
|
-
server.headersTimeout = 10_000
|
|
514
|
-
server.requestTimeout = 30_000
|
|
515
|
-
|
|
516
|
-
server.listen(port, options.host, () => {
|
|
517
|
-
console.log(`[cer-app] Static preview running at http://${options.host}:${port}`)
|
|
518
|
-
})
|
|
519
|
-
|
|
520
|
-
registerGracefulShutdown(server)
|
|
521
|
-
}
|
|
522
|
-
})
|
|
523
|
-
}
|