@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,340 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useState() composable tests.
|
|
3
|
-
*
|
|
4
|
-
* Covers:
|
|
5
|
-
* - Client path: singleton Map on globalThis.__CER_STATE__
|
|
6
|
-
* - SSR path: per-request Map via AsyncLocalStorage (__CER_STATE_STORE__)
|
|
7
|
-
* - SSR→client hydration: __CER_STATE_INIT__ pre-populates client Map
|
|
8
|
-
* - SSR serialization: v.value is readable from ALS Map refs (mirrors entry-server-template)
|
|
9
|
-
* - Full round-trip: SSR ALS → serialize → __CER_STATE_INIT__ → client hydration
|
|
10
|
-
* - Shared ref semantics (same key → same ref)
|
|
11
|
-
* - Factory init called only once
|
|
12
|
-
* - Concurrent SSR isolation
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
16
|
-
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
17
|
-
import { useState } from '../../runtime/composables/use-state.js'
|
|
18
|
-
|
|
19
|
-
const g = globalThis as Record<string, unknown>
|
|
20
|
-
|
|
21
|
-
function cleanup() {
|
|
22
|
-
delete g['__CER_STATE_STORE__']
|
|
23
|
-
delete g['__CER_STATE__']
|
|
24
|
-
delete g['__CER_STATE_INIT__']
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// ─── Client path ──────────────────────────────────────────────────────────────
|
|
28
|
-
|
|
29
|
-
describe('useState() — client path (singleton Map)', () => {
|
|
30
|
-
beforeEach(cleanup)
|
|
31
|
-
afterEach(cleanup)
|
|
32
|
-
|
|
33
|
-
it('creates a reactive ref with the init value', () => {
|
|
34
|
-
const title = useState('title', 'Hello')
|
|
35
|
-
expect(title.value).toBe('Hello')
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
it('returns the same ref object for the same key', () => {
|
|
39
|
-
const a = useState('counter', 0)
|
|
40
|
-
const b = useState('counter')
|
|
41
|
-
expect(a).toBe(b)
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
it('ignores init on subsequent calls for the same key', () => {
|
|
45
|
-
const a = useState('theme', 'light')
|
|
46
|
-
const b = useState('theme', 'dark')
|
|
47
|
-
expect(a.value).toBe('light')
|
|
48
|
-
expect(b.value).toBe('light')
|
|
49
|
-
expect(a).toBe(b)
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
it('mutations are visible across all refs sharing the same key', () => {
|
|
53
|
-
const a = useState('count', 0)
|
|
54
|
-
const b = useState('count')
|
|
55
|
-
a.value = 42
|
|
56
|
-
expect(b.value).toBe(42)
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
it('accepts a factory function as init', () => {
|
|
60
|
-
const state = useState('complex', () => ({ nested: true }))
|
|
61
|
-
expect(state.value).toEqual({ nested: true })
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
it('factory is only called once even when useState is called multiple times', () => {
|
|
65
|
-
let calls = 0
|
|
66
|
-
const factory = () => { calls++; return 'initial' }
|
|
67
|
-
useState('factory-key', factory)
|
|
68
|
-
useState('factory-key', factory)
|
|
69
|
-
expect(calls).toBe(1)
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
it('different keys produce independent refs', () => {
|
|
73
|
-
const a = useState('key-a', 1)
|
|
74
|
-
const b = useState('key-b', 2)
|
|
75
|
-
a.value = 99
|
|
76
|
-
expect(b.value).toBe(2)
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
it('works without an init value (value is null)', () => {
|
|
80
|
-
const state = useState<string | null>('noInit')
|
|
81
|
-
expect(state.value).toBeNull()
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
it('lazily initializes globalThis.__CER_STATE__ on first call', () => {
|
|
85
|
-
expect(g['__CER_STATE__']).toBeUndefined()
|
|
86
|
-
useState('lazy', 'x')
|
|
87
|
-
expect(g['__CER_STATE__']).toBeDefined()
|
|
88
|
-
})
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
// ─── SSR → client hydration ───────────────────────────────────────────────────
|
|
92
|
-
|
|
93
|
-
describe('useState() — SSR→client hydration (__CER_STATE_INIT__)', () => {
|
|
94
|
-
beforeEach(cleanup)
|
|
95
|
-
afterEach(cleanup)
|
|
96
|
-
|
|
97
|
-
it('pre-populates the client Map from __CER_STATE_INIT__ on first call', () => {
|
|
98
|
-
g['__CER_STATE_INIT__'] = { pageTitle: 'SSR Title', count: 42 }
|
|
99
|
-
const title = useState<string>('pageTitle')
|
|
100
|
-
expect(title.value).toBe('SSR Title')
|
|
101
|
-
const count = useState<number>('count')
|
|
102
|
-
expect(count.value).toBe(42)
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
it('SSR-hydrated value takes precedence over init', () => {
|
|
106
|
-
g['__CER_STATE_INIT__'] = { pageTitle: 'From Server' }
|
|
107
|
-
const title = useState<string>('pageTitle', 'Default Title')
|
|
108
|
-
expect(title.value).toBe('From Server')
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
it('keys not in __CER_STATE_INIT__ still use their init value', () => {
|
|
112
|
-
g['__CER_STATE_INIT__'] = { pageTitle: 'From Server' }
|
|
113
|
-
const other = useState<string>('otherKey', 'my-default')
|
|
114
|
-
expect(other.value).toBe('my-default')
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
it('returns the same ref for a key already hydrated from SSR', () => {
|
|
118
|
-
g['__CER_STATE_INIT__'] = { foo: 'bar' }
|
|
119
|
-
const a = useState<string>('foo')
|
|
120
|
-
const b = useState<string>('foo')
|
|
121
|
-
expect(a).toBe(b)
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
it('mutations to hydrated refs are shared across all callers', () => {
|
|
125
|
-
g['__CER_STATE_INIT__'] = { theme: 'light' }
|
|
126
|
-
const a = useState<string>('theme')
|
|
127
|
-
const b = useState<string>('theme')
|
|
128
|
-
a.value = 'dark'
|
|
129
|
-
expect(b.value).toBe('dark')
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
it('does nothing when __CER_STATE_INIT__ is absent', () => {
|
|
133
|
-
const state = useState('key', 'client-default')
|
|
134
|
-
expect(state.value).toBe('client-default')
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
it('ignores __CER_STATE_INIT__ when __CER_STATE__ Map already exists', () => {
|
|
138
|
-
// Simulate: Map already created before __CER_STATE_INIT__ is checked
|
|
139
|
-
g['__CER_STATE__'] = new Map()
|
|
140
|
-
g['__CER_STATE_INIT__'] = { pageTitle: 'Should Be Ignored' }
|
|
141
|
-
const title = useState<string>('pageTitle', 'From Init Param')
|
|
142
|
-
// __CER_STATE_INIT__ is skipped because Map already exists; init param is used
|
|
143
|
-
expect(title.value).toBe('From Init Param')
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
it('hydrates null values from __CER_STATE_INIT__', () => {
|
|
147
|
-
g['__CER_STATE_INIT__'] = { nullable: null }
|
|
148
|
-
const state = useState<null>('nullable')
|
|
149
|
-
expect(state.value).toBeNull()
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
it('hydrates object values from __CER_STATE_INIT__', () => {
|
|
153
|
-
g['__CER_STATE_INIT__'] = { user: { id: 1, name: 'Alice' } }
|
|
154
|
-
const state = useState<{ id: number; name: string }>('user')
|
|
155
|
-
expect(state.value).toEqual({ id: 1, name: 'Alice' })
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
it('hydrates array values from __CER_STATE_INIT__', () => {
|
|
159
|
-
g['__CER_STATE_INIT__'] = { items: [1, 2, 3] }
|
|
160
|
-
const state = useState<number[]>('items')
|
|
161
|
-
expect(state.value).toEqual([1, 2, 3])
|
|
162
|
-
})
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
// ─── SSR path ─────────────────────────────────────────────────────────────────
|
|
166
|
-
|
|
167
|
-
describe('useState() — SSR path (__CER_STATE_STORE__ ALS)', () => {
|
|
168
|
-
let store: AsyncLocalStorage<Map<string, unknown>>
|
|
169
|
-
|
|
170
|
-
beforeEach(() => {
|
|
171
|
-
cleanup()
|
|
172
|
-
store = new AsyncLocalStorage()
|
|
173
|
-
g['__CER_STATE_STORE__'] = store
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
afterEach(cleanup)
|
|
177
|
-
|
|
178
|
-
it('creates a ref with the init value inside run()', () => {
|
|
179
|
-
store.run(new Map(), () => {
|
|
180
|
-
const title = useState('title', 'SSR Page')
|
|
181
|
-
expect(title.value).toBe('SSR Page')
|
|
182
|
-
})
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
it('returns the same ref for the same key within the same run()', () => {
|
|
186
|
-
store.run(new Map(), () => {
|
|
187
|
-
const a = useState('counter', 0)
|
|
188
|
-
const b = useState('counter')
|
|
189
|
-
expect(a).toBe(b)
|
|
190
|
-
})
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
it('state written before rendering is visible to layout in same ALS context', () => {
|
|
194
|
-
store.run(new Map(), () => {
|
|
195
|
-
// Simulate loader setting state before render
|
|
196
|
-
const fromLoader = useState('pageTitle', 'Set by loader')
|
|
197
|
-
// Simulate layout reading state during render
|
|
198
|
-
const fromLayout = useState('pageTitle', 'Layout fallback')
|
|
199
|
-
expect(fromLayout.value).toBe('Set by loader')
|
|
200
|
-
expect(fromLoader).toBe(fromLayout)
|
|
201
|
-
})
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
it('concurrent requests have isolated state', async () => {
|
|
205
|
-
const results: string[] = []
|
|
206
|
-
await Promise.all([
|
|
207
|
-
store.run(new Map(), async () => {
|
|
208
|
-
useState('title', 'Request A')
|
|
209
|
-
await new Promise(r => setTimeout(r, 5))
|
|
210
|
-
results.push(useState<string>('title').value as string)
|
|
211
|
-
}),
|
|
212
|
-
store.run(new Map(), async () => {
|
|
213
|
-
useState('title', 'Request B')
|
|
214
|
-
await new Promise(r => setTimeout(r, 1))
|
|
215
|
-
results.push(useState<string>('title').value as string)
|
|
216
|
-
}),
|
|
217
|
-
])
|
|
218
|
-
expect(results).toContain('Request A')
|
|
219
|
-
expect(results).toContain('Request B')
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
it('falls back to client Map when called outside a run() context', () => {
|
|
223
|
-
// Store is on globalThis but no active run() — getStore() returns undefined
|
|
224
|
-
const state = useState('fallthrough', 'client-init')
|
|
225
|
-
expect(state.value).toBe('client-init')
|
|
226
|
-
expect(g['__CER_STATE__']).toBeDefined()
|
|
227
|
-
})
|
|
228
|
-
|
|
229
|
-
it('ignores client Map when inside ALS context', () => {
|
|
230
|
-
// Pre-populate the client map with a conflicting value
|
|
231
|
-
g['__CER_STATE__'] = new Map([['key', { value: 'client-value' }]])
|
|
232
|
-
store.run(new Map(), () => {
|
|
233
|
-
const state = useState('key', 'ssr-value')
|
|
234
|
-
expect(state.value).toBe('ssr-value')
|
|
235
|
-
})
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
it('accepts a factory init on SSR', () => {
|
|
239
|
-
store.run(new Map(), () => {
|
|
240
|
-
const state = useState('data', () => [1, 2, 3])
|
|
241
|
-
expect(state.value).toEqual([1, 2, 3])
|
|
242
|
-
})
|
|
243
|
-
})
|
|
244
|
-
})
|
|
245
|
-
|
|
246
|
-
// ─── SSR serialization ────────────────────────────────────────────────────────
|
|
247
|
-
//
|
|
248
|
-
// Validates that the Map entries written by useState() on the server expose
|
|
249
|
-
// .value (ref semantics), which is exactly what entry-server-template reads:
|
|
250
|
-
// for (const [k, v] of _stateMap) { _stateObj[k] = v.value }
|
|
251
|
-
|
|
252
|
-
describe('useState() — SSR serialization (v.value readable from ALS Map)', () => {
|
|
253
|
-
let store: AsyncLocalStorage<Map<string, unknown>>
|
|
254
|
-
|
|
255
|
-
beforeEach(() => {
|
|
256
|
-
cleanup()
|
|
257
|
-
store = new AsyncLocalStorage()
|
|
258
|
-
g['__CER_STATE_STORE__'] = store
|
|
259
|
-
})
|
|
260
|
-
|
|
261
|
-
afterEach(cleanup)
|
|
262
|
-
|
|
263
|
-
it('refs in the ALS Map expose .value matching what was set', () => {
|
|
264
|
-
store.run(new Map(), () => {
|
|
265
|
-
useState('pageTitle', 'My Page')
|
|
266
|
-
const map = store.getStore()!
|
|
267
|
-
expect(map.size).toBe(1)
|
|
268
|
-
expect((map.get('pageTitle') as { value: unknown }).value).toBe('My Page')
|
|
269
|
-
})
|
|
270
|
-
})
|
|
271
|
-
|
|
272
|
-
it('serialization mirrors entry-server-template: Object.fromEntries v.value', () => {
|
|
273
|
-
store.run(new Map(), () => {
|
|
274
|
-
useState('title', 'About Us')
|
|
275
|
-
useState('count', 7)
|
|
276
|
-
const map = store.getStore()!
|
|
277
|
-
// Mirror the exact serialization in entry-server-template.ts:
|
|
278
|
-
// for (const [k, v] of _stateMap) { _stateObj[k] = v.value }
|
|
279
|
-
const stateObj: Record<string, unknown> = {}
|
|
280
|
-
for (const [k, v] of map) { stateObj[k] = (v as { value: unknown }).value }
|
|
281
|
-
expect(stateObj).toEqual({ title: 'About Us', count: 7 })
|
|
282
|
-
})
|
|
283
|
-
})
|
|
284
|
-
|
|
285
|
-
it('mutation via .value is reflected in the serialized output', () => {
|
|
286
|
-
store.run(new Map(), () => {
|
|
287
|
-
const ref = useState('title', 'Original')
|
|
288
|
-
ref.value = 'Updated'
|
|
289
|
-
const map = store.getStore()!
|
|
290
|
-
expect((map.get('title') as { value: unknown }).value).toBe('Updated')
|
|
291
|
-
})
|
|
292
|
-
})
|
|
293
|
-
|
|
294
|
-
it('full round-trip: SSR ALS → serialize → __CER_STATE_INIT__ → client hydration', () => {
|
|
295
|
-
// 1. Server: loader sets state inside ALS context
|
|
296
|
-
store.run(new Map(), () => {
|
|
297
|
-
useState<string>('pageTitle').value = 'Page Title from Loader'
|
|
298
|
-
useState<number>('visitCount', 42)
|
|
299
|
-
|
|
300
|
-
// 2. Server: serialize the ALS Map (mirrors entry-server-template logic)
|
|
301
|
-
const map = store.getStore()!
|
|
302
|
-
const stateObj: Record<string, unknown> = {}
|
|
303
|
-
for (const [k, v] of map) { stateObj[k] = (v as { value: unknown }).value }
|
|
304
|
-
|
|
305
|
-
// Verify serialized shape is correct before handing it to the client
|
|
306
|
-
expect(stateObj).toEqual({ pageTitle: 'Page Title from Loader', visitCount: 42 })
|
|
307
|
-
|
|
308
|
-
// 3. Server injects window.__CER_STATE_INIT__ = JSON.parse(JSON.stringify(stateObj))
|
|
309
|
-
// (JSON round-trip ensures only serializable values survive, as in the real path)
|
|
310
|
-
g['__CER_STATE_INIT__'] = JSON.parse(JSON.stringify(stateObj))
|
|
311
|
-
})
|
|
312
|
-
|
|
313
|
-
// 4. Client: ALS store is gone; useState() falls through to client path
|
|
314
|
-
delete g['__CER_STATE_STORE__']
|
|
315
|
-
|
|
316
|
-
// 5. Client: first useState() call creates Map from __CER_STATE_INIT__ snapshot
|
|
317
|
-
const title = useState<string>('pageTitle')
|
|
318
|
-
const count = useState<number>('visitCount')
|
|
319
|
-
|
|
320
|
-
expect(title.value).toBe('Page Title from Loader')
|
|
321
|
-
expect(count.value).toBe(42)
|
|
322
|
-
|
|
323
|
-
// 6. Both callers share the same ref (same key → same ref identity)
|
|
324
|
-
expect(title).toBe(useState<string>('pageTitle'))
|
|
325
|
-
})
|
|
326
|
-
})
|
|
327
|
-
|
|
328
|
-
// ─── Generic types ────────────────────────────────────────────────────────────
|
|
329
|
-
|
|
330
|
-
describe('useState() — generic type inference', () => {
|
|
331
|
-
beforeEach(cleanup)
|
|
332
|
-
afterEach(cleanup)
|
|
333
|
-
|
|
334
|
-
it('preserves generic type T', () => {
|
|
335
|
-
interface User { id: number; name: string }
|
|
336
|
-
const user = useState<User>('user', { id: 1, name: 'Alice' })
|
|
337
|
-
expect(user.value.id).toBe(1)
|
|
338
|
-
expect(user.value.name).toBe('Alice')
|
|
339
|
-
})
|
|
340
|
-
})
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { defineConfig } from '../../types/config.js'
|
|
3
|
-
|
|
4
|
-
describe('defineConfig', () => {
|
|
5
|
-
it('returns the config object unchanged', () => {
|
|
6
|
-
const config = { mode: 'spa' as const, port: 4000 }
|
|
7
|
-
expect(defineConfig(config)).toBe(config)
|
|
8
|
-
})
|
|
9
|
-
|
|
10
|
-
it('accepts an empty config object', () => {
|
|
11
|
-
expect(defineConfig({})).toEqual({})
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
it('preserves nested ssg config', () => {
|
|
15
|
-
const config = { ssg: { routes: ['/a', '/b'], concurrency: 2 } }
|
|
16
|
-
expect(defineConfig(config)).toEqual(config)
|
|
17
|
-
})
|
|
18
|
-
})
|
|
@@ -1,335 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cloudflare Pages adapter.
|
|
3
|
-
*
|
|
4
|
-
* Transforms the cer-app `dist/` output into the files needed to deploy on
|
|
5
|
-
* Cloudflare Pages with an edge-side SSR worker.
|
|
6
|
-
*
|
|
7
|
-
* SSR mode:
|
|
8
|
-
* - Reads dist/client/index.html and inlines it as a string constant in the
|
|
9
|
-
* generated `_worker.js` (Cloudflare Pages Advanced Mode convention).
|
|
10
|
-
* The worker sets globalThis.__CER_CLIENT_TEMPLATE__ before dynamically
|
|
11
|
-
* importing the server bundle, bypassing the node:fs call in server.js.
|
|
12
|
-
* - Copies content-hashed assets and other public files to dist/ alongside
|
|
13
|
-
* _worker.js — Cloudflare Pages serves them from the CDN before the worker.
|
|
14
|
-
* - Writes `wrangler.toml` with nodejs_compat for AsyncLocalStorage + streams.
|
|
15
|
-
*
|
|
16
|
-
* SPA/SSG mode:
|
|
17
|
-
* - Copies static files to dist/ (already in correct layout after build).
|
|
18
|
-
* - Writes wrangler.toml pointing at dist/ with no function.
|
|
19
|
-
*
|
|
20
|
-
* Cloudflare Pages Advanced Mode (_worker.js) reference:
|
|
21
|
-
* https://developers.cloudflare.com/pages/functions/advanced-mode/
|
|
22
|
-
*
|
|
23
|
-
* nodejs_compat compatibility flag reference:
|
|
24
|
-
* https://developers.cloudflare.com/workers/runtime-apis/nodejs/
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
import { join } from 'pathe'
|
|
28
|
-
import {
|
|
29
|
-
existsSync,
|
|
30
|
-
mkdirSync,
|
|
31
|
-
writeFileSync,
|
|
32
|
-
readFileSync,
|
|
33
|
-
copyFileSync,
|
|
34
|
-
cpSync,
|
|
35
|
-
readdirSync,
|
|
36
|
-
statSync,
|
|
37
|
-
} from 'node:fs'
|
|
38
|
-
|
|
39
|
-
// ─── Worker bridge template ───────────────────────────────────────────────────
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Generates the _worker.js content with the client HTML inlined.
|
|
43
|
-
*
|
|
44
|
-
* Key design points:
|
|
45
|
-
* - Sets globalThis.__CER_CLIENT_TEMPLATE__ BEFORE dynamically importing the
|
|
46
|
-
* server bundle so the bundle's module-init code sees the value instead of
|
|
47
|
-
* trying readFileSync (which throws in Workers without node:fs).
|
|
48
|
-
* - Uses top-level await (supported in Cloudflare Workers ES modules).
|
|
49
|
-
* - Mirrors the Netlify bridge's Web Request → Node.js mock → Response pattern;
|
|
50
|
-
* Cloudflare Workers with nodejs_compat support node:stream + AsyncLocalStorage.
|
|
51
|
-
* - Responses are streamed via the Web Streams TransformStream API — chunks written
|
|
52
|
-
* by the SSR handler are forwarded to the client as they arrive rather than being
|
|
53
|
-
* buffered until the full page is rendered.
|
|
54
|
-
* - Exports `{ fetch }` — the Cloudflare Pages _worker.js module format.
|
|
55
|
-
*/
|
|
56
|
-
function generateWorkerBridge(clientHtml: string): string {
|
|
57
|
-
// Escape the HTML for embedding in a JS template literal.
|
|
58
|
-
const escaped = clientHtml.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${')
|
|
59
|
-
|
|
60
|
-
return `\
|
|
61
|
-
// Auto-generated by @jasonshimmy/vite-plugin-cer-app — do not edit.
|
|
62
|
-
// Cloudflare Pages _worker.js — Advanced Mode SSR bridge.
|
|
63
|
-
// Requires compatibility_flags = ["nodejs_compat"] in wrangler.toml.
|
|
64
|
-
import { Readable } from 'node:stream'
|
|
65
|
-
|
|
66
|
-
// Inline the client HTML template so the server bundle does not need node:fs.
|
|
67
|
-
// Must be set before the dynamic import below resolves.
|
|
68
|
-
globalThis.__CER_CLIENT_TEMPLATE__ = \`${escaped}\`
|
|
69
|
-
|
|
70
|
-
const { isrHandler, apiRoutes, runServerMiddleware, runWithRequestContext } = await import('./server/server.js')
|
|
71
|
-
|
|
72
|
-
function matchApiPattern(pattern, urlPath) {
|
|
73
|
-
const pp = pattern.split('/')
|
|
74
|
-
const up = urlPath.split('/')
|
|
75
|
-
if (pp.length !== up.length) return null
|
|
76
|
-
const params = {}
|
|
77
|
-
for (let i = 0; i < pp.length; i++) {
|
|
78
|
-
if (pp[i].startsWith(':')) params[pp[i].slice(1)] = decodeURIComponent(up[i])
|
|
79
|
-
else if (pp[i] !== up[i]) return null
|
|
80
|
-
}
|
|
81
|
-
return params
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function parseQuery(search) {
|
|
85
|
-
if (!search) return {}
|
|
86
|
-
const qs = search.startsWith('?') ? search.slice(1) : search
|
|
87
|
-
const result = {}
|
|
88
|
-
for (const part of qs.split('&')) {
|
|
89
|
-
if (!part) continue
|
|
90
|
-
const eqIdx = part.indexOf('=')
|
|
91
|
-
if (eqIdx === -1) result[decodeURIComponent(part)] = ''
|
|
92
|
-
else result[decodeURIComponent(part.slice(0, eqIdx))] = decodeURIComponent(part.slice(eqIdx + 1))
|
|
93
|
-
}
|
|
94
|
-
return result
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
async function toNodeRequest(webReq) {
|
|
98
|
-
const url = new URL(webReq.url)
|
|
99
|
-
const hasBody = webReq.body !== null
|
|
100
|
-
&& webReq.method !== 'GET'
|
|
101
|
-
&& webReq.method !== 'HEAD'
|
|
102
|
-
const body = hasBody ? Buffer.from(await webReq.arrayBuffer()) : null
|
|
103
|
-
const req = Object.assign(
|
|
104
|
-
body ? Readable.from([body]) : Readable.from([]),
|
|
105
|
-
{
|
|
106
|
-
url: url.pathname + url.search,
|
|
107
|
-
method: webReq.method,
|
|
108
|
-
headers: Object.fromEntries(webReq.headers.entries()),
|
|
109
|
-
},
|
|
110
|
-
)
|
|
111
|
-
req.query = parseQuery(url.search)
|
|
112
|
-
if (body !== null) {
|
|
113
|
-
const ct = webReq.headers.get('content-type') ?? ''
|
|
114
|
-
if (ct.includes('application/json')) {
|
|
115
|
-
try { req.body = JSON.parse(body.toString('utf-8')) } catch { req.body = undefined }
|
|
116
|
-
} else {
|
|
117
|
-
req.body = body
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return req
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function createNodeResponse() {
|
|
124
|
-
const { readable, writable } = new TransformStream()
|
|
125
|
-
const writer = writable.getWriter()
|
|
126
|
-
const encoder = new TextEncoder()
|
|
127
|
-
const headers = {}
|
|
128
|
-
let _resolve
|
|
129
|
-
let _ended = false
|
|
130
|
-
const promise = new Promise((res) => { _resolve = res })
|
|
131
|
-
const res = {
|
|
132
|
-
statusCode: 200,
|
|
133
|
-
setHeader(name, value) { headers[name.toLowerCase()] = String(value) },
|
|
134
|
-
getHeader(name) { return headers[name.toLowerCase()] },
|
|
135
|
-
removeHeader(name) { delete headers[name.toLowerCase()] },
|
|
136
|
-
write(chunk) {
|
|
137
|
-
if (_ended) return
|
|
138
|
-
void writer.write(typeof chunk === 'string' ? encoder.encode(chunk) : chunk).catch(() => {})
|
|
139
|
-
},
|
|
140
|
-
end(chunk) {
|
|
141
|
-
if (_ended) return
|
|
142
|
-
_ended = true
|
|
143
|
-
if (chunk) void writer.write(typeof chunk === 'string' ? encoder.encode(chunk) : chunk).catch(() => {})
|
|
144
|
-
void writer.close().catch(() => {})
|
|
145
|
-
_resolve(new Response(readable, { status: res.statusCode, headers }))
|
|
146
|
-
},
|
|
147
|
-
json(data) {
|
|
148
|
-
this.setHeader('Content-Type', 'application/json; charset=utf-8')
|
|
149
|
-
this.end(JSON.stringify(data))
|
|
150
|
-
},
|
|
151
|
-
status(code) { this.statusCode = code; return this },
|
|
152
|
-
get writableEnded() { return _ended },
|
|
153
|
-
}
|
|
154
|
-
return { res, promise }
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
async function handleRequest(webReq) {
|
|
158
|
-
const url = new URL(webReq.url)
|
|
159
|
-
const urlPath = url.pathname
|
|
160
|
-
const method = webReq.method ?? 'GET'
|
|
161
|
-
|
|
162
|
-
const nodeReq = await toNodeRequest(webReq)
|
|
163
|
-
const { res, promise } = createNodeResponse()
|
|
164
|
-
|
|
165
|
-
// Run server middleware chain (auth, logging, CORS, etc.).
|
|
166
|
-
if (!(await runServerMiddleware(nodeReq, res))) return promise
|
|
167
|
-
|
|
168
|
-
// Route /api/* requests to the API handlers exported by the server bundle.
|
|
169
|
-
if (urlPath.startsWith('/api/')) {
|
|
170
|
-
for (const route of (apiRoutes ?? [])) {
|
|
171
|
-
const params = matchApiPattern(route.path, urlPath)
|
|
172
|
-
if (params !== null) {
|
|
173
|
-
nodeReq.params = params
|
|
174
|
-
const fn = route.handlers[method.toLowerCase()]
|
|
175
|
-
?? route.handlers[method.toUpperCase()]
|
|
176
|
-
?? route.handlers['default']
|
|
177
|
-
if (typeof fn === 'function') {
|
|
178
|
-
// Wrap in request context so useCookie / useSession work in API handlers.
|
|
179
|
-
runWithRequestContext(nodeReq, res, () => Promise.resolve(fn(nodeReq, res))).catch(() => {
|
|
180
|
-
if (!res.writableEnded) {
|
|
181
|
-
res.statusCode = 500
|
|
182
|
-
res.end(JSON.stringify({ error: 'Internal Server Error' }))
|
|
183
|
-
}
|
|
184
|
-
})
|
|
185
|
-
return promise
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
return new Response('Not Found', { status: 404 })
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// All other requests: SSR (with ISR for routes that declare meta.ssg.revalidate).
|
|
193
|
-
isrHandler(nodeReq, res).catch(() => {
|
|
194
|
-
if (!res.writableEnded) {
|
|
195
|
-
res.statusCode = 500
|
|
196
|
-
res.end('Internal Server Error')
|
|
197
|
-
}
|
|
198
|
-
})
|
|
199
|
-
return promise
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
export default {
|
|
203
|
-
async fetch(request, _env, _ctx) {
|
|
204
|
-
return handleRequest(request)
|
|
205
|
-
},
|
|
206
|
-
}
|
|
207
|
-
`
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
211
|
-
|
|
212
|
-
export async function runCloudflareAdapter(root: string): Promise<void> {
|
|
213
|
-
const distDir = join(root, 'dist')
|
|
214
|
-
if (!existsSync(distDir)) {
|
|
215
|
-
throw new Error(
|
|
216
|
-
`[cer-app] No dist/ directory found at ${distDir}. Run 'cer-app build' first.`,
|
|
217
|
-
)
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const serverBundle = join(distDir, 'server/server.js')
|
|
221
|
-
const ssgManifest = join(distDir, 'ssg-manifest.json')
|
|
222
|
-
const isSSR = existsSync(serverBundle) && !existsSync(ssgManifest)
|
|
223
|
-
|
|
224
|
-
if (isSSR) {
|
|
225
|
-
_buildSSR(root, distDir)
|
|
226
|
-
} else {
|
|
227
|
-
_buildStatic(root, distDir)
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
console.log('[cer-app] Cloudflare adapter complete.')
|
|
231
|
-
if (isSSR) {
|
|
232
|
-
console.log(' Worker: dist/_worker.js')
|
|
233
|
-
console.log(' Static: dist/')
|
|
234
|
-
console.log(' Deploy with: wrangler pages deploy dist')
|
|
235
|
-
} else {
|
|
236
|
-
console.log(' Deploy with: wrangler pages deploy dist')
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// ─── SSR output ───────────────────────────────────────────────────────────────
|
|
241
|
-
|
|
242
|
-
function _buildSSR(root: string, distDir: string): void {
|
|
243
|
-
const clientHtmlPath = join(distDir, 'client/index.html')
|
|
244
|
-
const clientHtml = existsSync(clientHtmlPath)
|
|
245
|
-
? readFileSync(clientHtmlPath, 'utf-8')
|
|
246
|
-
: ''
|
|
247
|
-
|
|
248
|
-
// Write the worker bridge with inlined client HTML.
|
|
249
|
-
const workerPath = join(distDir, '_worker.js')
|
|
250
|
-
writeFileSync(workerPath, generateWorkerBridge(clientHtml))
|
|
251
|
-
|
|
252
|
-
// P1-4: Warn when the worker file approaches or exceeds Cloudflare's size limits.
|
|
253
|
-
// Free plan: 1 MB compressed; Paid plan: 10 MB compressed. Check uncompressed as
|
|
254
|
-
// a conservative proxy — the compressed size will always be smaller.
|
|
255
|
-
const workerSizeBytes = statSync(workerPath).size
|
|
256
|
-
const WARN_LIMIT = 900_000 // 900 KB — approaching Free plan limit
|
|
257
|
-
const ERROR_LIMIT = 9_000_000 // 9 MB — approaching Paid plan limit
|
|
258
|
-
if (workerSizeBytes > ERROR_LIMIT) {
|
|
259
|
-
console.error(
|
|
260
|
-
`[cer-app] Cloudflare _worker.js is ${(workerSizeBytes / 1e6).toFixed(1)} MB — ` +
|
|
261
|
-
`exceeds the ${(ERROR_LIMIT / 1e6).toFixed(0)} MB limit. Deployment will likely fail.`,
|
|
262
|
-
)
|
|
263
|
-
process.exit(1)
|
|
264
|
-
} else if (workerSizeBytes > WARN_LIMIT) {
|
|
265
|
-
console.warn(
|
|
266
|
-
`[cer-app] Cloudflare _worker.js is ${(workerSizeBytes / 1e3).toFixed(0)} KB — ` +
|
|
267
|
-
`approaching the Cloudflare Free plan 1 MB limit. Consider the Paid plan or reducing bundle size.`,
|
|
268
|
-
)
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// Copy assets from dist/client/ into dist/ (at the same URL paths).
|
|
272
|
-
// Cloudflare Pages CDN serves files in the deploy directory as static first;
|
|
273
|
-
// requests that don't match a static file fall through to _worker.js.
|
|
274
|
-
// We deliberately skip index.html so HTML requests hit the SSR worker.
|
|
275
|
-
_copyClientAssets(join(distDir, 'client'), distDir)
|
|
276
|
-
|
|
277
|
-
// wrangler.toml: nodejs_compat for AsyncLocalStorage + node:stream support.
|
|
278
|
-
_writeWranglerToml(root, true)
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// ─── Static (SPA / SSG) output ────────────────────────────────────────────────
|
|
282
|
-
|
|
283
|
-
function _buildStatic(root: string, distDir: string): void {
|
|
284
|
-
const clientDir = join(distDir, 'client')
|
|
285
|
-
|
|
286
|
-
if (existsSync(clientDir)) {
|
|
287
|
-
// SSG: pre-rendered HTML lives in dist/; assets in dist/client/.
|
|
288
|
-
// Move assets alongside the HTML so Cloudflare Pages serves them correctly.
|
|
289
|
-
_copyClientAssets(clientDir, distDir)
|
|
290
|
-
}
|
|
291
|
-
// SPA: everything is already in dist/ — nothing to reorganise.
|
|
292
|
-
|
|
293
|
-
_writeWranglerToml(root, false)
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
297
|
-
|
|
298
|
-
function _writeWranglerToml(root: string, ssrMode: boolean): void {
|
|
299
|
-
const lines = [
|
|
300
|
-
'# Auto-generated by @jasonshimmy/vite-plugin-cer-app — do not edit.',
|
|
301
|
-
'name = "cer-app"',
|
|
302
|
-
'compatibility_date = "2024-09-23"',
|
|
303
|
-
]
|
|
304
|
-
|
|
305
|
-
if (ssrMode) {
|
|
306
|
-
lines.push('compatibility_flags = ["nodejs_compat"]')
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
lines.push('')
|
|
310
|
-
lines.push('[pages_build_output_dir]')
|
|
311
|
-
lines.push('dir = "dist"')
|
|
312
|
-
lines.push('')
|
|
313
|
-
|
|
314
|
-
writeFileSync(join(root, 'wrangler.toml'), lines.join('\n'))
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Copies Vite-hashed assets and other public files (favicon, robots.txt, …)
|
|
319
|
-
* from the client dist directory into `destDir`.
|
|
320
|
-
* Skips index.html — served by the SSR worker or SSG pre-rendering.
|
|
321
|
-
*/
|
|
322
|
-
function _copyClientAssets(clientDir: string, destDir: string): void {
|
|
323
|
-
if (!existsSync(clientDir)) return
|
|
324
|
-
mkdirSync(destDir, { recursive: true })
|
|
325
|
-
for (const entry of readdirSync(clientDir)) {
|
|
326
|
-
if (entry === 'index.html') continue
|
|
327
|
-
const src = join(clientDir, entry)
|
|
328
|
-
const dest = join(destDir, entry)
|
|
329
|
-
if (statSync(src).isDirectory()) {
|
|
330
|
-
cpSync(src, dest, { recursive: true })
|
|
331
|
-
} else {
|
|
332
|
-
copyFileSync(src, dest)
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|