@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,246 @@
1
+ import { vi, describe, it, expect, beforeEach } from 'vitest'
2
+
3
+ vi.mock('node:fs', async (importOriginal) => {
4
+ const actual = await importOriginal<typeof import('node:fs')>()
5
+ return {
6
+ ...actual,
7
+ existsSync: vi.fn().mockReturnValue(false),
8
+ writeFileSync: vi.fn(),
9
+ readFileSync: vi.fn().mockReturnValue(''),
10
+ }
11
+ })
12
+ vi.mock('../../plugin/scanner.js', () => ({ scanDirectory: vi.fn().mockResolvedValue([]) }))
13
+
14
+ import { existsSync, writeFileSync, readFileSync } from 'node:fs'
15
+ import { scanDirectory } from '../../plugin/scanner.js'
16
+ import {
17
+ writeTsconfigPaths,
18
+ scanComposableExports,
19
+ generateAutoImportDts,
20
+ generateVirtualModuleDts,
21
+ writeAutoImportDts,
22
+ } from '../../plugin/dts-generator.js'
23
+
24
+ const ROOT = '/project'
25
+ const COMPOSABLES_DIR = '/project/app/composables'
26
+
27
+ beforeEach(() => {
28
+ vi.mocked(existsSync).mockReturnValue(false)
29
+ vi.mocked(writeFileSync).mockClear()
30
+ vi.mocked(scanDirectory).mockResolvedValue([])
31
+ vi.mocked(readFileSync).mockReturnValue('')
32
+ })
33
+
34
+ describe('writeTsconfigPaths', () => {
35
+ it('writes cer-tsconfig.json to the root directory', () => {
36
+ writeTsconfigPaths(ROOT, `${ROOT}/app`)
37
+ expect(writeFileSync).toHaveBeenCalledWith(
38
+ `${ROOT}/cer-tsconfig.json`,
39
+ expect.any(String),
40
+ 'utf-8',
41
+ )
42
+ })
43
+
44
+ it('includes ~/\\* path alias in tsconfig', () => {
45
+ writeTsconfigPaths(ROOT, `${ROOT}/app`)
46
+ const content = vi.mocked(writeFileSync).mock.calls[0][1] as string
47
+ expect(content).toContain('~/*')
48
+ })
49
+
50
+ it('includes ~/pages/* path alias', () => {
51
+ writeTsconfigPaths(ROOT, `${ROOT}/app`)
52
+ const content = vi.mocked(writeFileSync).mock.calls[0][1] as string
53
+ expect(content).toContain('~/pages/*')
54
+ })
55
+
56
+ it('generates valid JSON', () => {
57
+ writeTsconfigPaths(ROOT, `${ROOT}/app`)
58
+ const content = vi.mocked(writeFileSync).mock.calls[0][1] as string
59
+ expect(() => JSON.parse(content)).not.toThrow()
60
+ })
61
+
62
+ it('wraps paths in compilerOptions', () => {
63
+ writeTsconfigPaths(ROOT, `${ROOT}/app`)
64
+ const content = vi.mocked(writeFileSync).mock.calls[0][1] as string
65
+ const json = JSON.parse(content)
66
+ expect(json).toHaveProperty('compilerOptions.paths')
67
+ })
68
+ })
69
+
70
+ describe('scanComposableExports', () => {
71
+ it('returns empty map when composablesDir does not exist', async () => {
72
+ const result = await scanComposableExports(COMPOSABLES_DIR)
73
+ expect(result.size).toBe(0)
74
+ })
75
+
76
+ it('returns empty map when no files found', async () => {
77
+ vi.mocked(existsSync).mockReturnValue(true)
78
+ const result = await scanComposableExports(COMPOSABLES_DIR)
79
+ expect(result.size).toBe(0)
80
+ })
81
+
82
+ it('finds exported functions', async () => {
83
+ vi.mocked(existsSync).mockReturnValue(true)
84
+ vi.mocked(scanDirectory).mockResolvedValue([`${COMPOSABLES_DIR}/use-counter.ts`])
85
+ vi.mocked(readFileSync).mockReturnValue('export function useCounter() {}')
86
+ const result = await scanComposableExports(COMPOSABLES_DIR)
87
+ expect(result.has('useCounter')).toBe(true)
88
+ expect(result.get('useCounter')).toBe(`${COMPOSABLES_DIR}/use-counter.ts`)
89
+ })
90
+
91
+ it('finds exported const', async () => {
92
+ vi.mocked(existsSync).mockReturnValue(true)
93
+ vi.mocked(scanDirectory).mockResolvedValue([`${COMPOSABLES_DIR}/use-state.ts`])
94
+ vi.mocked(readFileSync).mockReturnValue('export const useState = () => {}')
95
+ const result = await scanComposableExports(COMPOSABLES_DIR)
96
+ expect(result.has('useState')).toBe(true)
97
+ })
98
+
99
+ it('finds multiple exports from a single file', async () => {
100
+ vi.mocked(existsSync).mockReturnValue(true)
101
+ vi.mocked(scanDirectory).mockResolvedValue([`${COMPOSABLES_DIR}/utils.ts`])
102
+ vi.mocked(readFileSync).mockReturnValue(`
103
+ export function useFoo() {}
104
+ export const useBar = () => {}
105
+ `)
106
+ const result = await scanComposableExports(COMPOSABLES_DIR)
107
+ expect(result.has('useFoo')).toBe(true)
108
+ expect(result.has('useBar')).toBe(true)
109
+ })
110
+
111
+ it('handles multiple files', async () => {
112
+ vi.mocked(existsSync).mockReturnValue(true)
113
+ vi.mocked(scanDirectory).mockResolvedValue([
114
+ `${COMPOSABLES_DIR}/a.ts`,
115
+ `${COMPOSABLES_DIR}/b.ts`,
116
+ ])
117
+ vi.mocked(readFileSync)
118
+ .mockReturnValueOnce('export function useA() {}')
119
+ .mockReturnValueOnce('export function useB() {}')
120
+ const result = await scanComposableExports(COMPOSABLES_DIR)
121
+ expect(result.has('useA')).toBe(true)
122
+ expect(result.has('useB')).toBe(true)
123
+ })
124
+ })
125
+
126
+ describe('generateAutoImportDts', () => {
127
+ it('includes AUTO-GENERATED comment', async () => {
128
+ const dts = await generateAutoImportDts(ROOT, COMPOSABLES_DIR)
129
+ expect(dts).toContain('AUTO-GENERATED')
130
+ })
131
+
132
+ it('declares component as a global', async () => {
133
+ const dts = await generateAutoImportDts(ROOT, COMPOSABLES_DIR)
134
+ expect(dts).toContain("const component: typeof import('@jasonshimmy/custom-elements-runtime')['component']")
135
+ })
136
+
137
+ it('declares html as a global', async () => {
138
+ const dts = await generateAutoImportDts(ROOT, COMPOSABLES_DIR)
139
+ expect(dts).toContain("const html: typeof import('@jasonshimmy/custom-elements-runtime')['html']")
140
+ })
141
+
142
+ it('declares ref as a global', async () => {
143
+ const dts = await generateAutoImportDts(ROOT, COMPOSABLES_DIR)
144
+ expect(dts).toContain("const ref: typeof import('@jasonshimmy/custom-elements-runtime')['ref']")
145
+ })
146
+
147
+ it('declares useHead as a framework global', async () => {
148
+ const dts = await generateAutoImportDts(ROOT, COMPOSABLES_DIR)
149
+ expect(dts).toContain("const useHead: typeof import('@jasonshimmy/vite-plugin-cer-app/composables')['useHead']")
150
+ })
151
+
152
+ it('declares usePageData as a framework global', async () => {
153
+ const dts = await generateAutoImportDts(ROOT, COMPOSABLES_DIR)
154
+ expect(dts).toContain("const usePageData: typeof import('@jasonshimmy/vite-plugin-cer-app/composables')['usePageData']")
155
+ })
156
+
157
+ it('declares when directive as a global', async () => {
158
+ const dts = await generateAutoImportDts(ROOT, COMPOSABLES_DIR)
159
+ expect(dts).toContain("const when: typeof import('@jasonshimmy/custom-elements-runtime/directives')['when']")
160
+ })
161
+
162
+ it('declares __CER_DATA__ global variable', async () => {
163
+ const dts = await generateAutoImportDts(ROOT, COMPOSABLES_DIR)
164
+ expect(dts).toContain('var __CER_DATA__')
165
+ })
166
+
167
+ it('wraps declarations in declare global block', async () => {
168
+ const dts = await generateAutoImportDts(ROOT, COMPOSABLES_DIR)
169
+ expect(dts).toContain('declare global {')
170
+ expect(dts).toContain('}')
171
+ })
172
+
173
+ it('includes user composable exports when provided', async () => {
174
+ const exports = new Map([['useMyThing', `${COMPOSABLES_DIR}/my-thing.ts`]])
175
+ const dts = await generateAutoImportDts(ROOT, COMPOSABLES_DIR, exports)
176
+ expect(dts).toContain('useMyThing')
177
+ })
178
+
179
+ it('uses relative path for user composables', async () => {
180
+ const exports = new Map([['useFoo', `${ROOT}/app/composables/foo.ts`]])
181
+ const dts = await generateAutoImportDts(ROOT, COMPOSABLES_DIR, exports)
182
+ // Path should be relative from root
183
+ expect(dts).toContain('./app/composables/foo')
184
+ })
185
+ })
186
+
187
+ describe('generateVirtualModuleDts', () => {
188
+ it('includes AUTO-GENERATED comment', async () => {
189
+ const dts = await generateVirtualModuleDts(ROOT, COMPOSABLES_DIR)
190
+ expect(dts).toContain('AUTO-GENERATED')
191
+ })
192
+
193
+ it('declares virtual:cer-routes module', async () => {
194
+ const dts = await generateVirtualModuleDts(ROOT, COMPOSABLES_DIR)
195
+ expect(dts).toContain("declare module 'virtual:cer-routes'")
196
+ })
197
+
198
+ it('declares virtual:cer-layouts module', async () => {
199
+ const dts = await generateVirtualModuleDts(ROOT, COMPOSABLES_DIR)
200
+ expect(dts).toContain("declare module 'virtual:cer-layouts'")
201
+ })
202
+
203
+ it('declares virtual:cer-plugins module', async () => {
204
+ const dts = await generateVirtualModuleDts(ROOT, COMPOSABLES_DIR)
205
+ expect(dts).toContain("declare module 'virtual:cer-plugins'")
206
+ })
207
+
208
+ it('declares virtual:cer-loading module with hasLoading and loadingTag', async () => {
209
+ const dts = await generateVirtualModuleDts(ROOT, COMPOSABLES_DIR)
210
+ expect(dts).toContain("declare module 'virtual:cer-loading'")
211
+ expect(dts).toContain('hasLoading')
212
+ expect(dts).toContain('loadingTag')
213
+ })
214
+
215
+ it('declares virtual:cer-error module with hasError and errorTag', async () => {
216
+ const dts = await generateVirtualModuleDts(ROOT, COMPOSABLES_DIR)
217
+ expect(dts).toContain("declare module 'virtual:cer-error'")
218
+ expect(dts).toContain('hasError')
219
+ expect(dts).toContain('errorTag')
220
+ })
221
+
222
+ it('includes user composable re-exports in virtual:cer-composables', async () => {
223
+ const exports = new Map([['useMyThing', `${ROOT}/app/composables/my-thing.ts`]])
224
+ const dts = await generateVirtualModuleDts(ROOT, COMPOSABLES_DIR, exports)
225
+ expect(dts).toContain('useMyThing')
226
+ })
227
+ })
228
+
229
+ describe('writeAutoImportDts', () => {
230
+ it('writes cer-auto-imports.d.ts to root', async () => {
231
+ await writeAutoImportDts(ROOT, COMPOSABLES_DIR)
232
+ const paths = vi.mocked(writeFileSync).mock.calls.map(([p]) => String(p))
233
+ expect(paths.some(p => p.includes('cer-auto-imports.d.ts'))).toBe(true)
234
+ })
235
+
236
+ it('writes cer-env.d.ts to root', async () => {
237
+ await writeAutoImportDts(ROOT, COMPOSABLES_DIR)
238
+ const paths = vi.mocked(writeFileSync).mock.calls.map(([p]) => String(p))
239
+ expect(paths.some(p => p.includes('cer-env.d.ts'))).toBe(true)
240
+ })
241
+
242
+ it('writes exactly two files', async () => {
243
+ await writeAutoImportDts(ROOT, COMPOSABLES_DIR)
244
+ expect(writeFileSync).toHaveBeenCalledTimes(2)
245
+ })
246
+ })
@@ -0,0 +1,158 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { resolveConfig } from '../../plugin/index.js'
3
+
4
+ const ROOT = '/project'
5
+
6
+ describe('resolveConfig', () => {
7
+ it('defaults mode to "spa"', () => {
8
+ const cfg = resolveConfig({}, ROOT)
9
+ expect(cfg.mode).toBe('spa')
10
+ })
11
+
12
+ it('respects explicit mode', () => {
13
+ expect(resolveConfig({ mode: 'ssr' }, ROOT).mode).toBe('ssr')
14
+ expect(resolveConfig({ mode: 'ssg' }, ROOT).mode).toBe('ssg')
15
+ })
16
+
17
+ it('defaults srcDir to <root>/app', () => {
18
+ const cfg = resolveConfig({}, ROOT)
19
+ expect(cfg.srcDir).toBe('/project/app')
20
+ })
21
+
22
+ it('respects explicit srcDir', () => {
23
+ const cfg = resolveConfig({ srcDir: 'src' }, ROOT)
24
+ expect(cfg.srcDir).toBe('/project/src')
25
+ })
26
+
27
+ it('derives pagesDir from srcDir', () => {
28
+ const cfg = resolveConfig({}, ROOT)
29
+ expect(cfg.pagesDir).toBe('/project/app/pages')
30
+ })
31
+
32
+ it('derives layoutsDir from srcDir', () => {
33
+ const cfg = resolveConfig({}, ROOT)
34
+ expect(cfg.layoutsDir).toBe('/project/app/layouts')
35
+ })
36
+
37
+ it('derives componentsDir from srcDir', () => {
38
+ const cfg = resolveConfig({}, ROOT)
39
+ expect(cfg.componentsDir).toBe('/project/app/components')
40
+ })
41
+
42
+ it('derives composablesDir from srcDir', () => {
43
+ const cfg = resolveConfig({}, ROOT)
44
+ expect(cfg.composablesDir).toBe('/project/app/composables')
45
+ })
46
+
47
+ it('derives pluginsDir from srcDir', () => {
48
+ const cfg = resolveConfig({}, ROOT)
49
+ expect(cfg.pluginsDir).toBe('/project/app/plugins')
50
+ })
51
+
52
+ it('derives middlewareDir from srcDir', () => {
53
+ const cfg = resolveConfig({}, ROOT)
54
+ expect(cfg.middlewareDir).toBe('/project/app/middleware')
55
+ })
56
+
57
+ it('derives serverApiDir from root (not srcDir)', () => {
58
+ const cfg = resolveConfig({}, ROOT)
59
+ expect(cfg.serverApiDir).toBe('/project/server/api')
60
+ })
61
+
62
+ it('derives serverMiddlewareDir from root (not srcDir)', () => {
63
+ const cfg = resolveConfig({}, ROOT)
64
+ expect(cfg.serverMiddlewareDir).toBe('/project/server/middleware')
65
+ })
66
+
67
+ it('defaults port to 3000', () => {
68
+ const cfg = resolveConfig({}, ROOT)
69
+ expect(cfg.port).toBe(3000)
70
+ })
71
+
72
+ it('respects explicit port', () => {
73
+ const cfg = resolveConfig({ port: 4000 }, ROOT)
74
+ expect(cfg.port).toBe(4000)
75
+ })
76
+
77
+ it('defaults ssr.dsd to true', () => {
78
+ const cfg = resolveConfig({}, ROOT)
79
+ expect(cfg.ssr.dsd).toBe(true)
80
+ })
81
+
82
+ it('respects explicit ssr.dsd=false', () => {
83
+ const cfg = resolveConfig({ ssr: { dsd: false } }, ROOT)
84
+ expect(cfg.ssr.dsd).toBe(false)
85
+ })
86
+
87
+ it('defaults ssr.streaming to false', () => {
88
+ const cfg = resolveConfig({}, ROOT)
89
+ expect(cfg.ssr.streaming).toBe(false)
90
+ })
91
+
92
+ it('defaults ssg.routes to "auto"', () => {
93
+ const cfg = resolveConfig({}, ROOT)
94
+ expect(cfg.ssg.routes).toBe('auto')
95
+ })
96
+
97
+ it('respects explicit ssg.routes array', () => {
98
+ const cfg = resolveConfig({ ssg: { routes: ['/a', '/b'] } }, ROOT)
99
+ expect(cfg.ssg.routes).toEqual(['/a', '/b'])
100
+ })
101
+
102
+ it('defaults ssg.concurrency to 4', () => {
103
+ const cfg = resolveConfig({}, ROOT)
104
+ expect(cfg.ssg.concurrency).toBe(4)
105
+ })
106
+
107
+ it('defaults ssg.fallback to false', () => {
108
+ const cfg = resolveConfig({}, ROOT)
109
+ expect(cfg.ssg.fallback).toBe(false)
110
+ })
111
+
112
+ it('defaults autoImports.components to true', () => {
113
+ const cfg = resolveConfig({}, ROOT)
114
+ expect(cfg.autoImports.components).toBe(true)
115
+ })
116
+
117
+ it('defaults autoImports.composables to true', () => {
118
+ const cfg = resolveConfig({}, ROOT)
119
+ expect(cfg.autoImports.composables).toBe(true)
120
+ })
121
+
122
+ it('defaults autoImports.directives to true', () => {
123
+ const cfg = resolveConfig({}, ROOT)
124
+ expect(cfg.autoImports.directives).toBe(true)
125
+ })
126
+
127
+ it('defaults autoImports.runtime to true', () => {
128
+ const cfg = resolveConfig({}, ROOT)
129
+ expect(cfg.autoImports.runtime).toBe(true)
130
+ })
131
+
132
+ it('sets jitCss.content defaults relative to srcDir', () => {
133
+ const cfg = resolveConfig({}, ROOT)
134
+ expect(cfg.jitCss.content).toContain('/project/app/pages/**/*.ts')
135
+ expect(cfg.jitCss.content).toContain('/project/app/components/**/*.ts')
136
+ expect(cfg.jitCss.content).toContain('/project/app/layouts/**/*.ts')
137
+ })
138
+
139
+ it('defaults jitCss.extendedColors to false', () => {
140
+ const cfg = resolveConfig({}, ROOT)
141
+ expect(cfg.jitCss.extendedColors).toBe(false)
142
+ })
143
+
144
+ it('passes router.base through', () => {
145
+ const cfg = resolveConfig({ router: { base: '/app', scrollToFragment: false } }, ROOT)
146
+ expect(cfg.router.base).toBe('/app')
147
+ })
148
+
149
+ it('passes router.scrollToFragment through', () => {
150
+ const cfg = resolveConfig({ router: { base: undefined, scrollToFragment: true } }, ROOT)
151
+ expect(cfg.router.scrollToFragment).toBe(true)
152
+ })
153
+
154
+ it('includes root in the resolved config', () => {
155
+ const cfg = resolveConfig({}, ROOT)
156
+ expect(cfg.root).toBe(ROOT)
157
+ })
158
+ })
@@ -6,7 +6,7 @@ const opts = { srcDir }
6
6
 
7
7
  const RUNTIME_PKG = `'@jasonshimmy/custom-elements-runtime'`
8
8
  const DIRECTIVES_PKG = `'@jasonshimmy/custom-elements-runtime/directives'`
9
- const FRAMEWORK_PKG = `'vite-plugin-cer-app/composables'`
9
+ const FRAMEWORK_PKG = `'@jasonshimmy/vite-plugin-cer-app/composables'`
10
10
 
11
11
  // ─── Target-directory gating ─────────────────────────────────────────────────
12
12
 
@@ -29,7 +29,7 @@ describe('generateComponentsCode', () => {
29
29
  it('includes AUTO-GENERATED comment', async () => {
30
30
  vi.mocked(scanDirectory).mockResolvedValue([`${COMPONENTS}/my-button.ts`])
31
31
  const code = await generateComponentsCode(COMPONENTS)
32
- expect(code).toContain('AUTO-GENERATED by vite-plugin-cer-app')
32
+ expect(code).toContain('AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app')
33
33
  })
34
34
 
35
35
  it('generates side-effect imports for each component file', async () => {
@@ -29,7 +29,7 @@ describe('generateComposablesCode', () => {
29
29
  it('includes AUTO-GENERATED comment', async () => {
30
30
  vi.mocked(scanDirectory).mockResolvedValue([`${COMPOSABLES}/useTheme.ts`])
31
31
  const code = await generateComposablesCode(COMPOSABLES)
32
- expect(code).toContain('AUTO-GENERATED by vite-plugin-cer-app')
32
+ expect(code).toContain('AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app')
33
33
  })
34
34
 
35
35
  it('generates export * from for each composable file', async () => {
@@ -0,0 +1,71 @@
1
+ import { vi, describe, it, expect, beforeEach } from 'vitest'
2
+
3
+ vi.mock('node:fs', () => ({ existsSync: vi.fn() }))
4
+
5
+ import { existsSync } from 'node:fs'
6
+ import { generateErrorCode } from '../../../plugin/virtual/error.js'
7
+
8
+ const SRC_DIR = '/project/app'
9
+
10
+ describe('generateErrorCode', () => {
11
+ beforeEach(() => {
12
+ vi.mocked(existsSync).mockReturnValue(false)
13
+ })
14
+
15
+ it('returns falsy exports when error.ts does not exist', async () => {
16
+ const code = await generateErrorCode(SRC_DIR)
17
+ expect(code).toContain('export const hasError = false')
18
+ expect(code).toContain('export const errorTag = null')
19
+ })
20
+
21
+ it('includes AUTO-GENERATED comment when no error file', async () => {
22
+ const code = await generateErrorCode(SRC_DIR)
23
+ expect(code).toContain('AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app')
24
+ })
25
+
26
+ it('returns truthy exports when error.ts exists', async () => {
27
+ vi.mocked(existsSync).mockReturnValue(true)
28
+ const code = await generateErrorCode(SRC_DIR)
29
+ expect(code).toContain('export const hasError = true')
30
+ expect(code).toContain("export const errorTag = 'page-error'")
31
+ })
32
+
33
+ it('imports the error file when it exists', async () => {
34
+ vi.mocked(existsSync).mockReturnValue(true)
35
+ const code = await generateErrorCode(SRC_DIR)
36
+ expect(code).toContain(`${SRC_DIR}/error.ts`)
37
+ })
38
+
39
+ it('includes AUTO-GENERATED comment when error file exists', async () => {
40
+ vi.mocked(existsSync).mockReturnValue(true)
41
+ const code = await generateErrorCode(SRC_DIR)
42
+ expect(code).toContain('AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app')
43
+ })
44
+
45
+ it('checks for error.ts specifically (not error.js)', async () => {
46
+ await generateErrorCode(SRC_DIR)
47
+ expect(existsSync).toHaveBeenCalledWith(`${SRC_DIR}/error.ts`)
48
+ })
49
+
50
+ it('does not include hasError=true when file is missing', async () => {
51
+ const code = await generateErrorCode(SRC_DIR)
52
+ expect(code).not.toContain('hasError = true')
53
+ })
54
+
55
+ it('does not include hasError=false when file exists', async () => {
56
+ vi.mocked(existsSync).mockReturnValue(true)
57
+ const code = await generateErrorCode(SRC_DIR)
58
+ expect(code).not.toContain('hasError = false')
59
+ })
60
+
61
+ it('ends with a newline', async () => {
62
+ const code = await generateErrorCode(SRC_DIR)
63
+ expect(code.endsWith('\n')).toBe(true)
64
+ })
65
+
66
+ it('ends with a newline when file exists', async () => {
67
+ vi.mocked(existsSync).mockReturnValue(true)
68
+ const code = await generateErrorCode(SRC_DIR)
69
+ expect(code.endsWith('\n')).toBe(true)
70
+ })
71
+ })
@@ -30,7 +30,7 @@ describe('generateLayoutsCode', () => {
30
30
 
31
31
  it('includes AUTO-GENERATED comment', async () => {
32
32
  const code = await generateLayoutsCode(LAYOUTS)
33
- expect(code).toContain('AUTO-GENERATED by vite-plugin-cer-app')
33
+ expect(code).toContain('AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app')
34
34
  })
35
35
 
36
36
  it('generates a side-effect import for each layout file', async () => {
@@ -0,0 +1,72 @@
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 { generateLoadingCode } from '../../../plugin/virtual/loading.js'
8
+
9
+ const SRC_DIR = '/project/app'
10
+
11
+ describe('generateLoadingCode', () => {
12
+ beforeEach(() => {
13
+ vi.mocked(existsSync).mockReturnValue(false)
14
+ })
15
+
16
+ it('returns falsy exports when loading.ts does not exist', async () => {
17
+ const code = await generateLoadingCode(SRC_DIR)
18
+ expect(code).toContain('export const hasLoading = false')
19
+ expect(code).toContain('export const loadingTag = null')
20
+ })
21
+
22
+ it('includes AUTO-GENERATED comment when no loading file', async () => {
23
+ const code = await generateLoadingCode(SRC_DIR)
24
+ expect(code).toContain('AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app')
25
+ })
26
+
27
+ it('returns truthy exports when loading.ts exists', async () => {
28
+ vi.mocked(existsSync).mockReturnValue(true)
29
+ const code = await generateLoadingCode(SRC_DIR)
30
+ expect(code).toContain('export const hasLoading = true')
31
+ expect(code).toContain("export const loadingTag = 'page-loading'")
32
+ })
33
+
34
+ it('imports the loading file when it exists', async () => {
35
+ vi.mocked(existsSync).mockReturnValue(true)
36
+ const code = await generateLoadingCode(SRC_DIR)
37
+ expect(code).toContain(`${SRC_DIR}/loading.ts`)
38
+ })
39
+
40
+ it('includes AUTO-GENERATED comment when loading file exists', async () => {
41
+ vi.mocked(existsSync).mockReturnValue(true)
42
+ const code = await generateLoadingCode(SRC_DIR)
43
+ expect(code).toContain('AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app')
44
+ })
45
+
46
+ it('checks for loading.ts specifically', async () => {
47
+ await generateLoadingCode(SRC_DIR)
48
+ expect(existsSync).toHaveBeenCalledWith(`${SRC_DIR}/loading.ts`)
49
+ })
50
+
51
+ it('does not include hasLoading=true when file is missing', async () => {
52
+ const code = await generateLoadingCode(SRC_DIR)
53
+ expect(code).not.toContain('hasLoading = true')
54
+ })
55
+
56
+ it('does not include hasLoading=false when file exists', async () => {
57
+ vi.mocked(existsSync).mockReturnValue(true)
58
+ const code = await generateLoadingCode(SRC_DIR)
59
+ expect(code).not.toContain('hasLoading = false')
60
+ })
61
+
62
+ it('ends with a newline when file is missing', async () => {
63
+ const code = await generateLoadingCode(SRC_DIR)
64
+ expect(code.endsWith('\n')).toBe(true)
65
+ })
66
+
67
+ it('ends with a newline when file exists', async () => {
68
+ vi.mocked(existsSync).mockReturnValue(true)
69
+ const code = await generateLoadingCode(SRC_DIR)
70
+ expect(code.endsWith('\n')).toBe(true)
71
+ })
72
+ })
@@ -30,7 +30,7 @@ describe('generateMiddlewareCode', () => {
30
30
  it('includes AUTO-GENERATED comment', async () => {
31
31
  vi.mocked(scanDirectory).mockResolvedValue([`${MIDDLEWARE}/auth.ts`])
32
32
  const code = await generateMiddlewareCode(MIDDLEWARE)
33
- expect(code).toContain('AUTO-GENERATED by vite-plugin-cer-app')
33
+ expect(code).toContain('AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app')
34
34
  })
35
35
 
36
36
  it('generates default import and registers under filename key', async () => {
@@ -30,7 +30,7 @@ describe('generatePluginsCode', () => {
30
30
  it('includes AUTO-GENERATED comment', async () => {
31
31
  vi.mocked(scanDirectory).mockResolvedValue([`${PLUGINS}/store.ts`])
32
32
  const code = await generatePluginsCode(PLUGINS)
33
- expect(code).toContain('AUTO-GENERATED by vite-plugin-cer-app')
33
+ expect(code).toContain('AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app')
34
34
  })
35
35
 
36
36
  it('generates default import with alias _p0 for first plugin', async () => {
@@ -33,7 +33,7 @@ describe('generateRoutesCode', () => {
33
33
 
34
34
  it('includes AUTO-GENERATED comment', async () => {
35
35
  const code = await generateRoutesCode(PAGES)
36
- expect(code).toContain('AUTO-GENERATED by vite-plugin-cer-app')
36
+ expect(code).toContain('AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app')
37
37
  })
38
38
 
39
39
  it('generates a lazy load() route for a static page', async () => {
@@ -30,7 +30,7 @@ describe('generateServerApiCode', () => {
30
30
  it('includes AUTO-GENERATED comment', async () => {
31
31
  vi.mocked(scanDirectory).mockResolvedValue([`${API}/health.ts`])
32
32
  const code = await generateServerApiCode(API)
33
- expect(code).toContain('AUTO-GENERATED by vite-plugin-cer-app')
33
+ expect(code).toContain('AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app')
34
34
  })
35
35
 
36
36
  it('prefixes route path with /api/', async () => {