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,32 +1,6 @@
1
- import { dirname, resolve } from 'node:path'
2
- import { parseSync, printSync } from '@swc/core'
3
-
4
- import { IMPORT_RE, BROWSER_ONLY, BROWSER_ONLY_SET, USE_CLIENT_RE, FILE_EXTENSIONS } from './constants'
1
+ import { resolve } from 'node:path'
5
2
  import { resolveAlias } from './aliases'
6
3
 
7
- import type {
8
- Metadata,
9
- FileMetadata,
10
- DepMetadata,
11
- ImportUsage, Usage,
12
- } from './types'
13
-
14
-
15
- function isBrowserLib(lib: string) {
16
- if (!lib) return false
17
- if (BROWSER_ONLY_SET.has(lib)) return true
18
-
19
- // avoids create template string every time
20
- for (let i = 0; i < BROWSER_ONLY.length; i++) {
21
- const base = BROWSER_ONLY[i]
22
- if (lib.startsWith(base) && lib.charCodeAt(base.length) === 47 /* '/' */) {
23
- return true
24
- }
25
- }
26
-
27
- return false
28
- }
29
-
30
4
  const resolveImportCache = new Map<string, string>()
31
5
  export const resolveImport = (lib: string, importer: string, aliases: Record<string, string>) => {
32
6
  if (resolveImportCache.has(lib))
@@ -38,517 +12,6 @@ export const resolveImport = (lib: string, importer: string, aliases: Record<str
38
12
  return id
39
13
  }
40
14
 
41
- export async function getFileMap(meta: Metadata) {
42
- const map = new Map<string, FileMetadata>()
43
- const violations = new Map<string, DepMetadata[]>()
44
-
45
- for await (const path of new Bun.Glob(`**/*.{${FILE_EXTENSIONS.join(',')}}`).scan({
46
- cwd: meta.root,
47
- absolute: true,
48
- onlyFiles: true,
49
- ignore: [
50
- '**/node_modules/**',
51
- '**/.git/**',
52
- '**/dist/**',
53
- '**/out/**',
54
- '**/public/**',
55
- ],
56
- })) {
57
- const file: FileMetadata = { path, content: '', deps: new Map<string, DepMetadata>() }
58
- file.content = await Bun.file(path).text()
59
- if (!file.content.includes('import')) {
60
- map.set(path, file)
61
- continue
62
- }
63
-
64
- const dir = dirname(path)
65
- let match: RegExpExecArray | null
66
- const found: DepMetadata[] = []
67
-
68
- let importsEndIndex = 0
69
- while ((match = IMPORT_RE.exec(file.content))) {
70
- const statement = match[0]
71
- const ref = match[1]
72
- if (match[0].includes('import type') || !ref) continue
73
-
74
- const importEnd = match.index + statement.length
75
- if (importEnd > importsEndIndex) importsEndIndex = importEnd
76
-
77
- const identifiers = extractIdentifiers(statement)
78
- if (identifiers.length > 0 && !isAnyIdentifierUsed(identifiers, file.content, importEnd))
79
- continue
80
-
81
- const dep = {path: resolveImport(match[1], dir, meta.aliases), ref}
82
- file.deps.set(dep.path, dep)
83
- if (isBrowserLib(dep.path)) found.push(dep)
84
- }
85
-
86
- map.set(path, file)
87
-
88
- if (found.length || USE_CLIENT_RE.test(file.content))
89
- violations.set(path, found)
90
- }
91
-
92
- const visited = new Set<string>()
93
- const isViolator = (path: string, stack = new Set<string>()) => {
94
- if (violations.has(path)) return true
95
- if (visited.has(path) || stack.has(path)) return false
96
-
97
- stack.add(path)
98
-
99
- const file = map.get(path)
100
- if (!file) {
101
- visited.add(path)
102
- return false
103
- }
104
-
105
- for (const [depPath, dep] of file.deps) {
106
- if (isViolator(depPath, stack)) {
107
- const _violations = violations.get(path) || []
108
- violations.set(path, [..._violations, dep])
109
- visited.add(path)
110
- return true
111
- }
112
- }
113
-
114
- visited.add(path)
115
- return false
116
- }
117
-
118
- for (const [subpath, _] of map)
119
- isViolator(subpath)
120
-
121
- return { map, violations }
122
- }
123
-
124
- /**
125
- * Extracts identifiers from an import line without AST.
126
- *
127
- * Supports:
128
- * import Foo from '...'
129
- * import { Foo, Bar as Baz } from '...'
130
- * import * as Foo from '...'
131
- * import Foo, { Bar } from '...'
132
- */
133
- function extractIdentifiers(importStatement: string): string[] {
134
- // Removes the module path and the word 'import'
135
- const withoutPath = importStatement.replace(/from\s+['"][^'"]+['"]\s*;?/, '').replace(/^import\s+/, '')
136
-
137
- const identifiers: string[] = []
138
-
139
- // import * as Name
140
- const namespaceMatch = withoutPath.match(/\*\s+as\s+(\w+)/)
141
- if (namespaceMatch) return [namespaceMatch[1]];
142
-
143
- // Named imports { Foo, Bar as Baz }
144
- const namedMatch = withoutPath.match(/\{([^}]+)\}/)
145
- if (namedMatch) {
146
- for (const part of namedMatch[1].split(',')) {
147
- // "Bar as Baz" → usa "Baz" (o alias local)
148
- const alias = part.trim().match(/(?:.*\bas\s+)?(\w+)$/)
149
- if (alias) identifiers.push(alias[1]);
150
- }
151
- }
152
-
153
- // Default import (before a comma or the end): import Foo, { ... }
154
- const defaultMatch = withoutPath.match(/^(\w+)/)
155
- if (defaultMatch) identifiers.push(defaultMatch[1])
156
-
157
- return identifiers
158
- }
159
-
160
- /**
161
- * Checks if at least one of the identifiers appears in the code
162
- * after the end of the import line (avoids false positives with the import itself).
163
- *
164
- * Uses indexOf for maximum performance — no regex, no AST.
165
- */
166
- function isAnyIdentifierUsed(identifiers: string[], code: string, afterIndex: number): boolean {
167
- const body = code.slice(afterIndex)
168
-
169
- for (const id of identifiers) {
170
- let pos = body.indexOf(id)
171
- while (pos !== -1) {
172
- // Checks if it is a whole word (not part of another identifier)
173
- const before = pos === 0 ? '' : body[pos - 1]
174
- const after = body[pos + id.length] ?? ''
175
-
176
- const isWordBoundaryBefore = !/\w/.test(before)
177
- const isWordBoundaryAfter = !/\w/.test(after)
178
-
179
- if (isWordBoundaryBefore && isWordBoundaryAfter) return true
180
-
181
- pos = body.indexOf(id, pos + 1)
182
- }
183
- }
184
-
185
- return false
186
- }
187
-
188
- const SKIP_KEYS = new Set([
189
- 'span',
190
- 'ctxt',
191
- 'raw',
192
- 'hasConstructor',
193
- 'isGenerator',
194
- 'isAsync',
195
- 'typeAnnotation',
196
- 'typeParams',
197
- ])
198
-
199
- function printImport(node: unknown): string {
200
- const n = node as { span: unknown }
201
- return printSync({
202
- type: 'Module',
203
- span: n.span as never,
204
- body: [node as never], // @ts-ignore
205
- interpreter: null,
206
- }).code
207
- }
208
-
209
- function printExpression(node: unknown): string {
210
- const n = node as { span: unknown }
211
- return printSync({
212
- type: 'Module',
213
- span: n.span as never,
214
- body: [
215
- {
216
- type: 'ExpressionStatement',
217
- span: n.span,
218
- expression: node,
219
- } as never,
220
- ], // @ts-ignore
221
- interpreter: null,
222
- }).code
223
- .replace(/;$/, '')
224
- .trim()
225
- }
226
-
227
- /**
228
- * Builds a byteOffset → charIndex map to convert SWC spans
229
- * (which are UTF-8 byte offsets, base 1) in JS character indices (base 0).
230
- *
231
- * Required because characters like →, ─, 🧪 take up more than 1 byte,
232
- * causing drift between byte offset and charIndex
233
- */
234
- export function makeByteToCharMap(code: string): Int32Array {
235
- const buf = Buffer.from(code, 'utf8')
236
- const map = new Int32Array(buf.length + 1)
237
- let charIdx = 0
238
- let byteIdx = 0
239
-
240
- while (byteIdx < buf.length) {
241
- map[byteIdx] = charIdx
242
- const byte = buf[byteIdx]
243
- let byteLen = 1
244
- if ((byte & 0x80) === 0) byteLen = 1
245
- else if ((byte & 0xe0) === 0xc0) byteLen = 2
246
- else if ((byte & 0xf0) === 0xe0) byteLen = 3
247
- else if ((byte & 0xf8) === 0xf0) byteLen = 4
248
- // surrogate pair: 4-byte UTF-8 = 2 JS chars
249
- charIdx += byteLen === 4 ? 2 : 1
250
- byteIdx += byteLen
251
- }
252
-
253
- map[buf.length] = charIdx
254
- return map
255
- }
256
-
257
- /**
258
- * Checks if the usage is inside a JSXExpressionContainer, that is,
259
- * if it is wrapped in `{` and `}` in JSX. Returns the span of the container if yes.
260
- */
261
- function getJSXContainerSpan(
262
- parent: unknown,
263
- ): { start: number, end: number } | null {
264
- const p = parent as Record<string, unknown> | undefined
265
- if (p?.['type'] === 'JSXExpressionContainer') {
266
- const span = p['span'] as { start: number, end: number } | undefined
267
- if (span) return span
268
- }
269
- return null
270
- }
271
-
272
- export function findUsage(
273
- code: string,
274
- target: string,
275
- _ast?: any,
276
- ): ImportUsage[] {
277
- const ast = _ast ?? parseSync(code, { syntax: 'typescript', tsx: true })
278
- const imports: ImportUsage[] = [] // @ts-ignore
279
- const source = resolveImport(target)
280
-
281
- // collect imports
282
- for (const node of ast.body) {
283
- if (node.type !== 'ImportDeclaration') continue
284
-
285
- const decl = node as {
286
- type: string,
287
- source: { value: string },
288
- specifiers: Array<{
289
- type: string,
290
- imported?: { value: string },
291
- local: { value: string },
292
- }>,
293
- span: { start: number, end: number },
294
- }
295
-
296
- if (decl.source.value !== target) continue
297
-
298
- for (const specifier of decl.specifiers) {
299
- if (specifier.type !== 'ImportSpecifier' && specifier.type !== 'ImportDefaultSpecifier') continue
300
-
301
- imports.push({
302
- source,
303
- // source: decl.source.value,
304
- imported: specifier.type === 'ImportDefaultSpecifier' ? 'default' : specifier.imported?.value ?? specifier.local.value,
305
- local: specifier.local.value,
306
- importStart: decl.span.start,
307
- importEnd: decl.span.end,
308
- importText: printImport(node),
309
- usages: [],
310
- })
311
- }
312
- }
313
-
314
- if (imports.length === 0) return imports
315
-
316
- // fast lookup
317
- const map = new Map<string, ImportUsage>()
318
-
319
- for (const item of imports)
320
- map.set(item.local, item)
321
-
322
- // walk — skip the keys that never contain us children
323
- function walk(node: unknown, parent?: unknown) {
324
- if (!node || typeof node !== 'object') return
325
-
326
- const n = node as Record<string, unknown>
327
-
328
- if (n['type'] === 'JSXElement') { // <Counter /> / <Counter></Counter>
329
- const opening = n['opening'] as Record<string, unknown> | undefined
330
-
331
- if (opening?.['name'] && (opening['name'] as Record<string, unknown>)['type'] === 'Identifier') {
332
- const local = (opening['name'] as Record<string, unknown>)['value'] as string
333
- const found = map.get(local)
334
-
335
- if (found) {
336
- found.usages.push({
337
- type: 'jsx',
338
- source: found.source,
339
- imported: found.imported,
340
- local,
341
- start: (n['span'] as { start: number }).start,
342
- end: (n['span'] as { end: number }).end,
343
- text: printExpression(node),
344
- node,
345
- parent,
346
- })
347
- }
348
- }
349
- }
350
-
351
- if (n['type'] === 'CallExpression') { // Counter() or {Counter()}
352
- const callee = n['callee'] as Record<string, unknown> | undefined
353
-
354
- if (callee?.['type'] === 'Identifier') {
355
- const local = callee['value'] as string
356
- const found = map.get(local)
357
-
358
- if (found) {
359
- // If the parent is JSXExpressionContainer ({Counter()}), expand the span
360
- // to include the braces { }
361
- const containerSpan = getJSXContainerSpan(parent)
362
- const nodeSpan = n['span'] as { start: number, end: number }
363
-
364
- found.usages.push({
365
- type: 'call',
366
- source: found.source,
367
- imported: found.imported,
368
- local,
369
- start: containerSpan ? containerSpan.start : nodeSpan.start,
370
- end: containerSpan ? containerSpan.end : nodeSpan.end,
371
- text: printExpression(node),
372
- node,
373
- parent,
374
- })
375
- }
376
- }
377
- }
378
-
379
- if (n['type'] === 'Identifier') { // {Counter} in JSX or Counter as a reference
380
- const local = n['value'] as string
381
- const found = map.get(local)
382
- const p = parent as Record<string, unknown> | undefined
383
-
384
- if (
385
- found &&
386
- p?.['type'] !== 'ImportSpecifier' &&
387
- p?.['type'] !== 'JSXOpeningElement' &&
388
- p?.['type'] !== 'JSXClosingElement' && // avoids duplicating usage of <X></X>
389
- p?.['type'] !== 'CallExpression'
390
- ) {
391
- // If the parent is JSXExpressionContainer ({Counter}), expand the span to include the braces { }
392
- const containerSpan = getJSXContainerSpan(parent)
393
- const nodeSpan = n['span'] as { start: number, end: number }
394
-
395
- found.usages.push({
396
- type: 'reference',
397
- source: found.source,
398
- imported: found.imported,
399
- local,
400
- start: containerSpan ? containerSpan.start : nodeSpan.start,
401
- end: containerSpan ? containerSpan.end : nodeSpan.end,
402
- text: local,
403
- node,
404
- parent,
405
- })
406
- }
407
- }
408
-
409
- for (const key in n) { // recurse
410
- if (SKIP_KEYS.has(key)) continue
411
-
412
- const value = n[key]
413
-
414
- if (Array.isArray(value)) {
415
- for (const item of value)
416
- walk(item, node)
417
- } else if (value && typeof value === 'object') {
418
- walk(value, node)
419
- }
420
- }
421
- }
422
-
423
- walk(ast)
424
- return imports
425
- }
426
-
427
- /**
428
- * Removes snippets corresponding to the reported usages from the source code.
429
- *
430
- * SWC spans are UTF-8 (base 1) byte offsets, not JS character indices.
431
- * The conversion is done internally via makeByteToCharMap — the caller
432
- * does not need to worry about it.
433
- */
434
- export function removeUsage(
435
- code: string,
436
- targets: Usage | Usage[],
437
- ): string {
438
- const list = Array.isArray(targets) ? targets : [targets]
439
- if (!list.length) return code
440
-
441
- // converte byte offset (base 1) → char index JS (base 0)
442
- const b2c = makeByteToCharMap(code)
443
- const toChar = (byteOffset: number) => b2c[byteOffset - 1]
444
-
445
- const raw: Array<[number, number]> = []
446
-
447
- for (const usage of list) {
448
- if (usage.start < usage.end)
449
- raw.push([toChar(usage.start), toChar(usage.end)])
450
- }
451
-
452
- if (!raw.length) return code
453
-
454
- raw.sort((a, b) => a[0] - b[0])
455
-
456
- const merged: Array<[number, number]> = []
457
-
458
- for (const [start, end] of raw) {
459
- const last = merged[merged.length - 1]
460
- if (last && start >= last[0] && end <= last[1]) continue
461
- if (last && start <= last[1]) {
462
- last[1] = Math.max(last[1], end)
463
- continue
464
- }
465
- merged.push([start, end])
466
- }
467
-
468
- const parts: string[] = []
469
- let cursor = 0
470
-
471
- for (const [start, end] of merged) {
472
- if (start > cursor) parts.push(code.slice(cursor, start))
473
- cursor = end
474
- }
475
-
476
- if (cursor < code.length) parts.push(code.slice(cursor))
477
-
478
- return parts.join('').replace(/\n{3,}/g, '\n\n')
479
- }
480
-
481
-
482
- /**
483
- * Extracts props from a JSXElement node from the SWC AST.
484
- * Supports: string literals, numeric literals, boolean (true/false), simple expressions.
485
- */
486
- export function extractPropsFromJSX(node: unknown): Record<string, unknown> {
487
- const n = node as Record<string, unknown>
488
- const opening = n['opening'] as Record<string, unknown> | undefined
489
- if (!opening) return {}
490
-
491
- const attrs = opening['attributes'] as Array<Record<string, unknown>> | undefined
492
- if (!attrs) return {}
493
-
494
- const props: Record<string, unknown> = {}
495
-
496
- for (const attr of attrs) {
497
- if (attr['type'] !== 'JSXAttribute') continue
498
-
499
- const nameNode = attr['name'] as Record<string, unknown>
500
- const propName = nameNode['value'] as string
501
-
502
- const valueNode = attr['value'] as Record<string, unknown> | null | undefined
503
-
504
- if (valueNode === null || valueNode === undefined) {
505
- // <Comp disabled /> → disabled: true
506
- props[propName] = true
507
- continue
508
- }
509
-
510
- switch (valueNode['type']) {
511
- case 'StringLiteral':
512
- props[propName] = valueNode['value']
513
- break
514
-
515
- case 'JSXExpressionContainer': {
516
- const expr = valueNode['expression'] as Record<string, unknown>
517
- switch (expr['type']) {
518
- case 'NumericLiteral':
519
- case 'StringLiteral':
520
- props[propName] = expr['value']
521
- break
522
- case 'BooleanLiteral':
523
- props[propName] = expr['value']
524
- break
525
- case 'NullLiteral':
526
- props[propName] = null
527
- break
528
- case 'ArrayExpression': {
529
- // Serialize an array of simple literals
530
- const elems = expr['elements'] as Array<{ expression: Record<string, unknown> }> | undefined
531
- props[propName] = elems?.map(e => e?.expression?.['value']) ?? []
532
- break
533
- }
534
- default:
535
- // Complex expressions: keep as undefined (not safely serializable)
536
- break
537
- }
538
- break
539
- }
540
-
541
- default:
542
- break
543
- }
544
- }
545
-
546
- return props
547
- }
548
-
549
-
550
-
551
-
552
15
  const reCache = new Map<string, string>()
553
16
  export function removeExports(id: string, code: string, targets: Set<string>) {
554
17
  let should = false
@@ -6,19 +6,101 @@ export const LAYOUT_RE = /(?:.*\/)?_?(layout)\.(tsx?|jsx?)$/i
6
6
  export const DOCTYPE_RE = /<!doctype html/i
7
7
 
8
8
  // export const USE_CLIENT_RE = /^[\s\r\n]*["']use client["'](?=[\s\r\n]|$)/m
9
- export const USE_CLIENT_RE = /^(?:\s|\/\/.*\n|\/\*[\s\S]*?\*\/)*["']use client["'](?=\s|;|$)/
10
- // export const IMPORT_RE = /import\s+(?:type\s+)?(?:[\w*\s{},]+from\s+)?['"]([^'"]+)['"]/g
11
- export const IMPORT_RE = /(?:\/\/.*|\/\*[\s\S]*?\*\/)|import\s+(?:type\s+)?(?:[\w*\s{},]+from\s+)?['"]([^'"]+)['"]/g
12
- export const BROWSER_ONLY = [
13
- 'preact', '@preact/signals',
14
- 'react', 'react-dom',
15
- 'wouter', 'wouter-preact',
16
- 'framer-motion', 'gsap', 'three',
17
- ]
18
- export const BROWSER_ONLY_SET = new Set(BROWSER_ONLY)
9
+ export const USE_CLIENT_RE = /^(?:\s|\/\/.*\n|\/\*[\s\S]*?\*\/)*["']use client["'](?=\s|;|$)/
19
10
 
20
11
  export const LAYOUT_NAMES = ['_layout', 'layout']
21
12
  export const NF_NAMES = ['404', 'not-found']
22
- export const FILE_EXTENSIONS = ['tsx', 'ts', 'jsx', 'js'] as const
23
- export const CONFIG_EXTENSIONS = ['json', 'toml', 'yaml'] as const
24
- export const EXTENSIONS = [...FILE_EXTENSIONS, ...CONFIG_EXTENSIONS] as const
13
+ export const FILE_EXTENSIONS = ['tsx', 'ts', 'jsx', 'js']
14
+ export const CONFIG_EXTENSIONS = ['json', 'toml', 'yaml', 'json5', 'jsonc']
15
+ export const EXTENSIONS = [...FILE_EXTENSIONS, ...CONFIG_EXTENSIONS]
16
+
17
+ export const EXTENSIONS_LIST = new Set([
18
+ // ===== IMAGES =====
19
+ 'jpg', 'jpeg', 'jpe', 'jfif', 'png', 'gif', 'bmp', 'dib', 'webp', 'svg', 'svgz',
20
+ 'ico', 'tif', 'tiff', 'heic', 'heif', 'avif', 'raw', 'cr2', 'nef', 'arw', 'dng',
21
+ 'psd', 'ai', 'eps', 'indd', 'sketch', 'fig', 'xd',
22
+
23
+ // ===== DOCS =====
24
+ 'pdf', 'doc', 'docx', 'docm', 'dot', 'dotx', 'xls', 'xlsx', 'xlsm', 'xlsb',
25
+ 'xlt', 'xltx', 'ppt', 'pptx', 'pptm', 'pot', 'potx', 'pps', 'ppsx',
26
+ 'txt', 'text', 'rtf', 'odt', 'ods', 'odp', 'odg', 'ott', 'ots', 'otp',
27
+ 'pages', 'numbers', 'key', 'epub', 'mobi', 'azw', 'azw3', 'djvu', 'tex', 'latex',
28
+
29
+ // ===== AUDIO =====
30
+ 'mp3', 'wav', 'flac', 'aac', 'ogg', 'oga', 'opus', 'wma', 'm4a', 'm4b',
31
+ 'aiff', 'aif', 'alac', 'ape', 'wv', 'mid', 'midi', 'amr', 'ac3', 'dts',
32
+
33
+ // ===== VIDEO =====
34
+ 'mp4', 'm4v', 'avi', 'mkv', 'mov', 'wmv', 'flv', 'f4v', 'webm', 'mpg', 'mpeg',
35
+ 'mpe', 'm2v', '3gp', '3g2', 'ogv', 'ts', 'mts', 'm2ts', 'vob', 'rm', 'rmvb',
36
+ 'asf', 'divx', 'xvid',
37
+
38
+ // ===== COMPACT =====
39
+ 'zip', 'rar', '7z', 'tar', 'gz', 'gzip', 'bz2', 'bzip2', 'xz', 'lz', 'lzma',
40
+ 'z', 'tgz', 'tbz', 'tbz2', 'txz', 'cab', 'iso', 'img', 'dmg', 'jar', 'war',
41
+ 'ear', 'apk', 'ipa', 'deb', 'rpm', 'msi', 'pkg', 'snap', 'flatpak',
42
+
43
+ // ===== DEV =====
44
+ // Web
45
+ 'html', 'htm', 'xhtml', 'shtml', 'css', 'scss', 'sass', 'less', 'styl',
46
+ 'js', 'mjs', 'cjs', 'jsx', 'ts', 'tsx', 'mts', 'cts', 'vue', 'svelte', 'astro',
47
+
48
+ // Backend / Geral
49
+ 'py', 'pyw', 'pyi', 'pyx', 'rb', 'rbw', 'php', 'php3', 'php4', 'php5', 'phtml',
50
+ 'java', 'class', 'jar', 'kt', 'kts', 'scala', 'sc', 'groovy', 'gradle',
51
+ 'c', 'h', 'cc', 'cpp', 'cxx', 'c++', 'hpp', 'hxx', 'h++', 'cs', 'csx',
52
+ 'go', 'rs', 'swift', 'm', 'mm', 'dart', 'lua', 'pl', 'pm', 't', 'raku',
53
+ 'r', 'rmd', 'jl', 'nim', 'zig', 'v', 'cr', 'ex', 'exs', 'erl', 'hrl',
54
+ 'clj', 'cljs', 'cljc', 'edn', 'hs', 'lhs', 'ml', 'mli', 'fs', 'fsx', 'fsi',
55
+ 'elm', 'purs', 'reason', 're', 'rei', 'ocaml', 'pas', 'pp', 'f', 'f90', 'f95',
56
+ 'for', 'f77', 'asm', 's', 'nasm', 'wasm', 'wat', 'sol', 'move', 'cairo',
57
+ 'vb', 'vbs', 'vba', 'bas', 'frm', 'cls', 'ada', 'adb', 'ads',
58
+ 'cob', 'cbl', 'cobol', 'pro', 'prolog', 'lisp', 'lsp', 'cl', 'el', 'scm',
59
+ 'rkt', 'tcl', 'awk', 'sed', 'ahk', 'au3', 'applescript', 'scpt',
60
+
61
+ // Shell / Scripts
62
+ 'sh', 'bash', 'zsh', 'fish', 'ksh', 'csh', 'tcsh', 'ps1', 'psm1', 'psd1',
63
+ 'bat', 'cmd', 'btm',
64
+
65
+ // ===== DATA / CONFIGS =====
66
+ 'json', 'json5', 'jsonc', 'jsonl', 'ndjson', 'geojson', 'topojson',
67
+ 'yaml', 'yml', 'toml', 'ini', 'cfg', 'conf', 'config', 'properties', 'env',
68
+ 'xml', 'xsd', 'xsl', 'xslt', 'dtd', 'rss', 'atom', 'plist',
69
+ 'csv', 'tsv', 'psv', 'dsv', 'parquet', 'avro', 'orc', 'arrow', 'feather',
70
+ 'sql', 'sqlite', 'sqlite3', 'db', 'mdb', 'accdb', 'dbf', 'dump',
71
+ 'graphql', 'gql', 'proto', 'thrift', 'avsc', 'capnp',
72
+ 'lock', 'editorconfig', 'gitignore', 'gitattributes', 'npmrc', 'nvmrc',
73
+ 'dockerignore', 'htaccess', 'htpasswd',
74
+
75
+ // ===== MARKUP / DOCS =====
76
+ 'md', 'markdown', 'mdown', 'mkd', 'mkdn', 'mdx', 'rst', 'adoc', 'asciidoc',
77
+ 'org', 'wiki', 'textile', 'creole', 'pod',
78
+
79
+ // ===== FONTS =====
80
+ 'ttf', 'otf', 'woff', 'woff2', 'eot', 'fon', 'fnt', 'pfb', 'pfm',
81
+
82
+ // ===== 3D / CAD / MODEL =====
83
+ 'obj', 'fbx', 'dae', '3ds', 'max', 'blend', 'stl', 'step', 'stp', 'iges', 'igs',
84
+ 'dwg', 'dxf', 'dgn', 'skp', 'sldprt', 'sldasm', 'ipt', 'iam', 'prt', 'asm',
85
+ 'gltf', 'glb', 'usd', 'usda', 'usdc', 'usdz', 'abc', 'ply', 'x3d',
86
+
87
+ // ===== EXE / BINS =====
88
+ 'exe', 'com', 'scr', 'pif', 'msi', 'msp', 'appx', 'appxbundle', 'msix',
89
+ 'app', 'dmg', 'pkg', 'mpkg', 'deb', 'rpm', 'apk', 'ipa', 'snap', 'flatpak',
90
+ 'appimage', 'run', 'bin', 'elf', 'so', 'dll', 'dylib', 'lib', 'a', 'o', 'obj',
91
+ 'out', 'ko', 'sys', 'drv', 'ocx', 'cpl',
92
+
93
+ // ===== CERT / SECURITY =====
94
+ 'pem', 'crt', 'cer', 'der', 'pfx', 'p12', 'key', 'pub', 'asc', 'gpg', 'pgp',
95
+ 'csr', 'crl', 'jks', 'keystore',
96
+
97
+ // ===== SUBTITLES =====
98
+ 'srt', 'vtt', 'ass', 'ssa', 'sub', 'sbv', 'ttml', 'dfxp',
99
+
100
+ // ===== EBOOKS =====
101
+ 'epub', 'mobi', 'azw', 'azw3', 'fb2', 'lit', 'lrf', 'pdb', 'prc', 'cbr', 'cbz',
102
+
103
+ // ===== OTHERS / LOGS =====
104
+ 'log', 'tmp', 'temp', 'bak', 'backup', 'old', 'orig', 'swp', 'swo',
105
+ 'pid', 'sock', 'srt', 'diff', 'patch', 'rej'
106
+ ])
@@ -1,7 +1,7 @@
1
1
  import { extname, join } from 'node:path'
2
2
  import { existsSync } from 'node:fs'
3
3
  import { EXTENSIONS, FILE_EXTENSIONS } from './constants'
4
- import type { Metadata } from '@/types'
4
+ import type { Metadata } from './types'
5
5
 
6
6
  const extensionCache = new Map<string, string | null>()
7
7
  export function resolveExtension(filePath: string): string | null {