polen 0.9.0-next.2 → 0.9.0-next.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/build/api/server/report-error.d.ts +2 -0
  2. package/build/api/server/report-error.d.ts.map +1 -0
  3. package/build/api/server/report-error.js +47 -0
  4. package/build/api/server/report-error.js.map +1 -0
  5. package/build/api/vite/data/navbar.d.ts +9 -0
  6. package/build/api/vite/data/navbar.d.ts.map +1 -0
  7. package/build/api/vite/data/navbar.js +6 -0
  8. package/build/api/vite/data/navbar.js.map +1 -0
  9. package/build/api/vite/plugins/core.d.ts.map +1 -1
  10. package/build/api/vite/plugins/core.js +36 -104
  11. package/build/api/vite/plugins/core.js.map +1 -1
  12. package/build/api/vite/plugins/pages.d.ts +25 -0
  13. package/build/api/vite/plugins/pages.d.ts.map +1 -0
  14. package/build/api/vite/plugins/pages.js +273 -0
  15. package/build/api/vite/plugins/pages.js.map +1 -0
  16. package/build/api/vite/plugins/serve.d.ts.map +1 -1
  17. package/build/api/vite/plugins/serve.js +5 -26
  18. package/build/api/vite/plugins/serve.js.map +1 -1
  19. package/build/cli/commands/dev.js +9 -1
  20. package/build/cli/commands/dev.js.map +1 -1
  21. package/build/lib/debug/environment-variable.d.ts +1 -0
  22. package/build/lib/debug/environment-variable.d.ts.map +1 -1
  23. package/build/lib/debug/environment-variable.js +30 -15
  24. package/build/lib/debug/environment-variable.js.map +1 -1
  25. package/build/lib/extensible-data/extensible-data.d.ts +17 -0
  26. package/build/lib/extensible-data/extensible-data.d.ts.map +1 -0
  27. package/build/lib/extensible-data/extensible-data.js +24 -0
  28. package/build/lib/extensible-data/extensible-data.js.map +1 -0
  29. package/build/lib/extensible-data/index.d.ts +2 -0
  30. package/build/lib/extensible-data/index.d.ts.map +1 -0
  31. package/build/lib/extensible-data/index.js +2 -0
  32. package/build/lib/extensible-data/index.js.map +1 -0
  33. package/build/lib/kit-temp.d.ts +2 -0
  34. package/build/lib/kit-temp.d.ts.map +1 -1
  35. package/build/lib/kit-temp.js +10 -1
  36. package/build/lib/kit-temp.js.map +1 -1
  37. package/build/lib/react-router-loader/react-router-loader.d.ts.map +1 -1
  38. package/build/lib/react-router-loader/react-router-loader.js +0 -1
  39. package/build/lib/react-router-loader/react-router-loader.js.map +1 -1
  40. package/build/lib/vite-plugin-json/index.d.ts +2 -0
  41. package/build/lib/vite-plugin-json/index.d.ts.map +1 -0
  42. package/build/lib/vite-plugin-json/index.js +2 -0
  43. package/build/lib/vite-plugin-json/index.js.map +1 -0
  44. package/build/lib/vite-plugin-json/vite-plugin-json.d.ts +64 -0
  45. package/build/lib/vite-plugin-json/vite-plugin-json.d.ts.map +1 -0
  46. package/build/lib/vite-plugin-json/vite-plugin-json.js +59 -0
  47. package/build/lib/vite-plugin-json/vite-plugin-json.js.map +1 -0
  48. package/build/lib/vite-plugin-reactive-data/index.d.ts +2 -0
  49. package/build/lib/vite-plugin-reactive-data/index.d.ts.map +1 -0
  50. package/build/lib/vite-plugin-reactive-data/index.js +2 -0
  51. package/build/lib/vite-plugin-reactive-data/index.js.map +1 -0
  52. package/build/lib/vite-plugin-reactive-data/vite-plugin-reactive-data.d.ts +39 -0
  53. package/build/lib/vite-plugin-reactive-data/vite-plugin-reactive-data.d.ts.map +1 -0
  54. package/build/lib/vite-plugin-reactive-data/vite-plugin-reactive-data.js +92 -0
  55. package/build/lib/vite-plugin-reactive-data/vite-plugin-reactive-data.js.map +1 -0
  56. package/build/lib/vite-plugins/build-logger.d.ts.map +1 -1
  57. package/build/lib/vite-plugins/build-logger.js +9 -8
  58. package/build/lib/vite-plugins/build-logger.js.map +1 -1
  59. package/build/project-data.d.ts +0 -11
  60. package/build/project-data.d.ts.map +1 -1
  61. package/build/template/components/Link.d.ts.map +1 -1
  62. package/build/template/components/Link.jsx +4 -3
  63. package/build/template/components/Link.jsx.map +1 -1
  64. package/build/template/routes/changelog.jsx +1 -1
  65. package/build/template/routes/changelog.jsx.map +1 -1
  66. package/build/template/routes/reference.jsx +1 -1
  67. package/build/template/routes/reference.jsx.map +1 -1
  68. package/build/template/routes/root.d.ts.map +1 -1
  69. package/build/template/routes/root.jsx +5 -3
  70. package/build/template/routes/root.jsx.map +1 -1
  71. package/build/template/server/app.js +1 -1
  72. package/build/template/server/app.js.map +1 -1
  73. package/build/template/server/render-page.d.ts.map +1 -1
  74. package/build/template/server/render-page.jsx +4 -1
  75. package/build/template/server/render-page.jsx.map +1 -1
  76. package/build/template/server/ssg/generate.js +1 -1
  77. package/build/template/server/ssg/generate.js.map +1 -1
  78. package/build/template/server/ssg/get-route-paths.js +1 -1
  79. package/build/template/server/ssg/get-route-paths.js.map +1 -1
  80. package/package.json +3 -1
  81. package/src/api/server/report-error.ts +61 -0
  82. package/src/api/vite/data/navbar.ts +15 -0
  83. package/src/api/vite/plugins/core.ts +38 -116
  84. package/src/api/vite/plugins/pages.ts +332 -0
  85. package/src/api/vite/plugins/serve.ts +5 -26
  86. package/src/cli/commands/dev.ts +9 -1
  87. package/src/lib/debug/environment-variable.ts +31 -14
  88. package/src/lib/extensible-data/extensible-data.ts +38 -0
  89. package/src/lib/extensible-data/index.ts +1 -0
  90. package/src/lib/kit-temp.ts +12 -1
  91. package/src/lib/react-router-loader/react-router-loader.ts +0 -1
  92. package/src/lib/vite-plugin-json/index.ts +1 -0
  93. package/src/lib/vite-plugin-json/vite-plugin-json.ts +128 -0
  94. package/src/lib/vite-plugin-reactive-data/index.ts +1 -0
  95. package/src/lib/vite-plugin-reactive-data/vite-plugin-reactive-data.ts +131 -0
  96. package/src/lib/vite-plugins/build-logger.ts +10 -8
  97. package/src/project-data.ts +0 -13
  98. package/src/template/components/Link.tsx +6 -3
  99. package/src/template/routes/changelog.tsx +1 -1
  100. package/src/template/routes/reference.tsx +1 -1
  101. package/src/template/routes/root.tsx +5 -3
  102. package/src/template/server/app.ts +1 -1
  103. package/src/template/server/render-page.tsx +4 -1
  104. package/src/template/server/ssg/generate.ts +1 -1
  105. package/src/template/server/ssg/get-route-paths.ts +1 -1
  106. package/build/api/vite/plugins/pages-tree.d.ts +0 -16
  107. package/build/api/vite/plugins/pages-tree.d.ts.map +0 -1
  108. package/build/api/vite/plugins/pages-tree.js +0 -153
  109. package/build/api/vite/plugins/pages-tree.js.map +0 -1
  110. package/src/api/vite/plugins/pages-tree.ts +0 -187
@@ -1,38 +1,32 @@
1
1
  import type { Config } from '#api/config/index'
2
+ import { NavbarData } from '#api/vite/data/navbar'
2
3
  import { VitePluginSelfContainedMode } from '#cli/_/self-contained-mode'
3
4
  import type { ReactRouter } from '#dep/react-router/index'
4
5
  import type { Vite } from '#dep/vite/index'
5
- import { reportDiagnostics } from '#lib/file-router/diagnostic-reporter'
6
- import { FileRouter } from '#lib/file-router/index'
7
- import { Tree } from '#lib/tree/index'
6
+ import { VitePluginJson } from '#lib/vite-plugin-json/index'
7
+ import { VitePluginReactiveData } from '#lib/vite-plugin-reactive-data/index'
8
8
  import { ViteVirtual } from '#lib/vite-virtual/index'
9
9
  import { debug } from '#singletons/debug'
10
+ import { superjson } from '#singletons/superjson'
10
11
  import { Json, Str } from '@wollybeard/kit'
11
- import jsesc from 'jsesc'
12
- import type { ProjectData, SidebarIndex, SiteNavigationItem } from '../../../project-data.ts'
13
- import { superjson } from '../../../singletons/superjson.ts'
12
+ import type { ProjectData } from '../../../project-data.ts'
14
13
  import { SchemaAugmentation } from '../../schema-augmentation/index.ts'
15
14
  import { Schema } from '../../schema/index.ts'
16
15
  import { createLogger } from '../logger.ts'
17
16
  import { polenVirtual } from '../vi.ts'
18
- import { createPagesPlugin, getRouteTree } from './pages-tree.ts'
17
+ import { Pages } from './pages.ts'
19
18
 
20
19
  const _debug = debug.sub(`vite-plugin-core`)
21
20
 
22
21
  const viTemplateVariables = polenVirtual([`template`, `variables`])
23
22
  const viTemplateSchemaAugmentations = polenVirtual([`template`, `schema-augmentations`])
24
- const viProjectData = polenVirtual([`project`, `data`])
23
+ const viProjectData = polenVirtual([`project`, `data.jsonsuper`], { allowPluginProcessing: true })
25
24
 
26
25
  export interface ProjectPagesModule {
27
26
  pages: ReactRouter.RouteObject[]
28
27
  }
29
28
 
30
29
  export const Core = (config: Config.Config): Vite.PluginOption[] => {
31
- // State for current pages data (updated by pages plugin)
32
- let currentPagesData: FileRouter.ScanResult | null = null
33
- let currentTreeData: FileRouter.RouteTreeNode | null = null
34
- let viteDevServer: Vite.ViteDevServer | null = null
35
-
36
30
  // Schema cache management
37
31
  let schemaCache: Awaited<ReturnType<typeof Schema.readOrThrow>> | null = null
38
32
 
@@ -52,6 +46,7 @@ export const Core = (config: Config.Config): Vite.PluginOption[] => {
52
46
  }
53
47
 
54
48
  const plugins: Vite.Plugin[] = []
49
+ const navbarData = NavbarData()
55
50
 
56
51
  // Note: The main use for this right now is to resolve the react imports
57
52
  // from the mdx vite plugin which have to go through the Polen exports since Polen keeps those deps bundled.
@@ -64,26 +59,19 @@ export const Core = (config: Config.Config): Vite.PluginOption[] => {
64
59
  }))
65
60
  }
66
61
 
62
+ const json = VitePluginJson.create({
63
+ codec: {
64
+ validate: superjson,
65
+ importPath: import.meta.resolve('#singletons/superjson'),
66
+ importExport: 'superjson',
67
+ },
68
+ filter: {
69
+ moduleTypes: ['jsonsuper'],
70
+ },
71
+ })
72
+
67
73
  return [
68
74
  ...plugins,
69
-
70
- // Self-contained pages plugin
71
- ...createPagesPlugin({
72
- config,
73
- onPagesChange: (pages) => {
74
- currentPagesData = pages
75
- // Invalidate project data virtual module to regenerate navigation/sidebar
76
- if (viteDevServer) {
77
- const projectDataModule = viteDevServer.moduleGraph.getModuleById(viProjectData.resolved)
78
- if (projectDataModule) {
79
- viteDevServer.moduleGraph.invalidateModule(projectDataModule)
80
- }
81
- }
82
- },
83
- onTreeChange: (tree) => {
84
- currentTreeData = tree
85
- },
86
- }),
87
75
  /**
88
76
  * If a `polen*` import is encountered from the user's project, resolve it to the currently
89
77
  * running source code of Polen rather than the user's node_modules.
@@ -95,9 +83,9 @@ export const Core = (config: Config.Config): Vite.PluginOption[] => {
95
83
  * 2. Secondary: Using Polen CLI on a project that does not have Polen installed.
96
84
  * (User would likely not want to do this because they would not be able to achieve type safety)
97
85
  */
98
-
99
86
  {
100
87
  name: `polen:internal-import-alias`,
88
+ enforce: 'pre' as const,
101
89
  resolveId(id, importer) {
102
90
  const d = debug.sub(`vite-plugin:internal-import-alias`)
103
91
 
@@ -133,11 +121,20 @@ export const Core = (config: Config.Config): Vite.PluginOption[] => {
133
121
  return to
134
122
  },
135
123
  },
124
+ json,
125
+ VitePluginReactiveData.create({
126
+ moduleId: `virtual:polen/project/data/navbar`,
127
+ data: navbarData.value,
128
+ codec: superjson,
129
+ name: `polen-navbar`,
130
+ moduleType: 'jsonsuper',
131
+ }),
132
+ ...Pages({
133
+ config,
134
+ navbarData,
135
+ }),
136
136
  {
137
137
  name: `polen:core`,
138
- configureServer(server) {
139
- viteDevServer = server
140
- },
141
138
  config(_, { command }) {
142
139
  // isServing = command === `serve`
143
140
  return {
@@ -187,81 +184,15 @@ export const Core = (config: Config.Config): Vite.PluginOption[] => {
187
184
  identifier: viProjectData,
188
185
  async loader() {
189
186
  _debug(`loadingViProjectDataVirtualModule`)
190
- // todo: parallel
191
187
  const schema = await readSchema()
192
188
 
193
- // Get pages data from the pages plugin or load initially
194
- if (!currentPagesData) {
195
- _debug(`loadingPagesDataInitially`)
196
- currentPagesData = await FileRouter.scan({
197
- dir: config.paths.project.absolute.pages,
198
- glob: `**/*.{md,mdx}`,
199
- })
200
- // Report any diagnostics from initial scan
201
- reportDiagnostics(currentPagesData.diagnostics)
202
- }
203
- if (!currentTreeData) {
204
- _debug(`loadingTreeDataInitially`)
205
- currentTreeData = await getRouteTree(config)
206
- }
207
- const pagesScanResult = currentPagesData
208
- const routeTree = currentTreeData
209
- _debug(`usingPageRoutesFromPagesPlugin`, pagesScanResult.routes.length)
210
-
211
- const siteNavigationItems: SiteNavigationItem[] = []
212
-
213
- //
214
- // ━━ Build Navbar
215
- //
216
-
217
- // Process first-level children as navigation items
218
- for (const child of routeTree.children) {
219
- if (child.value.type === 'directory') {
220
- // Check if this directory has an index file
221
- const hasIndex = child.children.some(c => c.value.type === 'file' && c.value.name === 'index')
222
-
223
- if (hasIndex) {
224
- const pathExp = FileRouter.pathToExpression([child.value.name])
225
- const title = Str.titlizeSlug(child.value.name)
226
- siteNavigationItems.push({
227
- pathExp: pathExp.startsWith('/') ? pathExp.slice(1) : pathExp,
228
- title,
229
- })
230
- }
231
- } else if (child.value.type === 'file' && child.value.name !== 'index') {
232
- const pathExp = FileRouter.pathToExpression([child.value.name])
233
- const title = Str.titlizeSlug(child.value.name)
234
- siteNavigationItems.push({
235
- pathExp: pathExp.startsWith('/') ? pathExp.slice(1) : pathExp,
236
- title,
237
- })
238
- }
239
- }
240
-
241
189
  // ━ Schema presence causes adding some navbar items
190
+ const schemaNavbar = navbarData.get('schema')
191
+ schemaNavbar.length = 0 // Clear existing
242
192
  if (schema) {
243
- siteNavigationItems.push({ pathExp: `reference`, title: `Reference` })
193
+ schemaNavbar.push({ pathExp: `reference`, title: `Reference` })
244
194
  if (schema.versions.length > 1) {
245
- siteNavigationItems.push({ pathExp: `changelog`, title: `Changelog` })
246
- }
247
- }
248
-
249
- //
250
- // ━━ Build Sidebar
251
- //
252
-
253
- const sidebarIndex: SidebarIndex = {}
254
-
255
- // Build sidebar for each top-level directory
256
- for (const child of routeTree.children) {
257
- if (child.value.type === 'directory') {
258
- const pathExp = `/${child.value.name}`
259
- // Create a subtree starting from this directory
260
- const subtree = Tree.node(child.value, child.children)
261
- // Pass the directory name as base path so paths are built correctly
262
- const sidebar = FileRouter.Sidebar.buildFromTree(subtree, [child.value.name])
263
- _debug(`Built sidebar for ${pathExp}:`, sidebar)
264
- sidebarIndex[pathExp] = sidebar
195
+ schemaNavbar.push({ pathExp: `changelog`, title: `Changelog` })
265
196
  }
266
197
  }
267
198
 
@@ -271,10 +202,7 @@ export const Core = (config: Config.Config): Vite.PluginOption[] => {
271
202
 
272
203
  const projectData: ProjectData = {
273
204
  schema,
274
- siteNavigationItems,
275
- sidebarIndex,
276
205
  faviconPath: `/logo.svg`,
277
- pagesScanResult: pagesScanResult,
278
206
  paths: config.paths.project,
279
207
  server: {
280
208
  static: {
@@ -288,14 +216,8 @@ export const Core = (config: Config.Config): Vite.PluginOption[] => {
288
216
  },
289
217
  }
290
218
 
291
- const projectDataCode = jsesc(superjson.stringify(projectData))
292
- const content = `
293
- import { superjson } from '#singletons/superjson'
294
-
295
- export const PROJECT_DATA = superjson.parse('${projectDataCode}')
296
- `
297
-
298
- return content
219
+ // Return just the JSON string - let the JSON plugin handle the transformation
220
+ return superjson.stringify(projectData)
299
221
  },
300
222
  },
301
223
  ),
@@ -0,0 +1,332 @@
1
+ import type { Config } from '#api/config/index'
2
+ import type { NavbarDataRegistry } from '#api/vite/data/navbar'
3
+ import { polenVirtual } from '#api/vite/vi'
4
+ import type { Vite } from '#dep/vite/index'
5
+ import { reportDiagnostics } from '#lib/file-router/diagnostic-reporter'
6
+ import { FileRouter } from '#lib/file-router/index'
7
+ import { Tree } from '#lib/tree/index'
8
+ import { debug } from '#singletons/debug'
9
+ import { superjson } from '#singletons/superjson'
10
+ import mdx from '@mdx-js/rollup'
11
+ import { Path, Str } from '@wollybeard/kit'
12
+ import remarkGfm from 'remark-gfm'
13
+
14
+ const _debug = debug.sub(`vite-plugin-pages`)
15
+
16
+ export const viProjectPages = polenVirtual([`project`, `pages.jsx`], { allowPluginProcessing: true })
17
+ export const viProjectPagesData = polenVirtual([`project`, `data`, 'pages.jsonsuper'], { allowPluginProcessing: true })
18
+
19
+ export interface PagesTreePluginOptions {
20
+ config: Config.Config
21
+ navbarData?: NavbarDataRegistry
22
+ onPagesChange?: (pages: FileRouter.ScanResult) => void
23
+ onTreeChange?: (tree: FileRouter.RouteTreeNode) => void
24
+ }
25
+
26
+ export interface ProjectDataPages {
27
+ sidebarIndex: SidebarIndex
28
+ pagesScanResult: FileRouter.ScanResult
29
+ }
30
+
31
+ export interface SidebarIndex {
32
+ [pathExpression: string]: FileRouter.Sidebar.Sidebar
33
+ }
34
+
35
+ /**
36
+ * Pages plugin with tree support
37
+ */
38
+ export const Pages = ({
39
+ config,
40
+ navbarData,
41
+ onPagesChange,
42
+ onTreeChange,
43
+ }: PagesTreePluginOptions): Vite.Plugin[] => {
44
+ let currentPagesData: FileRouter.ScanResult | null = null
45
+ let currentTreeData: FileRouter.RouteTreeNode | null = null
46
+
47
+ // State management
48
+ let pagesCache: FileRouter.ScanResult | null = null
49
+ let treeCache: FileRouter.RouteTreeNode | null = null
50
+
51
+ // Helper functions
52
+ const scanPages = async () => {
53
+ if (!pagesCache) {
54
+ _debug(`Scanning pages - cache is null, loading fresh data`)
55
+ pagesCache = await FileRouter.scan({
56
+ dir: config.paths.project.absolute.pages,
57
+ glob: `**/*.{md,mdx}`,
58
+ })
59
+ _debug(`Found ${String(pagesCache.routes.length)} pages`)
60
+ } else {
61
+ _debug(`Using cached pages`)
62
+ }
63
+ return pagesCache
64
+ }
65
+
66
+ const scanTree = async () => {
67
+ if (!treeCache) {
68
+ _debug(`Scanning tree - cache is null, loading fresh data`)
69
+ const result = await FileRouter.scanTree({
70
+ dir: config.paths.project.absolute.pages,
71
+ glob: `**/*.{md,mdx}`,
72
+ })
73
+ treeCache = result.routeTree
74
+ _debug(`Built route tree`)
75
+ } else {
76
+ _debug(`Using cached tree`)
77
+ }
78
+ return treeCache
79
+ }
80
+
81
+ const clearCache = () => {
82
+ _debug(`Clearing pages and tree cache`)
83
+ pagesCache = null
84
+ treeCache = null
85
+ }
86
+
87
+ const isPageFile = (file: string) => {
88
+ return (file.endsWith(`.md`) || file.endsWith(`.mdx`))
89
+ && file.includes(config.paths.project.absolute.pages)
90
+ }
91
+
92
+ const generatePagesModule = (pagesScanResult: FileRouter.ScanResult): string => {
93
+ const $ = {
94
+ pages: `pages`,
95
+ }
96
+
97
+ const s = Str.Builder()
98
+ s`export const ${$.pages} = []`
99
+
100
+ // Generate imports and route objects
101
+ for (const route of pagesScanResult.routes) {
102
+ const filePathExp = Path.format(route.file.path.absolute)
103
+ const pathExp = FileRouter.routeToPathExpression(route)
104
+ const ident = Str.Case.camel(`page ` + Str.titlizeSlug(pathExp))
105
+
106
+ s`
107
+ import ${ident} from '${filePathExp}'
108
+
109
+ ${$.pages}.push({
110
+ path: '${pathExp}',
111
+ Component: ${ident}
112
+ })
113
+ `
114
+ }
115
+
116
+ return s.render()
117
+ }
118
+
119
+ return [
120
+ // Plugin 1: MDX Processing
121
+ {
122
+ enforce: `pre` as const,
123
+ ...mdx({
124
+ jsxImportSource: `polen/react`,
125
+ remarkPlugins: [remarkGfm],
126
+ }),
127
+ },
128
+
129
+ // Plugin 2: Pages Management
130
+ {
131
+ name: `polen:pages`,
132
+
133
+ // Dev server configuration
134
+ configureServer(server) {
135
+ // Add pages directory to watcher
136
+ _debug(`configureServer: watch pages directory`, config.paths.project.absolute.pages)
137
+ server.watcher.add(config.paths.project.absolute.pages)
138
+ },
139
+
140
+ // Hot update handling
141
+ async handleHotUpdate({ file, server, modules }) {
142
+ _debug(`handleHotUpdate`, file)
143
+ if (!isPageFile(file)) return
144
+
145
+ _debug(`Page file changed:`, file)
146
+
147
+ // Check if this is a content-only change to an existing page
148
+ const oldPages = pagesCache
149
+
150
+ // Clear cache and rescan
151
+ clearCache()
152
+ const newPages = await scanPages()
153
+ currentPagesData = newPages
154
+
155
+ // Check if page structure changed (added/removed pages)
156
+ const structureChanged = !oldPages
157
+ || oldPages.routes.length !== newPages.routes.length
158
+ || !oldPages.routes.every((oldRoute, i) =>
159
+ oldRoute.file.path.absolute === newPages.routes[i]?.file.path.absolute
160
+ )
161
+
162
+ if (structureChanged) {
163
+ _debug(`Page structure changed, triggering full reload`)
164
+
165
+ // Invalidate virtual module
166
+ const mod = server.moduleGraph.getModuleById(viProjectPages.id)
167
+ if (mod) {
168
+ server.moduleGraph.invalidateModule(mod)
169
+ _debug(`Invalidated pages virtual module`)
170
+ }
171
+
172
+ // Notify about changes
173
+ if (onPagesChange) {
174
+ reportDiagnostics(newPages.diagnostics)
175
+ onPagesChange(newPages)
176
+ }
177
+
178
+ if (onTreeChange) {
179
+ const tree = await scanTree()
180
+ onTreeChange(tree)
181
+ currentTreeData = tree
182
+ }
183
+
184
+ // Trigger full reload for structure changes
185
+ server.ws.send({ type: `full-reload` })
186
+ return []
187
+ } else {
188
+ _debug(`Page content changed, allowing HMR`)
189
+ // Let default HMR handle the MDX file change
190
+ return modules
191
+ }
192
+ },
193
+ resolveId(id) {
194
+ if (id === viProjectPagesData.id) {
195
+ return viProjectPagesData.resolved
196
+ }
197
+ },
198
+ load: {
199
+ // filter: {
200
+ // id: viProjectPagesData.resolved,
201
+ // },
202
+ async handler(id) {
203
+ if (id !== viProjectPagesData.resolved) return
204
+ _debug(`viProjectDataPages`)
205
+
206
+ // Get pages data from the pages plugin or load initially
207
+ if (!currentPagesData) {
208
+ _debug(`loadingPagesDataInitially`)
209
+ currentPagesData = await FileRouter.scan({
210
+ dir: config.paths.project.absolute.pages,
211
+ glob: `**/*.{md,mdx}`,
212
+ })
213
+ // Report any diagnostics from initial scan
214
+ reportDiagnostics(currentPagesData.diagnostics)
215
+ }
216
+ if (!currentTreeData) {
217
+ _debug(`loadingTreeDataInitially`)
218
+ currentTreeData = await getRouteTree(config)
219
+ }
220
+ const pagesScanResult = currentPagesData
221
+ const routeTree = currentTreeData
222
+ _debug(`usingPageRoutesFromPagesPlugin`, pagesScanResult.routes.length)
223
+
224
+ //
225
+ // ━━ Build Navbar
226
+ //
227
+
228
+ // Update navbar if provided
229
+ if (navbarData) {
230
+ const navbarPages = navbarData.get('pages')
231
+ navbarPages.length = 0 // Clear existing
232
+
233
+ // Process first-level children as navigation items
234
+ for (const child of routeTree.children) {
235
+ if (child.value.type === 'directory') {
236
+ // Check if this directory has an index file
237
+ const hasIndex = child.children.some(c => c.value.type === 'file' && c.value.name === 'index')
238
+
239
+ if (hasIndex) {
240
+ const pathExp = FileRouter.pathToExpression([child.value.name])
241
+ const title = Str.titlizeSlug(child.value.name)
242
+ navbarPages.push({
243
+ pathExp: pathExp.startsWith('/') ? pathExp.slice(1) : pathExp,
244
+ title,
245
+ })
246
+ }
247
+ } else if (child.value.type === 'file' && child.value.name !== 'index') {
248
+ const pathExp = FileRouter.pathToExpression([child.value.name])
249
+ const title = Str.titlizeSlug(child.value.name)
250
+ navbarPages.push({
251
+ pathExp: pathExp.startsWith('/') ? pathExp.slice(1) : pathExp,
252
+ title,
253
+ })
254
+ }
255
+ }
256
+ }
257
+
258
+ //
259
+ // ━━ Build Sidebar
260
+ //
261
+
262
+ const sidebarIndex: SidebarIndex = {}
263
+
264
+ // Build sidebar for each top-level directory
265
+ for (const child of routeTree.children) {
266
+ if (child.value.type === 'directory') {
267
+ const pathExp = `/${child.value.name}`
268
+ // Create a subtree starting from this directory
269
+ const subtree = Tree.node(child.value, child.children)
270
+ // Pass the directory name as base path so paths are built correctly
271
+ const sidebar = FileRouter.Sidebar.buildFromTree(subtree, [child.value.name])
272
+ _debug(`Built sidebar for ${pathExp}:`, sidebar)
273
+ sidebarIndex[pathExp] = sidebar
274
+ }
275
+ }
276
+
277
+ //
278
+ // ━━ Put It All together
279
+ //
280
+
281
+ const projectDataPages: ProjectDataPages = {
282
+ sidebarIndex,
283
+ pagesScanResult: pagesScanResult,
284
+ }
285
+
286
+ // Return just the JSON string - let the JSON plugin handle the transformation
287
+ return superjson.stringify(projectDataPages)
288
+ },
289
+ },
290
+ },
291
+ // Plugin 4: Virtual Module for Pages Routes
292
+ {
293
+ name: 'polen:pages:routes',
294
+ resolveId(id) {
295
+ if (id === viProjectPages.id) {
296
+ return viProjectPages.resolved
297
+ }
298
+ },
299
+ load: {
300
+ // filter: {
301
+ // id: viProjectPages.resolved,
302
+ // },
303
+ handler: async (id) => {
304
+ if (id !== viProjectPages.resolved) return
305
+
306
+ _debug(`Loading viProjectPages virtual module`)
307
+
308
+ // Ensure we have pages data
309
+ if (!currentPagesData) {
310
+ currentPagesData = await scanPages()
311
+ reportDiagnostics(currentPagesData.diagnostics)
312
+ }
313
+
314
+ // Generate the module code
315
+ return {
316
+ code: generatePagesModule(currentPagesData),
317
+ moduleType: 'js',
318
+ }
319
+ },
320
+ },
321
+ },
322
+ ]
323
+ }
324
+
325
+ // Helper to get tree
326
+ export const getRouteTree = async (config: Config.Config): Promise<FileRouter.RouteTreeNode> => {
327
+ const result = await FileRouter.scanTree({
328
+ dir: config.paths.project.absolute.pages,
329
+ glob: `**/*.{md,mdx}`,
330
+ })
331
+ return result.routeTree
332
+ }
@@ -1,11 +1,11 @@
1
1
  import type { Config } from '#api/config/index'
2
+ import { reportError } from '#api/server/report-error'
2
3
  import type { Hono } from '#dep/hono/index'
3
4
  import type { Vite } from '#dep/vite/index'
5
+ import { ResponseInternalServerError } from '#lib/kit-temp'
4
6
  import { debug } from '#singletons/debug'
5
7
  import * as HonoNodeServer from '@hono/node-server'
6
- import { Err, Http } from '@wollybeard/kit'
7
- import cleanStack from 'clean-stack'
8
- import { ErrorParser } from 'youch-core'
8
+ import { Err } from '@wollybeard/kit'
9
9
 
10
10
  type App = Hono.Hono
11
11
 
@@ -28,25 +28,7 @@ export const Serve = (
28
28
  if (Err.is(error)) {
29
29
  // ━ Clean Stack Trace
30
30
  server.ssrFixStacktrace(error)
31
- const stack = cleanStack(error.stack, {
32
- pathFilter: (path) => {
33
- return !path.match(/.*rolldown-vite.*/)
34
- },
35
- basePath: config.paths.project.rootDir,
36
- // pretty: true,
37
- })
38
- error.stack = stack
39
- // ━ Log Error
40
- Err.log(error)
41
- const parser = new ErrorParser()
42
- const parsedError = await parser.parse(error)
43
- const snippet = parsedError.frames[0]?.source?.map(line => {
44
- return line.lineNumber.toString().padStart(4, ' ') + `: ` + line.chunk
45
- }).join(`\n`)
46
- if (snippet) {
47
- console.log('-----------------------------')
48
- console.log(snippet)
49
- }
31
+ reportError(error)
50
32
  return error
51
33
  }
52
34
  throw error
@@ -97,10 +79,7 @@ export const Serve = (
97
79
  const app = await appPromise
98
80
  if (Err.is(app)) {
99
81
  // Err.log(app)
100
- return new Response(null, {
101
- status: Http.Status.InternalServerError.code,
102
- statusText: Http.Status.InternalServerError.description,
103
- })
82
+ return ResponseInternalServerError()
104
83
  }
105
84
  const response = await app.fetch(request, { viteDevServer: server })
106
85
  return response
@@ -8,6 +8,7 @@ import { Err, Path } from '@wollybeard/kit'
8
8
  import { z } from 'zod'
9
9
 
10
10
  const args = Command.create()
11
+ .parameter(`--debug -d`, z.boolean().optional())
11
12
  .parameter(
12
13
  `--project -p`,
13
14
  // @ts-expect-error
@@ -28,7 +29,14 @@ const args = Command.create()
28
29
 
29
30
  const dir = ensureOptionalAbsoluteWithCwd(args.project) as string
30
31
 
31
- const viteUserConfig = await Api.ConfigResolver.fromFile({ dir })
32
+ const viteUserConfig = await Api.ConfigResolver.fromFile({
33
+ dir,
34
+ overrides: {
35
+ advanced: {
36
+ debug: args.debug,
37
+ },
38
+ },
39
+ })
32
40
 
33
41
  const viteDevServer = await Err.tryCatch(() => Vite.createServer(viteUserConfig))
34
42