one 1.17.3 → 1.17.5

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 (94) hide show
  1. package/dist/cjs/metro-config/withOne.test.cjs +8 -0
  2. package/dist/cjs/metro-config/withOne.test.native.js +8 -0
  3. package/dist/cjs/metro-config/withOne.test.native.js.map +1 -1
  4. package/dist/cjs/typed-routes/generateRouteTypes.cjs +12 -2
  5. package/dist/cjs/typed-routes/generateRouteTypes.native.js +12 -2
  6. package/dist/cjs/typed-routes/generateRouteTypes.native.js.map +1 -1
  7. package/dist/cjs/typed-routes/generateRouteTypes.test.cjs +58 -0
  8. package/dist/cjs/typed-routes/generateRouteTypes.test.native.js +61 -0
  9. package/dist/cjs/typed-routes/generateRouteTypes.test.native.js.map +1 -0
  10. package/dist/cjs/typed-routes/getTypedRoutesDeclarationFile.cjs +5 -5
  11. package/dist/cjs/typed-routes/getTypedRoutesDeclarationFile.native.js +5 -5
  12. package/dist/cjs/typed-routes/getTypedRoutesDeclarationFile.native.js.map +1 -1
  13. package/dist/cjs/typed-routes/getTypedRoutesDeclarationFile.test.cjs +18 -0
  14. package/dist/cjs/typed-routes/getTypedRoutesDeclarationFile.test.native.js +25 -0
  15. package/dist/cjs/typed-routes/getTypedRoutesDeclarationFile.test.native.js.map +1 -0
  16. package/dist/cjs/utils/routeFileWatch.cjs +58 -0
  17. package/dist/cjs/utils/routeFileWatch.native.js +62 -0
  18. package/dist/cjs/utils/routeFileWatch.native.js.map +1 -0
  19. package/dist/cjs/utils/routeFileWatch.test.cjs +92 -0
  20. package/dist/cjs/utils/routeFileWatch.test.native.js +95 -0
  21. package/dist/cjs/utils/routeFileWatch.test.native.js.map +1 -0
  22. package/dist/cjs/vite/plugins/fileSystemRouterPlugin.cjs +16 -7
  23. package/dist/cjs/vite/plugins/fileSystemRouterPlugin.native.js +16 -7
  24. package/dist/cjs/vite/plugins/fileSystemRouterPlugin.native.js.map +1 -1
  25. package/dist/cjs/vite/plugins/fileSystemRouterPlugin.test.cjs +111 -0
  26. package/dist/cjs/vite/plugins/fileSystemRouterPlugin.test.native.js +116 -0
  27. package/dist/cjs/vite/plugins/fileSystemRouterPlugin.test.native.js.map +1 -0
  28. package/dist/cjs/vite/plugins/generateFileSystemRouteTypesPlugin.cjs +9 -9
  29. package/dist/cjs/vite/plugins/generateFileSystemRouteTypesPlugin.native.js +10 -10
  30. package/dist/cjs/vite/plugins/generateFileSystemRouteTypesPlugin.native.js.map +1 -1
  31. package/dist/esm/metro-config/withOne.test.mjs +8 -0
  32. package/dist/esm/metro-config/withOne.test.mjs.map +1 -1
  33. package/dist/esm/metro-config/withOne.test.native.js +8 -0
  34. package/dist/esm/metro-config/withOne.test.native.js.map +1 -1
  35. package/dist/esm/typed-routes/generateRouteTypes.mjs +13 -3
  36. package/dist/esm/typed-routes/generateRouteTypes.mjs.map +1 -1
  37. package/dist/esm/typed-routes/generateRouteTypes.native.js +13 -3
  38. package/dist/esm/typed-routes/generateRouteTypes.native.js.map +1 -1
  39. package/dist/esm/typed-routes/generateRouteTypes.test.mjs +35 -0
  40. package/dist/esm/typed-routes/generateRouteTypes.test.mjs.map +1 -0
  41. package/dist/esm/typed-routes/generateRouteTypes.test.native.js +35 -0
  42. package/dist/esm/typed-routes/generateRouteTypes.test.native.js.map +1 -0
  43. package/dist/esm/typed-routes/getTypedRoutesDeclarationFile.mjs +5 -5
  44. package/dist/esm/typed-routes/getTypedRoutesDeclarationFile.mjs.map +1 -1
  45. package/dist/esm/typed-routes/getTypedRoutesDeclarationFile.native.js +5 -5
  46. package/dist/esm/typed-routes/getTypedRoutesDeclarationFile.native.js.map +1 -1
  47. package/dist/esm/typed-routes/getTypedRoutesDeclarationFile.test.mjs +19 -0
  48. package/dist/esm/typed-routes/getTypedRoutesDeclarationFile.test.mjs.map +1 -0
  49. package/dist/esm/typed-routes/getTypedRoutesDeclarationFile.test.native.js +23 -0
  50. package/dist/esm/typed-routes/getTypedRoutesDeclarationFile.test.native.js.map +1 -0
  51. package/dist/esm/utils/routeFileWatch.mjs +20 -0
  52. package/dist/esm/utils/routeFileWatch.mjs.map +1 -0
  53. package/dist/esm/utils/routeFileWatch.native.js +21 -0
  54. package/dist/esm/utils/routeFileWatch.native.js.map +1 -0
  55. package/dist/esm/utils/routeFileWatch.test.mjs +69 -0
  56. package/dist/esm/utils/routeFileWatch.test.mjs.map +1 -0
  57. package/dist/esm/utils/routeFileWatch.test.native.js +69 -0
  58. package/dist/esm/utils/routeFileWatch.test.native.js.map +1 -0
  59. package/dist/esm/vite/plugins/fileSystemRouterPlugin.mjs +16 -7
  60. package/dist/esm/vite/plugins/fileSystemRouterPlugin.mjs.map +1 -1
  61. package/dist/esm/vite/plugins/fileSystemRouterPlugin.native.js +16 -7
  62. package/dist/esm/vite/plugins/fileSystemRouterPlugin.native.js.map +1 -1
  63. package/dist/esm/vite/plugins/fileSystemRouterPlugin.test.mjs +88 -0
  64. package/dist/esm/vite/plugins/fileSystemRouterPlugin.test.mjs.map +1 -0
  65. package/dist/esm/vite/plugins/fileSystemRouterPlugin.test.native.js +90 -0
  66. package/dist/esm/vite/plugins/fileSystemRouterPlugin.test.native.js.map +1 -0
  67. package/dist/esm/vite/plugins/generateFileSystemRouteTypesPlugin.mjs +9 -9
  68. package/dist/esm/vite/plugins/generateFileSystemRouteTypesPlugin.mjs.map +1 -1
  69. package/dist/esm/vite/plugins/generateFileSystemRouteTypesPlugin.native.js +10 -10
  70. package/dist/esm/vite/plugins/generateFileSystemRouteTypesPlugin.native.js.map +1 -1
  71. package/package.json +10 -10
  72. package/src/metro-config/withOne.test.ts +14 -0
  73. package/src/typed-routes/generateRouteTypes.test.ts +53 -0
  74. package/src/typed-routes/generateRouteTypes.ts +14 -3
  75. package/src/typed-routes/getTypedRoutesDeclarationFile.test.ts +22 -0
  76. package/src/typed-routes/getTypedRoutesDeclarationFile.ts +5 -5
  77. package/src/utils/routeFileWatch.test.ts +94 -0
  78. package/src/utils/routeFileWatch.ts +40 -0
  79. package/src/vite/plugins/fileSystemRouterPlugin.test.ts +106 -0
  80. package/src/vite/plugins/fileSystemRouterPlugin.tsx +21 -13
  81. package/src/vite/plugins/generateFileSystemRouteTypesPlugin.tsx +16 -17
  82. package/types/typed-routes/generateRouteTypes.d.ts.map +1 -1
  83. package/types/typed-routes/generateRouteTypes.test.d.ts +2 -0
  84. package/types/typed-routes/generateRouteTypes.test.d.ts.map +1 -0
  85. package/types/typed-routes/getTypedRoutesDeclarationFile.test.d.ts +2 -0
  86. package/types/typed-routes/getTypedRoutesDeclarationFile.test.d.ts.map +1 -0
  87. package/types/utils/routeFileWatch.d.ts +9 -0
  88. package/types/utils/routeFileWatch.d.ts.map +1 -0
  89. package/types/utils/routeFileWatch.test.d.ts +2 -0
  90. package/types/utils/routeFileWatch.test.d.ts.map +1 -0
  91. package/types/vite/plugins/fileSystemRouterPlugin.d.ts.map +1 -1
  92. package/types/vite/plugins/fileSystemRouterPlugin.test.d.ts +2 -0
  93. package/types/vite/plugins/fileSystemRouterPlugin.test.d.ts.map +1 -0
  94. package/types/vite/plugins/generateFileSystemRouteTypesPlugin.d.ts.map +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "one",
3
- "version": "1.17.3",
3
+ "version": "1.17.5",
4
4
  "license": "BSD-3-Clause",
5
5
  "sideEffects": [
6
6
  "setup.mjs",
@@ -180,18 +180,18 @@
180
180
  "@cloudflare/vite-plugin": "^1.33.1",
181
181
  "@swc/core": "^1.14.0",
182
182
  "@ungap/structured-clone": "^1.2.0",
183
- "@vxrn/color-scheme": "1.17.3",
184
- "@vxrn/compiler": "1.17.3",
185
- "@vxrn/resolve": "1.17.3",
186
- "@vxrn/tslib-lite": "1.17.3",
187
- "@vxrn/use-isomorphic-layout-effect": "1.17.3",
188
- "@vxrn/vite-plugin-metro": "1.17.3",
183
+ "@vxrn/color-scheme": "1.17.5",
184
+ "@vxrn/compiler": "1.17.5",
185
+ "@vxrn/resolve": "1.17.5",
186
+ "@vxrn/tslib-lite": "1.17.5",
187
+ "@vxrn/use-isomorphic-layout-effect": "1.17.5",
188
+ "@vxrn/vite-plugin-metro": "1.17.5",
189
189
  "babel-dead-code-elimination": "1.0.10",
190
190
  "babel-plugin-module-resolver": "^5.0.2",
191
191
  "babel-preset-expo": "*",
192
192
  "citty": "^0.1.6",
193
193
  "core-js": "^3.38.1",
194
- "create-vxrn": "1.17.3",
194
+ "create-vxrn": "1.17.5",
195
195
  "escape-string-regexp": "^5.0.0",
196
196
  "expo-linking": "~55.0.7",
197
197
  "expo-modules-core": "~55.0.16",
@@ -210,8 +210,8 @@
210
210
  "ts-pattern": "^5.6.2",
211
211
  "tsconfig-paths": "^4",
212
212
  "use-latest-callback": "^0.2.3",
213
- "vite": "^8.0.0",
214
- "vxrn": "1.17.3",
213
+ "vite": "^8.0.13",
214
+ "vxrn": "1.17.5",
215
215
  "ws": "^8.18.0",
216
216
  "xxhashjs": "^0.2.2"
217
217
  },
@@ -115,4 +115,18 @@ describe('withOne', () => {
115
115
  expect(config.resolver.extraNodeModules['fixture-singleton']).toBe(fixtureRoot)
116
116
  expect(config.watchFolders).toContain(path.join(fixtureRoot, 'shared'))
117
117
  })
118
+
119
+ it('rewrites native default index bundle requests to the One entry', async () => {
120
+ const config = (await withOne(projectRoot, { loadViteConfig: false })) as any
121
+ const rewriteRequestUrl = config.server.rewriteRequestUrl
122
+
123
+ expect(
124
+ rewriteRequestUrl('/.expo/.virtual-metro-entry.bundle?platform=ios&dev=true')
125
+ ).toContain('/packages/one/metro-entry.bundle?platform=ios&dev=true')
126
+ expect(
127
+ rewriteRequestUrl('/index.bundle?platform=ios&dev=true&hot=true&minify=false')
128
+ ).toContain(
129
+ '/packages/one/metro-entry.bundle?platform=ios&dev=true&hot=true&minify=false'
130
+ )
131
+ })
118
132
  })
@@ -0,0 +1,53 @@
1
+ import {
2
+ mkdirSync,
3
+ mkdtempSync,
4
+ rmSync,
5
+ statSync,
6
+ utimesSync,
7
+ writeFileSync,
8
+ } from 'node:fs'
9
+ import { tmpdir } from 'node:os'
10
+ import path from 'node:path'
11
+ import { afterEach, describe, expect, it } from 'vitest'
12
+ import { generateRouteTypes } from './generateRouteTypes'
13
+
14
+ describe(generateRouteTypes, () => {
15
+ const originalCwd = process.cwd()
16
+ let tempRoot: string | undefined
17
+
18
+ afterEach(() => {
19
+ process.chdir(originalCwd)
20
+
21
+ if (tempRoot) {
22
+ rmSync(tempRoot, { recursive: true, force: true })
23
+ tempRoot = undefined
24
+ }
25
+ })
26
+
27
+ it('does not rewrite routes declarations when contents are unchanged', async () => {
28
+ tempRoot = mkdtempSync(path.join(tmpdir(), 'one-routes-types-'))
29
+ const appDir = path.join(tempRoot, 'app')
30
+ mkdirSync(appDir)
31
+ writeFileSync(
32
+ path.join(appDir, 'index+ssg.tsx'),
33
+ 'export default function Index() { return null }\n'
34
+ )
35
+ writeFileSync(
36
+ path.join(appDir, '[slug]+ssg.tsx'),
37
+ 'export default function Slug() { return null }\n'
38
+ )
39
+
40
+ process.chdir(tempRoot)
41
+
42
+ const outFile = path.join('app', 'routes.d.ts')
43
+ await generateRouteTypes(outFile, 'app')
44
+
45
+ const oldDate = new Date('2001-01-01T00:00:00.000Z')
46
+ utimesSync(outFile, oldDate, oldDate)
47
+ const previousMtimeMs = statSync(outFile).mtimeMs
48
+
49
+ await generateRouteTypes(outFile, 'app')
50
+
51
+ expect(statSync(outFile).mtimeMs).toBe(previousMtimeMs)
52
+ })
53
+ })
@@ -1,4 +1,4 @@
1
- import { writeFile } from 'node:fs/promises'
1
+ import { readFile, writeFile } from 'node:fs/promises'
2
2
  import { dirname, join } from 'node:path'
3
3
  import FSExtra from 'fs-extra'
4
4
  import micromatch from 'micromatch'
@@ -33,8 +33,19 @@ export async function generateRouteTypes(
33
33
  const context = globbedRoutesToRouteContext(routes, routerRoot)
34
34
  const declarations = getTypedRoutesDeclarationFile(context)
35
35
  const outDir = dirname(outFile)
36
- await FSExtra.ensureDir(outDir)
37
- await writeFile(outFile, declarations)
36
+ let currentDeclarations: string | undefined
37
+ try {
38
+ currentDeclarations = await readFile(outFile, 'utf8')
39
+ } catch (error) {
40
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
41
+ throw error
42
+ }
43
+ }
44
+
45
+ if (currentDeclarations !== declarations) {
46
+ await FSExtra.ensureDir(outDir)
47
+ await writeFile(outFile, declarations)
48
+ }
38
49
 
39
50
  // If experimental.typedRoutesGeneration is enabled, inject helpers into route files
40
51
  if (typedRoutesMode) {
@@ -0,0 +1,22 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import type { One } from '../vite/types'
3
+ import { getTypedRoutesDeclarationFile } from './getTypedRoutesDeclarationFile'
4
+
5
+ function createRouteContext(paths: string[]) {
6
+ const context = (() => ({ default() {} })) as unknown as One.RouteContext
7
+ Object.defineProperty(context, 'keys', {
8
+ value: () => paths,
9
+ })
10
+ return context
11
+ }
12
+
13
+ describe(getTypedRoutesDeclarationFile, () => {
14
+ it('does not emit trailing whitespace for multi-line route unions', () => {
15
+ const declaration = getTypedRoutesDeclarationFile(
16
+ createRouteContext(['./index+ssg.tsx', './about+ssg.tsx', './[slug]+ssg.tsx'])
17
+ )
18
+
19
+ expect(declaration).toContain(' StaticRoutes:\n | `/`')
20
+ expect(declaration).not.toMatch(/[ \t]+$/m)
21
+ })
22
+ })
@@ -36,9 +36,9 @@ import type { OneRouter } from 'one'
36
36
  declare module 'one' {
37
37
  export namespace OneRouter {
38
38
  export interface __routes<T extends string = string> extends Record<string, unknown> {
39
- StaticRoutes: ${setToUnionType(staticRoutes)}
40
- DynamicRoutes: ${setToUnionType(dynamicRoutes)}
41
- DynamicRouteTemplate: ${setToUnionType(dynamicRouteContextKeys)}
39
+ StaticRoutes:${setToUnionType(staticRoutes)}
40
+ DynamicRoutes:${setToUnionType(dynamicRoutes)}
41
+ DynamicRouteTemplate:${setToUnionType(dynamicRouteContextKeys)}
42
42
  IsTyped: true
43
43
  ${hasRoutes ? `RouteTypes: ${generateRouteTypesMap(dynamicRouteContextKeys)}` : ''}
44
44
  }
@@ -189,9 +189,9 @@ function addRouteNode(
189
189
  * Formats with one route per line for cleaner git diffs
190
190
  */
191
191
  const setToUnionType = <T>(set: Set<T>) => {
192
- if (set.size === 0) return 'never'
192
+ if (set.size === 0) return ' never'
193
193
  const sorted = [...set].sort()
194
- if (sorted.length === 1) return `\`${sorted[0]}\``
194
+ if (sorted.length === 1) return ` \`${sorted[0]}\``
195
195
  // format as multi-line union for cleaner diffs
196
196
  return '\n | ' + sorted.map((s) => `\`${s}\``).join('\n | ')
197
197
  }
@@ -0,0 +1,94 @@
1
+ import path from 'node:path'
2
+ import { describe, expect, it } from 'vitest'
3
+ import {
4
+ isPathInsideDirectory,
5
+ isRouteFilePath,
6
+ isRouteFileWatchEvent,
7
+ } from './routeFileWatch'
8
+
9
+ describe(isRouteFilePath, () => {
10
+ it('matches route source files', () => {
11
+ expect(isRouteFilePath('/project/app/index.ts')).toBe(true)
12
+ expect(isRouteFilePath('/project/app/index.tsx')).toBe(true)
13
+ expect(isRouteFilePath('/project/app/index.js')).toBe(true)
14
+ expect(isRouteFilePath('/project/app/index.jsx')).toBe(true)
15
+ })
16
+
17
+ it('ignores non-route files', () => {
18
+ expect(isRouteFilePath('/project/app/tamagui.generated.css')).toBe(false)
19
+ expect(isRouteFilePath('/project/app/routes.d.ts')).toBe(false)
20
+ })
21
+ })
22
+
23
+ describe(isPathInsideDirectory, () => {
24
+ it('only matches real descendants of the router root', () => {
25
+ const routerRoot = path.resolve('/project/app')
26
+
27
+ expect(isPathInsideDirectory('/project/app/index.tsx', routerRoot)).toBe(true)
28
+ expect(isPathInsideDirectory('/project/app-copy/index.tsx', routerRoot)).toBe(false)
29
+ expect(isPathInsideDirectory('/project/app', routerRoot)).toBe(false)
30
+ })
31
+ })
32
+
33
+ describe(isRouteFileWatchEvent, () => {
34
+ const routerRoot = path.resolve('/project/app')
35
+
36
+ it('matches route file add and delete events', () => {
37
+ expect(
38
+ isRouteFileWatchEvent({
39
+ event: 'add',
40
+ filePath: '/project/app/index.tsx',
41
+ routerRoot,
42
+ })
43
+ ).toBe(true)
44
+ expect(
45
+ isRouteFileWatchEvent({
46
+ event: 'delete',
47
+ filePath: '/project/app/nested/page.jsx',
48
+ routerRoot,
49
+ })
50
+ ).toBe(true)
51
+ expect(
52
+ isRouteFileWatchEvent({
53
+ event: 'unlink',
54
+ filePath: '/project/app/nested/page.jsx',
55
+ routerRoot,
56
+ })
57
+ ).toBe(true)
58
+ })
59
+
60
+ it('ignores non-route file add and delete events', () => {
61
+ expect(
62
+ isRouteFileWatchEvent({
63
+ event: 'add',
64
+ filePath: '/project/app/tamagui.generated.css',
65
+ routerRoot,
66
+ })
67
+ ).toBe(false)
68
+ expect(
69
+ isRouteFileWatchEvent({
70
+ event: 'delete',
71
+ filePath: '/project/app/routes.d.ts',
72
+ routerRoot,
73
+ })
74
+ ).toBe(false)
75
+ })
76
+
77
+ it('only matches change events when requested', () => {
78
+ expect(
79
+ isRouteFileWatchEvent({
80
+ event: 'change',
81
+ filePath: '/project/app/index.tsx',
82
+ routerRoot,
83
+ })
84
+ ).toBe(false)
85
+ expect(
86
+ isRouteFileWatchEvent({
87
+ event: 'change',
88
+ filePath: '/project/app/index.tsx',
89
+ routerRoot,
90
+ includeChangeEvents: true,
91
+ })
92
+ ).toBe(true)
93
+ })
94
+ })
@@ -0,0 +1,40 @@
1
+ import path from 'node:path'
2
+
3
+ const routeFileExtensionRe = /\.[jt]sx?$/
4
+
5
+ export function isRouteFilePath(filePath: string) {
6
+ return routeFileExtensionRe.test(filePath) && !filePath.endsWith('.d.ts')
7
+ }
8
+
9
+ export function isPathInsideDirectory(filePath: string, directory: string) {
10
+ const relativePath = path.relative(path.resolve(directory), path.resolve(filePath))
11
+ return (
12
+ relativePath !== '' &&
13
+ !relativePath.startsWith('..') &&
14
+ !path.isAbsolute(relativePath)
15
+ )
16
+ }
17
+
18
+ export function isRouteFileWatchEvent({
19
+ event,
20
+ filePath,
21
+ routerRoot,
22
+ includeChangeEvents = false,
23
+ }: {
24
+ event: string
25
+ filePath: string
26
+ routerRoot: string
27
+ includeChangeEvents?: boolean
28
+ }) {
29
+ const isRouteFileEvent =
30
+ event === 'add' ||
31
+ event === 'delete' ||
32
+ event === 'unlink' ||
33
+ (includeChangeEvents && event === 'change')
34
+
35
+ return (
36
+ isRouteFileEvent &&
37
+ isPathInsideDirectory(filePath, routerRoot) &&
38
+ isRouteFilePath(filePath)
39
+ )
40
+ }
@@ -0,0 +1,106 @@
1
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
2
+ import { tmpdir } from 'node:os'
3
+ import path from 'node:path'
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
5
+
6
+ vi.mock('vite', async () => {
7
+ const actual = await vi.importActual<typeof import('vite')>('vite')
8
+ return {
9
+ ...actual,
10
+ createServerModuleRunner: vi.fn(() => ({
11
+ clearCache: vi.fn(),
12
+ import: vi.fn(),
13
+ })),
14
+ }
15
+ })
16
+
17
+ describe('createFileSystemRouterPlugin', () => {
18
+ const previousIsVxrnCli = process.env.IS_VXRN_CLI
19
+ let previousVxrnPluginConfig: unknown
20
+ let tempRoot: string | undefined
21
+
22
+ beforeEach(() => {
23
+ previousVxrnPluginConfig = (globalThis as any).__vxrnPluginConfig__
24
+ })
25
+
26
+ afterEach(() => {
27
+ if (tempRoot) {
28
+ rmSync(tempRoot, { recursive: true, force: true })
29
+ tempRoot = undefined
30
+ }
31
+
32
+ if (previousIsVxrnCli === undefined) {
33
+ delete process.env.IS_VXRN_CLI
34
+ } else {
35
+ process.env.IS_VXRN_CLI = previousIsVxrnCli
36
+ }
37
+
38
+ if (previousVxrnPluginConfig === undefined) {
39
+ delete (globalThis as any).__vxrnPluginConfig__
40
+ } else {
41
+ ;(globalThis as any).__vxrnPluginConfig__ = previousVxrnPluginConfig
42
+ }
43
+ vi.restoreAllMocks()
44
+ })
45
+
46
+ it('keeps route watcher rebuild errors handled', async () => {
47
+ process.env.IS_VXRN_CLI = '1'
48
+ tempRoot = mkdtempSync(path.join(tmpdir(), 'one-router-watch-'))
49
+ const appDir = path.join(tempRoot, 'app')
50
+ writeFileSync(path.join(tempRoot, 'package.json'), '{}\n')
51
+ mkdirSync(appDir)
52
+ writeFileSync(
53
+ path.join(appDir, 'index.tsx'),
54
+ 'export default function Index() { return null }\n'
55
+ )
56
+
57
+ ;(globalThis as any).__vxrnPluginConfig__ = {
58
+ web: {
59
+ defaultRenderMode: 'ssg',
60
+ },
61
+ }
62
+
63
+ const { createFileSystemRouterPlugin } = await import('./fileSystemRouterPlugin')
64
+ const plugin = createFileSystemRouterPlugin({
65
+ router: {
66
+ root: appDir,
67
+ },
68
+ })
69
+
70
+ let watcherListener:
71
+ | ((event: string, changedPath: string) => void | Promise<void>)
72
+ | undefined
73
+ const server = {
74
+ environments: {
75
+ ssr: {},
76
+ },
77
+ watcher: {
78
+ addListener: vi.fn((event: string, listener: typeof watcherListener) => {
79
+ if (event === 'all') {
80
+ watcherListener = listener
81
+ }
82
+ }),
83
+ on: vi.fn(),
84
+ },
85
+ }
86
+
87
+ ;(plugin as any).configureServer(server)
88
+ expect(watcherListener).toBeDefined()
89
+
90
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
91
+ delete (globalThis as any).__vxrnPluginConfig__
92
+
93
+ if (!watcherListener) {
94
+ throw new Error('Expected route watcher listener to be registered')
95
+ }
96
+
97
+ await expect(
98
+ Promise.resolve(watcherListener('add', path.join(appDir, 'new-route.tsx')))
99
+ ).resolves.toBeUndefined()
100
+
101
+ expect(warn).toHaveBeenCalledWith(
102
+ expect.stringContaining('[one] Failed to rebuild routes'),
103
+ expect.any(Error)
104
+ )
105
+ })
106
+ })
@@ -14,6 +14,7 @@ import { getRouterRootFromOneOptions } from '../../utils/getRouterRootFromOneOpt
14
14
  import { isResponse } from '../../utils/isResponse'
15
15
  import { isStatusRedirect } from '../../utils/isStatus'
16
16
  import { promiseWithResolvers } from '../../utils/promiseWithResolvers'
17
+ import { isRouteFileWatchEvent } from '../../utils/routeFileWatch'
17
18
  import { trackLoaderDependencies } from '../../utils/trackLoaderDependencies'
18
19
  import { LoaderDataCache } from '../../vite/constants'
19
20
  import { replaceLoader } from '../../vite/replaceLoader'
@@ -524,6 +525,14 @@ export function createFileSystemRouterPlugin(options: One.PluginOptions): Plugin
524
525
  )
525
526
  }
526
527
 
528
+ function recreateRequestHandler(changedPath: string) {
529
+ try {
530
+ handleRequest = createRequestHandler()
531
+ } catch (error) {
532
+ console.warn(`[one] Failed to rebuild routes after ${changedPath} changed.`, error)
533
+ }
534
+ }
535
+
527
536
  return {
528
537
  name: `one-router-fs`,
529
538
  enforce: 'post',
@@ -585,21 +594,20 @@ export function createFileSystemRouterPlugin(options: One.PluginOptions): Plugin
585
594
  USE_SERVER_ENV ? server.environments.server : server.environments.ssr
586
595
  )
587
596
 
588
- const appDir = path.join(process.cwd(), getRouterRootFromOneOptions(options))
597
+ const appDir = path.resolve(process.cwd(), getRouterRootFromOneOptions(options))
589
598
 
590
599
  // on change ./app stuff lets reload this to pick up any route changes
591
- const fileWatcherChangeListener = debounce(
592
- async (type: string, changedPath: string) => {
593
- if (type === 'add' || type === 'delete') {
594
- // resolve to absolute path since watcher may emit relative paths
595
- const absolutePath = path.resolve(changedPath)
596
- if (absolutePath.startsWith(appDir)) {
597
- handleRequest = createRequestHandler()
598
- }
599
- }
600
- },
601
- 100
602
- )
600
+ const fileWatcherChangeListener = debounce((type: string, changedPath: string) => {
601
+ if (
602
+ isRouteFileWatchEvent({
603
+ event: type,
604
+ filePath: changedPath,
605
+ routerRoot: appDir,
606
+ })
607
+ ) {
608
+ recreateRequestHandler(changedPath)
609
+ }
610
+ }, 100)
603
611
 
604
612
  server.watcher.addListener('all', fileWatcherChangeListener)
605
613
 
@@ -3,6 +3,7 @@ import { debounce } from 'perfect-debounce'
3
3
  import type { Plugin } from 'vite'
4
4
  import { generateRouteTypes } from '../../typed-routes/generateRouteTypes'
5
5
  import { getRouterRootFromOneOptions } from '../../utils/getRouterRootFromOneOptions'
6
+ import { isRouteFileWatchEvent } from '../../utils/routeFileWatch'
6
7
  import type { One } from '../types'
7
8
 
8
9
  export function generateFileSystemRouteTypesPlugin(options: One.PluginOptions): Plugin {
@@ -12,7 +13,7 @@ export function generateFileSystemRouteTypesPlugin(options: One.PluginOptions):
12
13
  apply: 'serve',
13
14
 
14
15
  configureServer(server) {
15
- const appDir = join(process.cwd(), getRouterRootFromOneOptions(options))
16
+ const appDir = resolve(process.cwd(), getRouterRootFromOneOptions(options))
16
17
  // Generate routes.d.ts inside the app directory to keep it organized
17
18
  const outFile = join(appDir, 'routes.d.ts')
18
19
 
@@ -22,22 +23,20 @@ export function generateFileSystemRouteTypesPlugin(options: One.PluginOptions):
22
23
 
23
24
  // on change ./app stuff lets reload this to pick up any route changes
24
25
  const fileWatcherChangeListener = debounce(async (type: string, path: string) => {
25
- if (type === 'add' || type === 'delete' || type === 'change') {
26
- // resolve to absolute path since watcher may emit relative paths
27
- const absolutePath = resolve(path)
28
- // skip routes.d.ts itself to avoid infinite loop
29
- if (absolutePath === outFile) {
30
- return
31
- }
32
- if (absolutePath.startsWith(appDir)) {
33
- // generate
34
- generateRouteTypes(
35
- outFile,
36
- routerRoot,
37
- options.router?.ignoredRouteFiles,
38
- typedRoutesGeneration
39
- )
40
- }
26
+ if (
27
+ isRouteFileWatchEvent({
28
+ event: type,
29
+ filePath: path,
30
+ routerRoot: appDir,
31
+ includeChangeEvents: true,
32
+ })
33
+ ) {
34
+ generateRouteTypes(
35
+ outFile,
36
+ routerRoot,
37
+ options.router?.ignoredRouteFiles,
38
+ typedRoutesGeneration
39
+ )
41
40
  }
42
41
  }, 100)
43
42
 
@@ -1 +1 @@
1
- {"version":3,"file":"generateRouteTypes.d.ts","sourceRoot":"","sources":["../../src/typed-routes/generateRouteTypes.ts"],"names":[],"mappings":"AAWA,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,iBAAiB,CAAC,EAAE,MAAM,EAAE,EAC5B,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,iBA8DrC"}
1
+ {"version":3,"file":"generateRouteTypes.d.ts","sourceRoot":"","sources":["../../src/typed-routes/generateRouteTypes.ts"],"names":[],"mappings":"AAWA,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,iBAAiB,CAAC,EAAE,MAAM,EAAE,EAC5B,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,iBAyErC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=generateRouteTypes.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generateRouteTypes.test.d.ts","sourceRoot":"","sources":["../../src/typed-routes/generateRouteTypes.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=getTypedRoutesDeclarationFile.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getTypedRoutesDeclarationFile.test.d.ts","sourceRoot":"","sources":["../../src/typed-routes/getTypedRoutesDeclarationFile.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,9 @@
1
+ export declare function isRouteFilePath(filePath: string): boolean;
2
+ export declare function isPathInsideDirectory(filePath: string, directory: string): boolean;
3
+ export declare function isRouteFileWatchEvent({ event, filePath, routerRoot, includeChangeEvents, }: {
4
+ event: string;
5
+ filePath: string;
6
+ routerRoot: string;
7
+ includeChangeEvents?: boolean;
8
+ }): boolean;
9
+ //# sourceMappingURL=routeFileWatch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routeFileWatch.d.ts","sourceRoot":"","sources":["../../src/utils/routeFileWatch.ts"],"names":[],"mappings":"AAIA,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,WAE/C;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,WAOxE;AAED,wBAAgB,qBAAqB,CAAC,EACpC,KAAK,EACL,QAAQ,EACR,UAAU,EACV,mBAA2B,GAC5B,EAAE;IACD,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,mBAAmB,CAAC,EAAE,OAAO,CAAA;CAC9B,WAYA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=routeFileWatch.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routeFileWatch.test.d.ts","sourceRoot":"","sources":["../../src/utils/routeFileWatch.test.ts"],"names":[],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"fileSystemRouterPlugin.d.ts","sourceRoot":"","sources":["../../../src/vite/plugins/fileSystemRouterPlugin.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAW,MAAM,EAAiB,MAAM,MAAM,CAAA;AAe1D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAA;AAiB3C,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,GAAG,CAAC,aAAa,GAAG,MAAM,CA8tB/E"}
1
+ {"version":3,"file":"fileSystemRouterPlugin.d.ts","sourceRoot":"","sources":["../../../src/vite/plugins/fileSystemRouterPlugin.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAW,MAAM,EAAiB,MAAM,MAAM,CAAA;AAgB1D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAA;AAiB3C,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,GAAG,CAAC,aAAa,GAAG,MAAM,CAquB/E"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=fileSystemRouterPlugin.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileSystemRouterPlugin.test.d.ts","sourceRoot":"","sources":["../../../src/vite/plugins/fileSystemRouterPlugin.test.ts"],"names":[],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"generateFileSystemRouteTypesPlugin.d.ts","sourceRoot":"","sources":["../../../src/vite/plugins/generateFileSystemRouteTypesPlugin.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAGlC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAEnC,wBAAgB,kCAAkC,CAAC,OAAO,EAAE,GAAG,CAAC,aAAa,GAAG,MAAM,CAkDrF"}
1
+ {"version":3,"file":"generateFileSystemRouteTypesPlugin.d.ts","sourceRoot":"","sources":["../../../src/vite/plugins/generateFileSystemRouteTypesPlugin.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAIlC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAEnC,wBAAgB,kCAAkC,CAAC,OAAO,EAAE,GAAG,CAAC,aAAa,GAAG,MAAM,CAgDrF"}