Skip to content

Commit 8eb568e

Browse files
committed
Fix source inspector editor path resolution
1 parent c4bcc6f commit 8eb568e

3 files changed

Lines changed: 71 additions & 3 deletions

File tree

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { describe, expect, it } from 'vitest'
2+
3+
import { getSourceInspectorPath, resolveEditorFilePath } from './sourceInspectorPlugin'
4+
5+
describe('sourceInspectorPlugin helpers', () => {
6+
describe('getSourceInspectorPath', () => {
7+
it('keeps project files relative to the current cwd', () => {
8+
expect(getSourceInspectorPath('/repo/packages/one/src/App.tsx', '/repo')).toBe(
9+
'/packages/one/src/App.tsx'
10+
)
11+
})
12+
13+
it('does not strip matching path prefixes from files outside cwd', () => {
14+
expect(
15+
getSourceInspectorPath('/repo-other/packages/one/src/App.tsx', '/repo')
16+
).toBe('/repo-other/packages/one/src/App.tsx')
17+
})
18+
})
19+
20+
describe('resolveEditorFilePath', () => {
21+
it('resolves project-relative source paths against cwd', () => {
22+
const fileExists = (filePath: string) =>
23+
filePath === '/repo/packages/one/src/App.tsx'
24+
25+
expect(
26+
resolveEditorFilePath('/packages/one/src/App.tsx', '/repo', fileExists)
27+
).toBe('/repo/packages/one/src/App.tsx')
28+
})
29+
30+
it('preserves absolute source paths for files outside cwd', () => {
31+
expect(
32+
resolveEditorFilePath(
33+
'/Users/n8/shared/ui/Button.tsx',
34+
'/repo/apps/site',
35+
() => false
36+
)
37+
).toBe('/Users/n8/shared/ui/Button.tsx')
38+
})
39+
})
40+
})

packages/one/src/vite/plugins/sourceInspectorPlugin.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { spawn } from 'node:child_process'
2+
import { existsSync } from 'node:fs'
23
import path from 'node:path'
34
import { parse } from 'oxc-parser'
45
import type { Plugin, ViteDevServer } from 'vite'
@@ -91,14 +92,41 @@ async function findJsxElements(code: string, filename: string): Promise<JsxLocat
9192

9293
type TransformOut = { code: string; map?: null } | undefined
9394

95+
export function getSourceInspectorPath(filePath: string, cwd = process.cwd()): string {
96+
const normalizedFilePath = normalizePath(filePath)
97+
const normalizedCwd = normalizePath(cwd)
98+
99+
if (normalizedFilePath === normalizedCwd) {
100+
return '/'
101+
}
102+
103+
if (normalizedFilePath.startsWith(`${normalizedCwd}/`)) {
104+
return normalizedFilePath.slice(normalizedCwd.length)
105+
}
106+
107+
return normalizedFilePath
108+
}
109+
110+
export function resolveEditorFilePath(
111+
filePath: string,
112+
cwd = process.cwd(),
113+
fileExists: (path: string) => boolean = existsSync
114+
): string {
115+
const projectPath = path.join(cwd, filePath)
116+
117+
// data-one-source uses "/foo.tsx" for project-relative paths, but files outside
118+
// cwd are stored as absolute paths and must not be re-joined onto cwd.
119+
return fileExists(projectPath) ? projectPath : filePath
120+
}
121+
94122
/**
95123
* Transforms JSX to inject data-one-source attributes using oxc-parser.
96124
*/
97125
async function injectSourceToJsx(code: string, id: string): Promise<TransformOut> {
98126
const [filePath] = id.split('?')
99127
if (!filePath) return
100128

101-
const location = filePath.replace(normalizePath(process.cwd()), '')
129+
const location = getSourceInspectorPath(filePath)
102130

103131
// Quick check - skip if no JSX-like content
104132
if (!code.includes('<') || !code.includes('>')) {
@@ -159,7 +187,7 @@ function openInEditor(
159187
return
160188
}
161189

162-
const fullPath = path.join(process.cwd(), filePath)
190+
const fullPath = resolveEditorFilePath(filePath)
163191
const l = line || '1'
164192
const c = column || '1'
165193
const buildArgs = editorArgs[resolved]

tests/test-skew-protection/app/crash+spa.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export default function Crash() {
1919
if (type === 'chunk') {
2020
// matches the chrome chunk-load message in CHUNK_ERROR_PATTERNS
2121
throw new Error(
22-
"Failed to fetch dynamically imported module: https://example.com/assets/foo-abc123.js"
22+
'Failed to fetch dynamically imported module: https://example.com/assets/foo-abc123.js'
2323
)
2424
}
2525
if (type === 'other') {

0 commit comments

Comments
 (0)