@jasonshimmy/vite-plugin-cer-app 0.1.0 → 0.1.2

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.
Files changed (172) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/IMPLEMENTATION_PLAN.md +1 -1
  3. package/README.md +7 -7
  4. package/VITE_PLUGIN_FRAMEWORK_PLAN.md +12 -12
  5. package/commits.txt +1 -3
  6. package/dist/cli/commands/generate.d.ts.map +1 -1
  7. package/dist/cli/commands/generate.js +2 -0
  8. package/dist/cli/commands/generate.js.map +1 -1
  9. package/dist/cli/commands/preview.d.ts.map +1 -1
  10. package/dist/cli/commands/preview.js +21 -2
  11. package/dist/cli/commands/preview.js.map +1 -1
  12. package/dist/cli/create/index.js +2 -2
  13. package/dist/cli/create/index.js.map +1 -1
  14. package/dist/cli/index.js +1 -1
  15. package/dist/cli/index.js.map +1 -1
  16. package/dist/plugin/build-ssg.d.ts.map +1 -1
  17. package/dist/plugin/build-ssg.js +10 -21
  18. package/dist/plugin/build-ssg.js.map +1 -1
  19. package/dist/plugin/build-ssr.d.ts.map +1 -1
  20. package/dist/plugin/build-ssr.js +151 -28
  21. package/dist/plugin/build-ssr.js.map +1 -1
  22. package/dist/plugin/dts-generator.js +4 -4
  23. package/dist/plugin/dts-generator.js.map +1 -1
  24. package/dist/plugin/index.js +2 -2
  25. package/dist/plugin/index.js.map +1 -1
  26. package/dist/plugin/transforms/auto-import.js +3 -3
  27. package/dist/plugin/transforms/auto-import.js.map +1 -1
  28. package/dist/plugin/virtual/components.js +3 -3
  29. package/dist/plugin/virtual/components.js.map +1 -1
  30. package/dist/plugin/virtual/composables.js +3 -3
  31. package/dist/plugin/virtual/composables.js.map +1 -1
  32. package/dist/plugin/virtual/error.js +2 -2
  33. package/dist/plugin/virtual/error.js.map +1 -1
  34. package/dist/plugin/virtual/layouts.js +3 -3
  35. package/dist/plugin/virtual/layouts.js.map +1 -1
  36. package/dist/plugin/virtual/loading.js +2 -2
  37. package/dist/plugin/virtual/loading.js.map +1 -1
  38. package/dist/plugin/virtual/middleware.js +3 -3
  39. package/dist/plugin/virtual/middleware.js.map +1 -1
  40. package/dist/plugin/virtual/plugins.js +3 -3
  41. package/dist/plugin/virtual/plugins.js.map +1 -1
  42. package/dist/plugin/virtual/routes.d.ts.map +1 -1
  43. package/dist/plugin/virtual/routes.js +14 -4
  44. package/dist/plugin/virtual/routes.js.map +1 -1
  45. package/dist/plugin/virtual/server-api.js +3 -3
  46. package/dist/plugin/virtual/server-api.js.map +1 -1
  47. package/dist/plugin/virtual/server-middleware.js +3 -3
  48. package/dist/plugin/virtual/server-middleware.js.map +1 -1
  49. package/dist/runtime/app-template.d.ts +1 -1
  50. package/dist/runtime/app-template.d.ts.map +1 -1
  51. package/dist/runtime/app-template.js +6 -0
  52. package/dist/runtime/app-template.js.map +1 -1
  53. package/dist/runtime/composables/use-page-data.d.ts +15 -6
  54. package/dist/runtime/composables/use-page-data.d.ts.map +1 -1
  55. package/dist/runtime/composables/use-page-data.js +30 -9
  56. package/dist/runtime/composables/use-page-data.js.map +1 -1
  57. package/dist/runtime/entry-server-template.d.ts +1 -1
  58. package/dist/runtime/entry-server-template.d.ts.map +1 -1
  59. package/dist/runtime/entry-server-template.js +138 -17
  60. package/dist/runtime/entry-server-template.js.map +1 -1
  61. package/docs/cli.md +2 -2
  62. package/docs/configuration.md +4 -4
  63. package/docs/data-loading.md +8 -7
  64. package/docs/getting-started.md +5 -5
  65. package/docs/head-management.md +3 -3
  66. package/docs/middleware.md +2 -2
  67. package/docs/plugins.md +1 -1
  68. package/docs/rendering-modes.md +1 -1
  69. package/docs/routing.md +1 -1
  70. package/docs/server-api.md +10 -1
  71. package/docs/testing.md +4 -4
  72. package/package.json +1 -1
  73. package/src/__tests__/index.test.ts +21 -0
  74. package/src/__tests__/plugin/build-ssg.test.ts +265 -0
  75. package/src/__tests__/plugin/build-ssr.test.ts +180 -0
  76. package/src/__tests__/plugin/cer-app-plugin.test.ts +409 -0
  77. package/src/__tests__/plugin/dts-generator.test.ts +246 -0
  78. package/src/__tests__/plugin/resolve-config.test.ts +158 -0
  79. package/src/__tests__/plugin/transforms/auto-import.test.ts +1 -1
  80. package/src/__tests__/plugin/virtual/components.test.ts +1 -1
  81. package/src/__tests__/plugin/virtual/composables.test.ts +1 -1
  82. package/src/__tests__/plugin/virtual/error.test.ts +71 -0
  83. package/src/__tests__/plugin/virtual/layouts.test.ts +1 -1
  84. package/src/__tests__/plugin/virtual/loading.test.ts +72 -0
  85. package/src/__tests__/plugin/virtual/middleware.test.ts +1 -1
  86. package/src/__tests__/plugin/virtual/plugins.test.ts +1 -1
  87. package/src/__tests__/plugin/virtual/routes.test.ts +1 -1
  88. package/src/__tests__/plugin/virtual/server-api.test.ts +1 -1
  89. package/src/__tests__/plugin/virtual/server-middleware.test.ts +102 -0
  90. package/src/__tests__/runtime/use-page-data.test.ts +81 -5
  91. package/src/__tests__/types/config.test.ts +23 -0
  92. package/src/cli/commands/generate.ts +2 -0
  93. package/src/cli/commands/preview.ts +21 -2
  94. package/src/cli/create/index.ts +2 -2
  95. package/src/cli/create/templates/spa/cer.config.ts.tpl +1 -1
  96. package/src/cli/create/templates/spa/package.json.tpl +1 -1
  97. package/src/cli/create/templates/ssg/cer.config.ts.tpl +1 -1
  98. package/src/cli/create/templates/ssg/package.json.tpl +1 -1
  99. package/src/cli/create/templates/ssr/cer.config.ts.tpl +1 -1
  100. package/src/cli/create/templates/ssr/package.json.tpl +1 -1
  101. package/src/cli/index.ts +1 -1
  102. package/src/plugin/build-ssg.ts +9 -22
  103. package/src/plugin/build-ssr.ts +150 -28
  104. package/src/plugin/dts-generator.ts +4 -4
  105. package/src/plugin/index.ts +2 -2
  106. package/src/plugin/transforms/auto-import.ts +3 -3
  107. package/src/plugin/virtual/components.ts +3 -3
  108. package/src/plugin/virtual/composables.ts +3 -3
  109. package/src/plugin/virtual/error.ts +2 -2
  110. package/src/plugin/virtual/layouts.ts +3 -3
  111. package/src/plugin/virtual/loading.ts +2 -2
  112. package/src/plugin/virtual/middleware.ts +3 -3
  113. package/src/plugin/virtual/plugins.ts +3 -3
  114. package/src/plugin/virtual/routes.ts +15 -4
  115. package/src/plugin/virtual/server-api.ts +3 -3
  116. package/src/plugin/virtual/server-middleware.ts +3 -3
  117. package/src/runtime/app-template.ts +6 -0
  118. package/src/runtime/composables/use-page-data.ts +31 -9
  119. package/src/runtime/entry-server-template.ts +138 -17
  120. package/tsconfig.build.json +1 -1
  121. package/dist/__tests__/plugin/path-utils.test.d.ts +0 -2
  122. package/dist/__tests__/plugin/path-utils.test.d.ts.map +0 -1
  123. package/dist/__tests__/plugin/path-utils.test.js +0 -305
  124. package/dist/__tests__/plugin/path-utils.test.js.map +0 -1
  125. package/dist/__tests__/plugin/scanner.test.d.ts +0 -2
  126. package/dist/__tests__/plugin/scanner.test.d.ts.map +0 -1
  127. package/dist/__tests__/plugin/scanner.test.js +0 -143
  128. package/dist/__tests__/plugin/scanner.test.js.map +0 -1
  129. package/dist/__tests__/plugin/transforms/auto-import.test.d.ts +0 -2
  130. package/dist/__tests__/plugin/transforms/auto-import.test.d.ts.map +0 -1
  131. package/dist/__tests__/plugin/transforms/auto-import.test.js +0 -151
  132. package/dist/__tests__/plugin/transforms/auto-import.test.js.map +0 -1
  133. package/dist/__tests__/plugin/transforms/head-inject.test.d.ts +0 -2
  134. package/dist/__tests__/plugin/transforms/head-inject.test.d.ts.map +0 -1
  135. package/dist/__tests__/plugin/transforms/head-inject.test.js +0 -151
  136. package/dist/__tests__/plugin/transforms/head-inject.test.js.map +0 -1
  137. package/dist/__tests__/plugin/virtual/components.test.d.ts +0 -2
  138. package/dist/__tests__/plugin/virtual/components.test.d.ts.map +0 -1
  139. package/dist/__tests__/plugin/virtual/components.test.js +0 -47
  140. package/dist/__tests__/plugin/virtual/components.test.js.map +0 -1
  141. package/dist/__tests__/plugin/virtual/composables.test.d.ts +0 -2
  142. package/dist/__tests__/plugin/virtual/composables.test.d.ts.map +0 -1
  143. package/dist/__tests__/plugin/virtual/composables.test.js +0 -48
  144. package/dist/__tests__/plugin/virtual/composables.test.js.map +0 -1
  145. package/dist/__tests__/plugin/virtual/layouts.test.d.ts +0 -2
  146. package/dist/__tests__/plugin/virtual/layouts.test.d.ts.map +0 -1
  147. package/dist/__tests__/plugin/virtual/layouts.test.js +0 -59
  148. package/dist/__tests__/plugin/virtual/layouts.test.js.map +0 -1
  149. package/dist/__tests__/plugin/virtual/middleware.test.d.ts +0 -2
  150. package/dist/__tests__/plugin/virtual/middleware.test.d.ts.map +0 -1
  151. package/dist/__tests__/plugin/virtual/middleware.test.js +0 -58
  152. package/dist/__tests__/plugin/virtual/middleware.test.js.map +0 -1
  153. package/dist/__tests__/plugin/virtual/plugins.test.d.ts +0 -2
  154. package/dist/__tests__/plugin/virtual/plugins.test.d.ts.map +0 -1
  155. package/dist/__tests__/plugin/virtual/plugins.test.js +0 -73
  156. package/dist/__tests__/plugin/virtual/plugins.test.js.map +0 -1
  157. package/dist/__tests__/plugin/virtual/routes.test.d.ts +0 -2
  158. package/dist/__tests__/plugin/virtual/routes.test.d.ts.map +0 -1
  159. package/dist/__tests__/plugin/virtual/routes.test.js +0 -167
  160. package/dist/__tests__/plugin/virtual/routes.test.js.map +0 -1
  161. package/dist/__tests__/plugin/virtual/server-api.test.d.ts +0 -2
  162. package/dist/__tests__/plugin/virtual/server-api.test.d.ts.map +0 -1
  163. package/dist/__tests__/plugin/virtual/server-api.test.js +0 -72
  164. package/dist/__tests__/plugin/virtual/server-api.test.js.map +0 -1
  165. package/dist/__tests__/runtime/use-head.test.d.ts +0 -2
  166. package/dist/__tests__/runtime/use-head.test.d.ts.map +0 -1
  167. package/dist/__tests__/runtime/use-head.test.js +0 -202
  168. package/dist/__tests__/runtime/use-head.test.js.map +0 -1
  169. package/dist/__tests__/runtime/use-page-data.test.d.ts +0 -2
  170. package/dist/__tests__/runtime/use-page-data.test.d.ts.map +0 -1
  171. package/dist/__tests__/runtime/use-page-data.test.js +0 -41
  172. package/dist/__tests__/runtime/use-page-data.test.js.map +0 -1
@@ -0,0 +1,102 @@
1
+ import { vi, describe, it, expect, beforeEach } from 'vitest'
2
+
3
+ vi.mock('node:fs', () => ({ existsSync: vi.fn() }))
4
+ vi.mock('../../../plugin/scanner.js', () => ({ scanDirectory: vi.fn() }))
5
+
6
+ import { existsSync } from 'node:fs'
7
+ import { scanDirectory } from '../../../plugin/scanner.js'
8
+ import { generateServerMiddlewareCode } from '../../../plugin/virtual/server-middleware.js'
9
+
10
+ const MIDDLEWARE_DIR = '/project/server/middleware'
11
+
12
+ describe('generateServerMiddlewareCode', () => {
13
+ beforeEach(() => {
14
+ vi.mocked(existsSync).mockReturnValue(true)
15
+ vi.mocked(scanDirectory).mockResolvedValue([])
16
+ })
17
+
18
+ it('returns empty serverMiddleware when directory does not exist', async () => {
19
+ vi.mocked(existsSync).mockReturnValue(false)
20
+ const code = await generateServerMiddlewareCode(MIDDLEWARE_DIR)
21
+ expect(code).toContain('export const serverMiddleware = []')
22
+ expect(code).toContain('export default serverMiddleware')
23
+ })
24
+
25
+ it('returns empty serverMiddleware when no files found', async () => {
26
+ const code = await generateServerMiddlewareCode(MIDDLEWARE_DIR)
27
+ expect(code).toContain('export const serverMiddleware = []')
28
+ expect(code).toContain('export default serverMiddleware')
29
+ })
30
+
31
+ it('includes AUTO-GENERATED comment', async () => {
32
+ vi.mocked(scanDirectory).mockResolvedValue([`${MIDDLEWARE_DIR}/logger.ts`])
33
+ const code = await generateServerMiddlewareCode(MIDDLEWARE_DIR)
34
+ expect(code).toContain('AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app')
35
+ })
36
+
37
+ it('generates import for a middleware file', async () => {
38
+ vi.mocked(scanDirectory).mockResolvedValue([`${MIDDLEWARE_DIR}/logger.ts`])
39
+ const code = await generateServerMiddlewareCode(MIDDLEWARE_DIR)
40
+ expect(code).toContain(`import _sm_logger from "${MIDDLEWARE_DIR}/logger.ts"`)
41
+ })
42
+
43
+ it('registers middleware with its name and handler', async () => {
44
+ vi.mocked(scanDirectory).mockResolvedValue([`${MIDDLEWARE_DIR}/logger.ts`])
45
+ const code = await generateServerMiddlewareCode(MIDDLEWARE_DIR)
46
+ expect(code).toContain('name: "logger"')
47
+ expect(code).toContain('handler: _sm_logger?.default ?? _sm_logger')
48
+ })
49
+
50
+ it('exports serverMiddleware as both named and default export', async () => {
51
+ vi.mocked(scanDirectory).mockResolvedValue([`${MIDDLEWARE_DIR}/logger.ts`])
52
+ const code = await generateServerMiddlewareCode(MIDDLEWARE_DIR)
53
+ expect(code).toContain('export const serverMiddleware = [')
54
+ expect(code).toContain('export default serverMiddleware')
55
+ })
56
+
57
+ it('replaces dashes with underscores in the alias identifier', async () => {
58
+ vi.mocked(scanDirectory).mockResolvedValue([`${MIDDLEWARE_DIR}/check-auth.ts`])
59
+ const code = await generateServerMiddlewareCode(MIDDLEWARE_DIR)
60
+ expect(code).toContain('_sm_check_auth')
61
+ expect(code).toContain('name: "check-auth"')
62
+ })
63
+
64
+ it('prepends underscore to identifiers starting with a digit', async () => {
65
+ vi.mocked(scanDirectory).mockResolvedValue([`${MIDDLEWARE_DIR}/1first.ts`])
66
+ const code = await generateServerMiddlewareCode(MIDDLEWARE_DIR)
67
+ expect(code).toContain('_sm__1first')
68
+ })
69
+
70
+ it('handles multiple middleware files', async () => {
71
+ vi.mocked(scanDirectory).mockResolvedValue([
72
+ `${MIDDLEWARE_DIR}/logger.ts`,
73
+ `${MIDDLEWARE_DIR}/auth.ts`,
74
+ ])
75
+ const code = await generateServerMiddlewareCode(MIDDLEWARE_DIR)
76
+ expect(code).toContain('name: "auth"')
77
+ expect(code).toContain('name: "logger"')
78
+ })
79
+
80
+ it('sorts middleware files alphabetically', async () => {
81
+ vi.mocked(scanDirectory).mockResolvedValue([
82
+ `${MIDDLEWARE_DIR}/zz-last.ts`,
83
+ `${MIDDLEWARE_DIR}/aa-first.ts`,
84
+ ])
85
+ const code = await generateServerMiddlewareCode(MIDDLEWARE_DIR)
86
+ const authIdx = code.indexOf('aa-first')
87
+ const loggerIdx = code.indexOf('zz-last')
88
+ expect(authIdx).toBeLessThan(loggerIdx)
89
+ })
90
+
91
+ it('ends with a newline', async () => {
92
+ vi.mocked(scanDirectory).mockResolvedValue([`${MIDDLEWARE_DIR}/logger.ts`])
93
+ const code = await generateServerMiddlewareCode(MIDDLEWARE_DIR)
94
+ expect(code.endsWith('\n')).toBe(true)
95
+ })
96
+
97
+ it('returns AUTO-GENERATED comment even when directory is missing', async () => {
98
+ vi.mocked(existsSync).mockReturnValue(false)
99
+ const code = await generateServerMiddlewareCode(MIDDLEWARE_DIR)
100
+ expect(code).toContain('AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app')
101
+ })
102
+ })
@@ -1,10 +1,16 @@
1
- import { describe, it, expect, beforeEach } from 'vitest'
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest'
2
+ import { AsyncLocalStorage } from 'node:async_hooks'
2
3
  import { usePageData } from '../../runtime/composables/use-page-data.js'
3
4
 
4
5
  describe('usePageData', () => {
5
6
  beforeEach(() => {
6
7
  // Clean up global state between tests
7
8
  delete (globalThis as Record<string, unknown>)['__CER_DATA__']
9
+ delete (globalThis as Record<string, unknown>)['__CER_DATA_STORE__']
10
+ })
11
+
12
+ afterEach(() => {
13
+ delete (globalThis as Record<string, unknown>)['__CER_DATA_STORE__']
8
14
  })
9
15
 
10
16
  it('returns null when no SSR data is present', () => {
@@ -17,16 +23,16 @@ describe('usePageData', () => {
17
23
  expect(usePageData()).toEqual(data)
18
24
  })
19
25
 
20
- it('clears __CER_DATA__ after the first read', () => {
26
+ it('does NOT clear __CER_DATA__ on read (cleared by app.ts after router.replace)', () => {
21
27
  ;(globalThis as Record<string, unknown>)['__CER_DATA__'] = { title: 'Hello' }
22
28
  usePageData()
23
- expect((globalThis as Record<string, unknown>)['__CER_DATA__']).toBeUndefined()
29
+ expect((globalThis as Record<string, unknown>)['__CER_DATA__']).toEqual({ title: 'Hello' })
24
30
  })
25
31
 
26
- it('returns null on subsequent calls after the data has been consumed', () => {
32
+ it('returns the same data on subsequent calls (not consumed on first read)', () => {
27
33
  ;(globalThis as Record<string, unknown>)['__CER_DATA__'] = { title: 'Hello' }
28
34
  usePageData()
29
- expect(usePageData()).toBeNull()
35
+ expect(usePageData()).toEqual({ title: 'Hello' })
30
36
  })
31
37
 
32
38
  it('is generic and preserves the typed shape', () => {
@@ -43,3 +49,73 @@ describe('usePageData', () => {
43
49
  expect(usePageData()).toBeNull()
44
50
  })
45
51
  })
52
+
53
+ describe('usePageData — AsyncLocalStorage (server-side)', () => {
54
+ let store: AsyncLocalStorage<unknown>
55
+
56
+ beforeEach(() => {
57
+ delete (globalThis as Record<string, unknown>)['__CER_DATA__']
58
+ store = new AsyncLocalStorage()
59
+ ;(globalThis as Record<string, unknown>)['__CER_DATA_STORE__'] = store
60
+ })
61
+
62
+ afterEach(() => {
63
+ delete (globalThis as Record<string, unknown>)['__CER_DATA_STORE__']
64
+ delete (globalThis as Record<string, unknown>)['__CER_DATA__']
65
+ })
66
+
67
+ it('reads data from the ALS store when available', () => {
68
+ const data = { title: 'SSR Post', body: 'Hello from ALS' }
69
+ store.run(data, () => {
70
+ expect(usePageData()).toEqual(data)
71
+ })
72
+ })
73
+
74
+ it('returns null when ALS store is empty (null)', () => {
75
+ store.run(null, () => {
76
+ expect(usePageData()).toBeNull()
77
+ })
78
+ })
79
+
80
+ it('prefers ALS store over globalThis.__CER_DATA__', () => {
81
+ const alsData = { source: 'als' }
82
+ const globalData = { source: 'global' }
83
+ ;(globalThis as Record<string, unknown>)['__CER_DATA__'] = globalData
84
+ store.run(alsData, () => {
85
+ expect(usePageData()).toEqual(alsData)
86
+ })
87
+ })
88
+
89
+ it('does NOT delete __CER_DATA__ when returning ALS data', () => {
90
+ const globalData = { source: 'global' }
91
+ ;(globalThis as Record<string, unknown>)['__CER_DATA__'] = globalData
92
+ store.run({ source: 'als' }, () => {
93
+ usePageData()
94
+ })
95
+ // global data should still be present — ALS path doesn't consume it
96
+ expect((globalThis as Record<string, unknown>)['__CER_DATA__']).toEqual(globalData)
97
+ })
98
+
99
+ it('falls back to __CER_DATA__ when ALS store has no value outside run()', () => {
100
+ // Outside any store.run() context, getStore() returns undefined
101
+ const data = { fallback: true }
102
+ ;(globalThis as Record<string, unknown>)['__CER_DATA__'] = data
103
+ // store is set but we're outside a run() context → getStore() === undefined
104
+ expect(usePageData()).toEqual(data)
105
+ })
106
+
107
+ it('returns null when both ALS and __CER_DATA__ are absent', () => {
108
+ store.run(null, () => {
109
+ expect(usePageData()).toBeNull()
110
+ })
111
+ })
112
+
113
+ it('is safe to call multiple times inside same ALS context', () => {
114
+ const data = { count: 42 }
115
+ store.run(data, () => {
116
+ expect(usePageData()).toEqual(data)
117
+ // ALS data is NOT consumed on read, so a second call still returns it
118
+ expect(usePageData()).toEqual(data)
119
+ })
120
+ })
121
+ })
@@ -0,0 +1,23 @@
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 ssr config', () => {
15
+ const config = { ssr: { dsd: false, streaming: true } }
16
+ expect(defineConfig(config)).toEqual(config)
17
+ })
18
+
19
+ it('preserves nested ssg config', () => {
20
+ const config = { ssg: { routes: ['/a', '/b'], concurrency: 2 } }
21
+ expect(defineConfig(config)).toEqual(config)
22
+ })
23
+ })
@@ -77,5 +77,7 @@ export function generateCommand(): Command {
77
77
  }
78
78
 
79
79
  await buildSSG(config, viteUserConfig)
80
+ // Force exit: the SSG path-enumeration Vite server may keep alive Node timers.
81
+ process.exit(0)
80
82
  })
81
83
  }
@@ -95,9 +95,13 @@ export function previewCommand(): Command {
95
95
  const distDir = join(root, 'dist')
96
96
  const serverBundle = join(distDir, 'server/server.js')
97
97
 
98
- // Check if SSR server bundle exists
98
+ // Check if SSR server bundle exists.
99
+ // An SSG build also produces a server bundle, but previewing SSG means serving
100
+ // the pre-rendered static HTML — detect the SSG manifest to avoid switching to
101
+ // live SSR mode for SSG builds.
99
102
  const hasServerBundle = existsSync(serverBundle)
100
- const useSSR = options.ssr || hasServerBundle
103
+ const hasSsgManifest = existsSync(join(distDir, 'ssg-manifest.json'))
104
+ const useSSR = options.ssr || (hasServerBundle && !hasSsgManifest)
101
105
 
102
106
  if (useSSR && hasServerBundle) {
103
107
  console.log('[cer-app] Starting SSR preview server...')
@@ -200,6 +204,21 @@ export function previewCommand(): Command {
200
204
  }
201
205
 
202
206
  const server = createHttpServer((req: IncomingMessage, res: ServerResponse) => {
207
+ const urlPath = (req.url ?? '/').split('?')[0]
208
+ // SSG builds put assets in dist/client/ while HTML lives in dist/.
209
+ // For requests with a non-HTML file extension, check dist/client/ first
210
+ // so the static server finds the Vite-built JS/CSS bundles.
211
+ const clientDist = join(distDir, 'client')
212
+ const ext = extname(urlPath).toLowerCase()
213
+ if (ext && ext !== '.html' && existsSync(clientDist)) {
214
+ const assetPath = join(clientDist, urlPath)
215
+ if (existsSync(assetPath) && !statSync(assetPath).isDirectory()) {
216
+ res.setHeader('Content-Type', getMimeType(assetPath))
217
+ res.setHeader('Cache-Control', 'no-cache')
218
+ createReadStream(assetPath).pipe(res)
219
+ return
220
+ }
221
+ }
203
222
  const served = serveStaticFile(req, res, distDir)
204
223
  if (!served) {
205
224
  res.statusCode = 404
@@ -198,7 +198,7 @@ async function generateInlineTemplate(
198
198
  },
199
199
  devDependencies: {
200
200
  vite: '^5.0.0',
201
- 'vite-plugin-cer-app': '^0.1.0',
201
+ '@jasonshimmy/vite-plugin-cer-app': '^0.1.0',
202
202
  typescript: '^5.4.0',
203
203
  },
204
204
  },
@@ -211,7 +211,7 @@ async function generateInlineTemplate(
211
211
  // cer.config.ts
212
212
  await writeFile(
213
213
  join(targetDir, 'cer.config.ts'),
214
- `import { defineConfig } from 'vite-plugin-cer-app'\n\nexport default defineConfig({\n mode: '${mode}',\n autoImports: { components: true, composables: true, directives: true, runtime: true },\n})\n`,
214
+ `import { defineConfig } from '@jasonshimmy/vite-plugin-cer-app'\n\nexport default defineConfig({\n mode: '${mode}',\n autoImports: { components: true, composables: true, directives: true, runtime: true },\n})\n`,
215
215
  'utf-8',
216
216
  )
217
217
 
@@ -1,4 +1,4 @@
1
- import { defineConfig } from 'vite-plugin-cer-app'
1
+ import { defineConfig } from '@jasonshimmy/vite-plugin-cer-app'
2
2
 
3
3
  export default defineConfig({
4
4
  mode: 'spa',
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "devDependencies": {
14
14
  "vite": "^5.0.0",
15
- "vite-plugin-cer-app": "^0.1.0",
15
+ "@jasonshimmy/vite-plugin-cer-app": "^0.1.0",
16
16
  "typescript": "^5.4.0"
17
17
  }
18
18
  }
@@ -1,4 +1,4 @@
1
- import { defineConfig } from 'vite-plugin-cer-app'
1
+ import { defineConfig } from '@jasonshimmy/vite-plugin-cer-app'
2
2
 
3
3
  export default defineConfig({
4
4
  mode: 'ssg',
@@ -13,7 +13,7 @@
13
13
  },
14
14
  "devDependencies": {
15
15
  "vite": "^5.0.0",
16
- "vite-plugin-cer-app": "^0.1.0",
16
+ "@jasonshimmy/vite-plugin-cer-app": "^0.1.0",
17
17
  "typescript": "^5.4.0"
18
18
  }
19
19
  }
@@ -1,4 +1,4 @@
1
- import { defineConfig } from 'vite-plugin-cer-app'
1
+ import { defineConfig } from '@jasonshimmy/vite-plugin-cer-app'
2
2
 
3
3
  export default defineConfig({
4
4
  mode: 'ssr',
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "devDependencies": {
14
14
  "vite": "^5.0.0",
15
- "vite-plugin-cer-app": "^0.1.0",
15
+ "@jasonshimmy/vite-plugin-cer-app": "^0.1.0",
16
16
  "typescript": "^5.4.0"
17
17
  }
18
18
  }
package/src/cli/index.ts CHANGED
@@ -17,4 +17,4 @@ program.addCommand(buildCommand())
17
17
  program.addCommand(previewCommand())
18
18
  program.addCommand(generateCommand())
19
19
 
20
- program.parse(process.argv)
20
+ await program.parseAsync(process.argv)
@@ -1,4 +1,4 @@
1
- import { writeFile, mkdir, readFile } from 'node:fs/promises'
1
+ import { writeFile, mkdir } from 'node:fs/promises'
2
2
  import { existsSync } from 'node:fs'
3
3
  import { join } from 'pathe'
4
4
  import { createServer, build, type UserConfig } from 'vite'
@@ -101,14 +101,13 @@ let _serverMod: Record<string, unknown> | null = null
101
101
  /**
102
102
  * Renders a single path using the SSR server bundle and returns the HTML.
103
103
  *
104
- * The server bundle exports `handler` an Express-style (req, res) middleware
105
- * produced by createStreamingSSRHandler. We call it with a mock req/res that
106
- * captures the final HTML string via res.end().
104
+ * The server bundle's handler already merges the SSR output with
105
+ * dist/client/index.html internally (via the _mergeWithClientTemplate helper
106
+ * it embeds at build time), so this function simply captures the response.
107
107
  */
108
108
  async function renderPath(
109
109
  path: string,
110
110
  serverBundlePath: string,
111
- clientDistDir: string,
112
111
  ): Promise<string> {
113
112
  // Load server bundle once
114
113
  if (!_serverMod) {
@@ -130,28 +129,17 @@ async function renderPath(
130
129
  return ''
131
130
  }
132
131
 
133
- // Mock req/res for the Express-style handler
132
+ // Mock req/res for the Express-style handler.
133
+ // The handler internally merges with dist/client/index.html, so we just
134
+ // capture whatever it ends with.
134
135
  const mockReq = { url: path, headers: {} }
135
- const html = await new Promise<string>((resolve, reject) => {
136
+ return new Promise<string>((resolve, reject) => {
136
137
  const mockRes = {
137
138
  setHeader: () => {},
138
- // Intentionally omit write() so the handler uses the non-streaming code path
139
139
  end: (body: string) => resolve(body),
140
140
  }
141
141
  ;(handlerFn as Function)(mockReq, mockRes).catch(reject)
142
142
  })
143
-
144
- // Read the client index.html template and inject the server-rendered body
145
- const templatePath = join(clientDistDir, 'index.html')
146
- if (!existsSync(templatePath)) return html
147
-
148
- const template = await readFile(templatePath, 'utf-8')
149
- // The handler already emits a full document; just return it as-is
150
- // If it only emits a fragment we fall back to injecting into the template
151
- if (html.trimStart().startsWith('<!DOCTYPE') || html.trimStart().startsWith('<html')) {
152
- return html
153
- }
154
- return template.replace('<div id="app"></div>', `<div id="app">${html}</div>`)
155
143
  }
156
144
 
157
145
  /**
@@ -190,7 +178,6 @@ export async function buildSSG(
190
178
  viteUserConfig: UserConfig = {},
191
179
  ): Promise<void> {
192
180
  const distDir = join(config.root, 'dist')
193
- const clientDistDir = join(distDir, 'client')
194
181
  const serverDistDir = join(distDir, 'server')
195
182
  const serverBundlePath = join(serverDistDir, 'server.js')
196
183
 
@@ -222,7 +209,7 @@ export async function buildSSG(
222
209
  const results = await Promise.allSettled(
223
210
  chunk.map(async (path) => {
224
211
  console.log(`[cer-app] Generating: ${path}`)
225
- const html = await renderPath(path, serverBundlePath, clientDistDir)
212
+ const html = await renderPath(path, serverBundlePath)
226
213
  await writeRenderedPath(path, html, distDir)
227
214
  return path
228
215
  }),