duto 0.260906.0 → 0.261001.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/README.md +34 -12
  2. package/package.json +9 -3
  3. package/src/cli.ts +10 -7
  4. package/src/commands/new.ts +172 -0
  5. package/src/commands/templates/pages/.editorconfig +21 -0
  6. package/src/commands/templates/pages/README.md +21 -0
  7. package/src/commands/templates/pages/_gitignore +24 -0
  8. package/src/commands/templates/pages/components/locale-select.tsx +59 -0
  9. package/src/commands/templates/pages/components/select.tsx +95 -0
  10. package/src/commands/templates/pages/i18n/en.ts +17 -0
  11. package/src/commands/templates/pages/i18n/index.ts +19 -0
  12. package/src/commands/templates/pages/i18n/pt-br.ts +17 -0
  13. package/src/commands/templates/pages/package.json +22 -0
  14. package/src/commands/templates/pages/pages/404.tsx +12 -0
  15. package/src/commands/templates/pages/pages/index.tsx +16 -0
  16. package/src/commands/templates/pages/pages/layout.tsx +24 -0
  17. package/src/commands/templates/pages/style.css +24 -0
  18. package/src/commands/templates/pages/tsconfig.json +54 -0
  19. package/src/commands/templates/pages/types.ts +8 -0
  20. package/src/commands/templates/server/base/.editorconfig +21 -0
  21. package/src/commands/templates/server/base/.env.dev.example +2 -0
  22. package/src/commands/templates/server/base/.env.prod.example +2 -0
  23. package/src/commands/templates/server/base/README.md +29 -0
  24. package/src/commands/templates/server/base/_gitignore +100 -0
  25. package/src/commands/templates/server/base/actions/blog/index.get.ts +9 -0
  26. package/src/commands/templates/server/base/actions/blog/post/[id].get.ts +9 -0
  27. package/src/commands/templates/server/base/actions/index.get.ts +5 -0
  28. package/src/commands/templates/server/base/configs/duto.ts +11 -0
  29. package/src/commands/templates/server/base/configs/roles.ts +5 -0
  30. package/src/commands/templates/server/base/package.json +24 -0
  31. package/src/commands/templates/server/base/tsconfig.json +42 -0
  32. package/src/commands/templates/server/workerd/wrangler.toml +47 -0
  33. package/src/commands/upgrade.ts +30 -0
  34. package/src/index.ts +7 -2
  35. package/src/pages/{commands → cli/commands}/build.ts +13 -7
  36. package/src/pages/{commands → cli/commands}/dev.ts +19 -6
  37. package/src/pages/{commands → cli/commands}/preview.ts +6 -4
  38. package/src/pages/cli/index.ts +10 -0
  39. package/src/pages/cli/utils.ts +126 -0
  40. package/src/pages/client/app.tsx +27 -74
  41. package/src/pages/client/router.ts +15 -281
  42. package/src/pages/compiler/aliases.ts +2 -8
  43. package/src/pages/compiler/ast.ts +1 -538
  44. package/src/pages/compiler/constants.ts +95 -13
  45. package/src/pages/compiler/file.ts +1 -1
  46. package/src/pages/compiler/index.ts +20 -128
  47. package/src/pages/compiler/meta.ts +39 -228
  48. package/src/pages/compiler/routes.ts +4 -5
  49. package/src/pages/compiler/types.ts +11 -101
  50. package/src/pages/compiler/vite-plugin.ts +90 -69
  51. package/src/pages/i18n/client.ts +19 -7
  52. package/src/pages/i18n/element.ts +1 -1
  53. package/src/pages/i18n/server.ts +22 -7
  54. package/src/pages/index.ts +1 -2
  55. package/src/pages/preact/server.ts +1 -1
  56. package/src/server/app.ts +5 -10
  57. package/src/server/auth/ability.ts +1 -1
  58. package/src/server/auth/token.ts +5 -5
  59. package/src/server/cli/commands/build.ts +2 -2
  60. package/src/server/cli/commands/deploy.ts +1 -2
  61. package/src/server/cli/commands/dev.ts +6 -4
  62. package/src/server/cli/commands/routes.ts +0 -1
  63. package/src/server/cli/utils.ts +2 -8
  64. package/src/server/config.ts +18 -3
  65. package/src/server/entry.ts +3 -3
  66. package/src/server/open-api/register.ts +3 -3
  67. package/src/server/open-api/spec.ts +11 -4
  68. package/src/server/request.ts +46 -14
  69. package/src/server/routes.ts +8 -31
  70. package/src/server/utils.ts +25 -0
  71. package/src/utils.ts +43 -2
  72. package/src/pages/commands/dev-old.ts +0 -213
  73. package/src/pages/compiler/island.ts +0 -108
  74. package/src/pages/compiler/utils.ts +0 -5
  75. package/src/server/http.ts +0 -23
@@ -1,31 +1,23 @@
1
+ import { promisify } from 'node:util'
2
+ import { brotliCompress } from 'node:zlib'
1
3
  import { mkdir } from 'node:fs/promises'
2
4
  import { join, dirname } from 'node:path'
3
- import { getLayouts, parsePage, resolveStaticOutputPath } from './meta'
4
5
 
5
6
  import { h } from 'preact'
6
7
  import { render } from 'preact-render-to-string'
7
8
  import minify from '@minify-html/node'
8
9
 
9
- import type { ResolvedConfig } from 'vite'
10
+ import { JSJSON } from 't0n'
10
11
  import { DOCTYPE_RE } from './constants'
11
- import { getAsset, getChunk, normalizeId } from './assets'
12
-
13
- import type { IRoute, Metadata, RouteParams, StaticProps, SummaryMetadata } from './types'
14
-
15
-
16
- // import i18n from '../client/i18n'
12
+ import { getAsset, getChunk } from './assets'
13
+ import { getLayouts, resolveStaticOutputPath } from './meta'
17
14
  import { setLocale } from '../i18n/client'
18
- import { log, substep } from 't0n/log'
19
-
20
15
 
21
-
22
- import { promisify } from 'node:util'
23
- import { brotliCompress } from 'node:zlib'
24
-
25
- const brCompress = promisify(brotliCompress);
16
+ import type { ResolvedConfig } from 'vite'
17
+ import type { IRoute, Metadata, RouteParams, StaticProps, SummaryMetadata } from './types'
26
18
 
27
19
 
28
- export const selfCloseEmptyTags = (html: string, tagName: string) => html.replace(new RegExp(`<${tagName}\\b([^>]*?)>\\s*</${tagName}>`, 'g'), (_, attrs) => `<${tagName}${attrs} />`)
20
+ const brCompress = promisify(brotliCompress)
29
21
 
30
22
  export async function renderPage(
31
23
  meta: Metadata,
@@ -33,40 +25,15 @@ export async function renderPage(
33
25
  page: IRoute
34
26
  ) {
35
27
  const {
36
- mod, islands,
28
+ mod,
37
29
  hasGetProps, hasGetStaticProps,
38
30
  isClientOnly,
39
- } = await parsePage(page, meta)
40
- // const {
41
- // mod, islands,
42
- // hasGetProps, hasGetStaticProps,
43
- // isClientOnly,
44
- // } = page
45
-
46
- if (!mod?.default) {
47
- console.warn(`No default export in ${page.filePath} — skipping`)
48
- return
49
- }
31
+ } = page
32
+ if (!mod?.default)
33
+ return console.warn(`No default export in ${page.filePath} — skipping`)
50
34
 
51
- // console.log(page.filePath)
52
- // console.error('hasGetProps:', hasGetProps)
53
- // console.error('hasGetStaticProps:', hasGetStaticProps)
54
- // console.error('isClientOnly:', isClientOnly)
55
-
56
- if (page.isInternal || page.is404 || (page.isDynamic && !hasGetStaticProps)) {
57
- meta.summary.set('/'+ page.fileName, {
58
- type: 2,
59
- path: '/'+ page.fileName,
60
- size: 0,
61
- sizeBr: 0,
62
- sizeJS: 0,
63
- })
64
- // meta.routes.set(page.fullFilePath, page)
65
- return
66
- }
67
-
68
- // console.log(meta.routes.size)
69
- // meta.routes.set(page.filePath, page)
35
+ if (page.isInternal || page.is404 || (page.isDynamic && !hasGetStaticProps))
36
+ return meta.summary.set('/'+ page.fileName, { type: 2, path: '/'+ page.fileName, size: 0, sizeBr: 0, sizeJS: 0 })
70
37
 
71
38
  const { filePath, pattern, params } = page
72
39
 
@@ -109,7 +76,6 @@ export async function renderPage(
109
76
  const allProps = { ...prop.pageProps, ...prop.routeParams }
110
77
 
111
78
  // @ts-ignore
112
- // i18n(allProps?.locale || meta.i18n.fallbackLocale, meta.i18n.locales, meta.i18n.messages, meta.i18n.routes, meta.i18n.routeMap)
113
79
  allProps?.locale && setLocale(allProps?.locale || meta.i18n.fallbackLocale)
114
80
 
115
81
  const pathname = resolveStaticOutputPath(page, prop.routeParams)
@@ -139,36 +105,11 @@ export async function renderPage(
139
105
  }
140
106
  // }
141
107
 
142
- // TODO: refatorar isso... aqui dentro já é o SSG
143
- const needsClient = hasGetProps || !!isClientOnly || islands.length > 0
144
-
145
- // console.error(page.fileName)
146
- // console.error('needsClient: ', needsClient)
147
-
148
- const assets = []
149
- for (const [depPath, dep] of meta.map.get(page.fullFilePath)!.deps)
150
- assets.push(normalizeId(dep.path))
151
-
152
-
153
- html = buildHtml(assets, html) //, needsClient, allProps, pattern, islands)
154
- // html = selfCloseEmptyTags(String(minify.minify(
155
- // Buffer.from(buildHtml(assets, html, needsClient, allProps, pattern, islands)),
156
- // {}
157
- // // options != null ? options : {},
158
- // )), 'i-i')
159
-
160
- const outFile = join(
161
- config.root,
162
- config.build.outDir,
163
- pathname +'.html'
164
- )
165
- const outHTML = minify.minify(
166
- Buffer.from(html),
167
- {}
168
- // options != null ? options : {},
169
- )
108
+ html = buildHtml(html, Object.keys(allProps).length ? allProps : undefined)
109
+
110
+ const outFile = join(config.root, config.build.outDir, pathname +'.html')
111
+ const outHTML = meta.minify ? minify.minify(Buffer.from(html), {}) : html
170
112
  await mkdir(dirname(outFile), {recursive: true})
171
- // await writeFile(outFile, outHTML, 'utf-8')
172
113
  await Bun.write(outFile, outHTML)
173
114
 
174
115
  const key = '/'+ pathname
@@ -208,12 +149,8 @@ export async function renderPage(
208
149
  }
209
150
 
210
151
  function buildHtml(
211
- _assets: string[],
212
152
  html: string,
213
- // needsClient: boolean,
214
- // props?: StaticProps,
215
- // routePattern?: string,
216
- // islands?: Island[],
153
+ props?: StaticProps,
217
154
  ): string {
218
155
  if (!(html.includes('<body') || html.includes('<html') || html.includes('<head')))
219
156
  html = wrapHtml(html)
@@ -221,58 +158,14 @@ function buildHtml(
221
158
  if (!DOCTYPE_RE.test(html))
222
159
  html = '<!DOCTYPE html>'+ html
223
160
 
224
- // const pageProps = needsClient
225
- // ? `<script>
226
- // window.__PROPS__ = ${JSON.stringify(props ?? {})};
227
- // </script>`
228
- // : ''
229
-
230
161
  const assets = [
231
162
  ':duto/client/app.tsx',
232
- // ..._assets,
233
163
  ]
234
164
 
235
165
  const prepend = assets.map(asset => {
236
166
  const href = getAsset(asset)
237
167
  return href ? `<link href="${href}" rel=stylesheet>` : null
238
168
  }).filter(Boolean).join('')
239
- // + `<style>d-b,d-p,i-d,i-s{display:contents}</style>`
240
- // + `<style>d,d-slot,d-s-slot{display:contents}</style>`
241
-
242
- // d>*,d-slot>*,d-s-slot>*{display:revert}
243
-
244
- // d {
245
- // display: block;
246
- // box-sizing: border-box;
247
- // }
248
-
249
- // /* Herda todos os estilos padrão de div */
250
- // d:not([hidden]) {
251
- // display: block;
252
- // }
253
-
254
- // /* Remove qualquer comportamento de display:contents */
255
- // d, d-slot, d-s-slot {
256
- // display: block !important;
257
- // }
258
-
259
- // d *,
260
- // d-slot *,
261
- // d-s-slot * {
262
- // visibility: visible !important;
263
- // opacity: 1 !important;
264
- // }
265
- // </style>
266
-
267
- // <script>class DDiv extends HTMLElement {
268
- // constructor() {
269
- // super();
270
- // }
271
- // }
272
-
273
- // if (typeof customElements !== 'undefined' && !customElements.get('d')) {
274
- // customElements.define('d', DDiv);
275
- // }</script>
276
169
 
277
170
  if (html.includes('</head>')) {
278
171
  html = html.replace('</head>', prepend +'</head>')
@@ -282,8 +175,7 @@ function buildHtml(
282
175
  html = prepend + html
283
176
  }
284
177
 
285
- // const append = pageProps + assets.map(file => {
286
- const append = assets.map(file => {
178
+ const append = (props ? `<script>(self.$duto=self.$duto||{}).props=${JSJSON(props)}</script>` : '') + assets.map(file => {
287
179
  const src = getChunk(file)
288
180
  return src ? `<script src="${src}" type=module></script>` : null
289
181
  }).filter(Boolean).join('')
@@ -4,150 +4,65 @@
4
4
  // https://github.com/cloudflare/vinext/blob/main/packages/vinext/src/entries/pages-client-entry.ts
5
5
  // https://github.com/cloudflare/vinext/blob/main/packages/vinext/src/shims/ALS-ARCHITECTURE.md
6
6
 
7
- import { dirname, extname, join } from 'node:path'
8
- import { readdirSync } from 'node:fs'
7
+ import { dirname, extname } from 'node:path'
9
8
 
10
- import { IMPORT, JSJSON, newID } from 't0n'
9
+ import { getEnv, JSJSON, newID } from 't0n'
11
10
 
12
11
  import { getAliases } from './aliases'
13
- import { EXT_RE, DYNAMIC_RE, INTERNAL_RE, LAYOUT_RE, NF_NAMES } from './constants'
12
+ import { DYNAMIC_RE, INTERNAL_RE, LAYOUT_RE, NF_NAMES, FILE_EXTENSIONS } from './constants'
13
+ import { scan } from '@/utils'
14
14
 
15
15
  import { USE_CLIENT_RE } from './constants'
16
- import { parseSync } from '@swc/core'
17
- import { findUsage, getFileMap } from './ast'
18
- import { replaceIslands } from './island'
19
- import { mkdir } from 'node:fs/promises'
20
-
21
16
 
22
17
  import getI18n from '../i18n/server'
23
18
  import i18n from '../i18n/client'
24
19
 
25
20
  import type {
26
- PageModule,
27
21
  IRoute, RouteParam, RouteParams,
28
22
  Metadata,
29
- ImportUsage,
30
23
  SummaryMetadata,
31
24
  } from './types'
32
25
 
33
- export function getAssets(meta: Metadata): Set<string> {
34
- const deps = new Set<string>()
35
- const queue: string[] = []
36
- const dir = join(meta.root, meta.dir)
37
-
38
- for (const [path] of meta.map) {
39
- if (path.startsWith(dir))
40
- queue.push(path)
41
- }
42
-
43
- while (queue.length > 0) {
44
- const path = queue.pop()!
45
- const imports = meta.map.get(path)!.deps
46
- if (!imports?.size) continue
47
-
48
- for (const [dep] of imports) {
49
- if (!dep.startsWith(meta.root) || dep.startsWith(dir) || deps.has(dep)) continue
50
- deps.add(dep)
51
- if (dep in meta.map) queue.push(dep)
52
- }
53
- }
54
-
55
- return deps
56
- }
57
-
58
-
59
- export async function parsePage(page: IRoute, meta: Metadata) {
60
- const fileMeta = meta.map.get(page.fullFilePath)!
61
- if (!meta.violations.has(page.fullFilePath)) {
62
- return page
63
- }
64
-
65
- const islands: ImportUsage[] = []
66
- const ast = parseSync(fileMeta.content, { syntax: 'typescript', tsx: true })
67
- for (const [depPath, dep] of fileMeta.deps) {
68
- // console.error(depPath, page.fullFilePath)
69
- if (!meta.violations.has(depPath)) continue
70
-
71
- const usages = findUsage(fileMeta.content, dep.ref, ast)
72
- // console.log(usages, dep.ref)
73
- islands.push(...usages)
74
- // for (const usage of usages)
75
- // islands.push(...usage.usages)
76
-
77
-
78
- // console.log(id)
79
- // console.log(usage)
80
- // console.log(usage[0].usages)
81
- }
82
-
83
- // // Limpar linhas vazias extras
84
- // result = result.replace(/^\s*\n/gm, '')
85
-
86
- const content = await replaceIslands(fileMeta.content, islands)
87
- // @ts-ignore
88
- let mod: PageModule = {}
89
- const tmpPath = join(meta.root, 'tmp', page.filePath)
90
- try {
91
- await mkdir(dirname(tmpPath), {recursive: true})
92
- await Bun.write(tmpPath, content)
93
- mod = await IMPORT(tmpPath)
94
- } catch {}
95
-
96
- return {
97
- safeFilePath: tmpPath,
98
- mod, content, islands,
99
- hasGetStaticProps: typeof mod.getStaticProps === 'function',
100
- hasGetProps: typeof mod.getProps === 'function',
101
- isClientOnly: USE_CLIENT_RE.test(content),
102
- }
103
- }
104
-
105
26
 
106
27
  // TODO: https://github.com/cloudflare/vinext/blob/adfc2236cba5f7cde63f32f6fb35c0d892428376/packages/vinext/src/routing/file-matcher.ts#L90
107
28
  export async function getPages(meta: Metadata) {
108
- const files = readdirSync(join(meta.root, meta.dir), {recursive: true})
109
29
  const nf = new Map<string, IRoute>()
110
30
  const layouts = new Map<string, IRoute>()
111
31
  const pages = new Map<string, IRoute>()
112
32
  const spages = new Set<string>()
113
33
  const routes: IRoute[] = []
114
34
 
115
- for (const file of files) {
116
- if (typeof file !== 'string' || !EXT_RE.test(file)) continue
117
-
35
+ for await (const [file, path, mod] of scan(meta.dir, FILE_EXTENSIONS)) {
118
36
  const ext = extname(file)
119
- const fullFilePath = join(meta.root, meta.dir, file)
120
- const { pattern, params } = filePathToWouterPattern(file)
121
-
122
- const name = file.replace(EXT_RE, '').split('/').pop() as string
37
+ const filePath = path.split(meta.dir+'/').pop() as string
38
+ const fileName = filePath.replace(ext, '')
39
+ const { pattern, params } = filePathToWouterPattern(fileName)
123
40
 
124
- const fileMeta = meta.map.get(fullFilePath)!
125
- const mod = (await IMPORT(fullFilePath)) as PageModule
41
+ const name = file.replace(ext, '').split('/').pop() as string
42
+ const content = await Bun.file(file).text()
126
43
 
127
- // console.error(file, INTERNAL_RE.test(file))
128
44
  const page = {
129
45
  fileExt: ext.substring(1),
130
- fileName: file.replace(ext, ''),
131
- filePath: file,
132
- fullFilePath,
46
+ fileName,
47
+ filePath,
48
+ fullFilePath: file,
133
49
  pattern,
134
50
  params,
135
51
  is404: name ? NF_NAMES.includes(name) : false,
136
- isDynamic: DYNAMIC_RE.test(file),
137
- isInternal: INTERNAL_RE.test(file),
52
+ isDynamic: DYNAMIC_RE.test(path),
53
+ isInternal: INTERNAL_RE.test(path),
138
54
 
139
55
  importName: newID(),
140
56
 
141
57
  mod,
142
- content: fileMeta.content,
143
- islands: [],
58
+ content: content,
144
59
  layouts: [],
145
60
  hasGetStaticProps: typeof mod.getStaticProps === 'function',
146
61
  hasGetProps: typeof mod.getProps === 'function',
147
- isClientOnly: USE_CLIENT_RE.test(fileMeta.content),
62
+ isClientOnly: USE_CLIENT_RE.test(content),
148
63
  }
149
64
 
150
- if (LAYOUT_RE.test(file)) {
65
+ if (LAYOUT_RE.test(path)) {
151
66
  layouts.set(page.fileName, page)
152
67
  continue
153
68
  }
@@ -155,14 +70,14 @@ export async function getPages(meta: Metadata) {
155
70
  if (page.is404) {
156
71
  nf.set(page.fileName, page)
157
72
  routes.push(page)
158
- pages.set(fullFilePath, page)
73
+ pages.set(file, page)
159
74
  continue
160
75
  }
161
76
 
162
77
  if (!page.isInternal)
163
- pages.set(fullFilePath, page)
78
+ pages.set(file, page)
164
79
 
165
- if (!page.isInternal && page.isDynamic && page.hasGetProps)
80
+ if (!page.isInternal)
166
81
  routes.push(page)
167
82
 
168
83
  if (!page.isInternal) {
@@ -170,11 +85,9 @@ export async function getPages(meta: Metadata) {
170
85
  try {
171
86
  const _props = await page.mod.getStaticProps?.()
172
87
  const props = Array.isArray(_props) ? _props : [_props]
173
- for (const prop of props) {
174
- // console.log(prop, file)
88
+ for (const prop of props)
175
89
  spages.add('/'+resolveStaticOutputPath(page, prop as any))
176
- }
177
- // spages.add('/'+resolveStaticOutputPath(page, prop as any))
90
+
178
91
  } catch(e) {
179
92
  console.log('errro', e)
180
93
  }
@@ -192,49 +105,6 @@ export async function getPages(meta: Metadata) {
192
105
  meta.routes = await sortRoutes(meta, routes)
193
106
  }
194
107
 
195
- /**
196
- * A partir do path de um arquivo relativo à raiz de rotas do Next.js
197
- * (app/ ou pages/), retorna:
198
- *
199
- * - layouts: arquivos "layout.{tsx,ts,jsx,js}" aplicáveis à rota, ordenados
200
- * do mais próximo do arquivo (segmento irmão) ao mais distante (raiz).
201
- * Ex.: ["users/layout.tsx", "layout.tsx"]
202
- * - document: o "_document.{tsx,ts,jsx,js}" do Pages Router, se existir na
203
- * raiz das rotas. Só é checado uma vez no topo, nunca por segmento — é
204
- * um arquivo único, diferente do layout, que pode ser aninhado.
205
- *
206
- * Compatibilidade com a especificação oficial do Next.js:
207
- * - App Router: nenhum arquivo especial usa prefixo "_" — é "layout", não
208
- * "_layout". layout.js é o componente mais externo de um route segment e
209
- * envolve template.js, error.js, loading.js, not-found.js e page.js.
210
- * - Pages Router: pages/_document.js (com "_") customiza as tags <html> e
211
- * <body> e existe só uma vez, na raiz de pages/. No App Router esse papel
212
- * foi assumido pelo root layout.js — não há "_document" equivalente lá.
213
- */
214
- // export async function getLayouts(meta: Metadata, path: string) {
215
- // const isDir = existsSync(path) && statSync(path).isDirectory()
216
- // const fileDir = isDir ? path : dirname(path)
217
-
218
- // const layouts: Page[] = []
219
- // let currentDir = fileDir
220
-
221
- // while (true) {
222
- // const layout = findFileByName(meta, currentDir, 'layout')
223
- // if (layout && meta.pages.has(layout)) {
224
- // // const mod = meta.pages.get(layout)?.mod
225
- // const { mod, content } = await parsePage(meta.pages.get(layout)!, meta)
226
- // if (mod?.default) layouts.push(mod.default)
227
- // }
228
-
229
- // const parentDir = dirname(currentDir)
230
- // if (parentDir === currentDir) break
231
-
232
- // currentDir = parentDir
233
- // }
234
-
235
- // return layouts
236
- // }
237
-
238
108
  export async function getLayouts(meta: Metadata, path: string) {
239
109
  const layouts: IRoute[] = []
240
110
  const segments = dirname(path).split(/[\\/]/).filter(Boolean)
@@ -242,48 +112,36 @@ export async function getLayouts(meta: Metadata, path: string) {
242
112
  for (let i = segments.length; i >= 0; i--) {
243
113
  const dir = segments.slice(0, i).join('/')
244
114
 
245
- const page = meta.layouts.get(dir ? dir +'/_layout' : '_layout') || meta.layouts.get(dir ? dir +'/layout' : 'layout')
246
- if (!page) continue
247
-
248
- const { mod } = await parsePage(page, meta) // @ts-ignore
249
- if (mod?.default) {
250
- page.mod = mod
251
- layouts.push(page)
252
- }
115
+ const layout = meta.layouts.get(dir ? dir +'/_layout' : '_layout') || meta.layouts.get(dir ? dir +'/layout' : 'layout')
116
+ if (!layout) continue
253
117
 
118
+ // @ts-ignore
119
+ if (layout.mod?.default)
120
+ layouts.push(layout)
254
121
  }
255
122
 
256
123
  return layouts
257
124
  }
258
125
 
259
126
  export async function getMetadata(root: string, dir: string) {
260
- const meta = { root, dir } as Metadata
127
+ const meta = { root, dir, ...getEnv('duto', {}) } as Metadata
261
128
  meta.summary = new Map<string, SummaryMetadata>()
262
129
 
263
130
  meta.i18n = await getI18n(meta)
264
131
  i18n(meta.i18n.fallbackLocale, meta.i18n.locales, meta.i18n.messages, meta.i18n.routes, meta.i18n.routeMap)
265
132
 
266
- meta.aliases = getAliases(root)
267
- const fileMap = await getFileMap(meta)
268
- meta.map = fileMap.map
269
- meta.violations = fileMap.violations
270
- meta.assets = Array.from(getAssets(meta))
133
+ meta.aliases = await getAliases(root)
271
134
 
272
135
  await getPages(meta)
273
136
 
274
137
  meta.string = `// AUTO-GENERATED FILE - DO NOT EDIT
275
- ${meta.routes.map((page, index) => `\nimport Page${index + (page.hasGetProps ? `, { getProps as getProps${index} }` : '')} from '${page.fullFilePath}'`).join('\n')}
276
- ${Array.from(meta.layouts.values()).map(layout => `\nimport Layout${layout.importName} from '${layout.fullFilePath}'`).join('\n')}
138
+ ${meta.routes.filter(r => r.is404).map(r => `\nimport Page${r.importName + (r.hasGetProps ? `, { getProps as getProps${r.importName} }` : '')} from '${r.fullFilePath}'`).join('\n')}
139
+ ${Array.from(meta.layouts.values()).map(l => `\nimport Layout${l.importName} from '${l.fullFilePath}'`).join('\n')}
277
140
 
278
- export const views = [${meta.routes.map((page, index) => `['${page.pattern}',Page${index},${JSJSON(Object.entries(page.params).map(([_,v]) => v))}${page.hasGetProps ? `,getProps${index}` : (page.layouts.length ? ',0': '')}${page.layouts.length ? ',[' + Object.entries(page.layouts).map(([_,v]) => 'Layout'+v.importName) +']' : ''}]`).join(',')}]
141
+ export const views = [${meta.routes.map(r => `['${r.pattern}',${r.is404 ? 'Page'+ r.importName : `'${r.fullFilePath}'`},${Object.keys(r.params).length ? JSJSON(Object.entries(r.params).map(([_,v]) => v)) : '0'}${r.hasGetProps ? (r.is404 ? `,getProps${r.importName}` : `,1`) : (r.layouts.length ? ',0': '')}${r.layouts.length ? ',[' + Object.entries(r.layouts).map(([_,l]) => 'Layout'+l.importName) +']' : ''}]`).join(',')}]
279
142
 
280
143
  export const SRoutes = new Set(${JSJSON([...meta.spages])})
281
-
282
-
283
- ` /// + meta.i18n.string
284
-
285
- // console.error(meta.string)
286
- // console.log(meta.map.get(`/Users/nicolau/Projects/preact-skeleton/pages/index.tsx`))
144
+ `
287
145
 
288
146
  return meta
289
147
  }
@@ -381,64 +239,19 @@ function getRouteMeta(pattern: string): RouteMeta {
381
239
  segmentsCount: segments.length,
382
240
  }
383
241
  }
384
- // //////////////////////////////////////////////////////////////////////////////////////////
385
242
 
386
- // export function sortRoutes(pages: Map<string, IRoute>) {
387
- // const metas = new Map<string, { score: number, segmentsCount: number }>()
388
-
389
- // for (const [route, page] of pages)
390
- // metas.set(route, computeRouteMeta(page.pattern))
391
-
392
- // const list = Array.from(pages.values()).sort((a, b) => {
393
- // const metaA = metas.get(a.fullFilePath)!
394
- // const metaB = metas.get(b.fullFilePath)!
395
-
396
- // if (metaA.score === metaB.score)
397
- // return metaB.segmentsCount - metaA.segmentsCount
398
-
399
- // return metaB.score - metaA.score
400
- // })
401
-
402
- // while (list.length && list.at(-1)?.pattern === '/') {
403
- // const last = list.pop()
404
- // last && list.unshift(last)
405
- // }
406
-
407
- // return list
408
- // }
409
-
410
- // function computeRouteMeta(path: string) {
411
- // const segments = path.split('/').filter(Boolean)
412
-
413
- // let score = 0
414
- // for (const segment of segments) {
415
- // if (segment === '*') {
416
- // continue
417
- // } else if (segment.startsWith(':')) {
418
- // score += 1
419
- // } else {
420
- // score += 10
421
- // }
422
- // }
423
-
424
- // return { score, segmentsCount: segments.length }
425
- // }
426
-
427
- // //////////////////////////////////////////////////////////////////////////////////////////
428
243
  const CATCHALL_NAMES = new Set(['404', 'not-found'])
429
244
 
430
- export function filePathToWouterPattern(filePath: string): {
245
+ export function filePathToWouterPattern(path: string): {
431
246
  pattern: string
432
247
  params: Record<string, RouteParam>
433
248
  } {
434
- const withoutExt = filePath.replace(EXT_RE, '')
435
-
436
249
  const normalized =
437
- withoutExt === 'index'
250
+ path === 'index'
438
251
  ? ''
439
- : withoutExt.endsWith('/index')
440
- ? withoutExt.slice(0, -6)
441
- : withoutExt
252
+ : path.endsWith('/index')
253
+ ? path.slice(0, -6)
254
+ : path
442
255
 
443
256
  const segments = normalized ? normalized.split('/') : []
444
257
 
@@ -535,8 +348,6 @@ export function filePathToWouterPattern(filePath: string): {
535
348
 
536
349
  return { pattern, params }
537
350
  }
538
- // //////////////////////////////////////////////////////////////////////////////////////////
539
-
540
351
 
541
352
  /**
542
353
  * Given a static props entry (returned by getStaticProps),
@@ -1,13 +1,12 @@
1
- import { join } from 'pathe'
2
- import { _root } from './utils'
3
- import type { IRoute, Metadata } from './types'
4
- import { dirname } from 'node:path'
1
+ import { dirname, join } from 'pathe'
5
2
  import { NF_NAMES } from './constants'
3
+ import { _root } from '@/utils'
4
+ import type { Metadata } from './types'
6
5
 
7
6
  export const router = new Bun.FileSystemRouter({
8
7
  style: 'nextjs',
9
8
  dir: join(_root, 'pages'),
10
- // origin: 'https://mydomain.com',
9
+ // origin: 'https://example.com',
11
10
  // assetPrefix: 'assets/',
12
11
  })
13
12
  export const reload = () => router.reload()