Skip to content

Commit df1464c

Browse files
authored
fix(devtools-bundler-core): open source files outside cwd and end bad open-source requests (#541)
addSourceToJsx strips cwd from the module path. A file outside cwd (a monorepo package, or Vite run from another directory) keeps its absolute path, and the open-source handler then prefixed cwd again. The editor got paths like /repo/apps/web/repo/packages/pkg/src/panel.tsx. The handler now uses the absolute path when the cwd-relative file does not exist and the absolute one does. A missing or malformed `source` returned without ending the response, so the request stayed pending until the browser timed out. It now answers 400. Fixes #281 Fixes #176 Refs #451
1 parent 012421f commit df1464c

3 files changed

Lines changed: 45 additions & 15 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@tanstack/devtools-bundler-core': patch
3+
'@tanstack/devtools-vite': patch
4+
'@tanstack/devtools-rspack': patch
5+
---
6+
7+
Fix "Go to Source" for files outside the current working directory, for example a package in a monorepo or Vite run from another directory. The editor got a path with the working directory added twice. Also answer a missing or malformed `source` with `400` instead of leaving the request pending.

‎packages/devtools-bundler-core/src/utils.test.ts‎

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { EventEmitter } from 'node:events'
2+
import { resolve } from 'node:path'
23
import { beforeEach, describe, expect, it, test, vi } from 'vitest'
34
import {
45
handleDevToolsRequest,
@@ -200,6 +201,7 @@ function createMockReq(url?: string) {
200201

201202
function createMockRes() {
202203
return {
204+
statusCode: 200,
203205
setHeader: vi.fn(),
204206
write: vi.fn(),
205207
end: vi.fn(),
@@ -261,30 +263,42 @@ describe('handleDevToolsRequest', () => {
261263
expect(next).not.toHaveBeenCalled()
262264
})
263265

264-
it('does nothing for __tsd/open-source when source is missing', () => {
266+
it('keeps the absolute path of a source file outside cwd', () => {
267+
// A monorepo package, or Vite run from another directory: the injected
268+
// source is not under cwd, so it keeps its absolute path.
269+
const file = normalizePath(resolve(process.cwd(), '../../package.json'))
270+
const req = createMockReq(
271+
`/__tsd/open-source?source=${encodeURIComponent(`${file}:1:1`)}`,
272+
)
273+
const res = createMockRes()
274+
275+
handleDevToolsRequest(req, res as any, next as any, cb as any)
276+
277+
expect(cb.mock.calls[0]?.[0].data.source).toBe(file)
278+
})
279+
280+
it('answers 400 for __tsd/open-source when source is missing', () => {
265281
const req = createMockReq('/__tsd/open-source')
266282
const res = createMockRes()
267283

268284
handleDevToolsRequest(req, res as any, next as any, cb as any)
269285

270286
expect(cb).not.toHaveBeenCalled()
271-
expect(res.setHeader).not.toHaveBeenCalled()
272-
expect(res.write).not.toHaveBeenCalled()
273-
expect(res.end).not.toHaveBeenCalled()
287+
expect(res.statusCode).toBe(400)
288+
expect(res.end).toHaveBeenCalled()
274289
expect(next).not.toHaveBeenCalled()
275290
})
276291

277-
it('does nothing for __tsd/open-source when source is malformed', () => {
292+
it('answers 400 for __tsd/open-source when source is malformed', () => {
278293
const malformed = encodeURIComponent('src/file.ts:abc:def')
279294
const req = createMockReq(`/__tsd/open-source?source=${malformed}`)
280295
const res = createMockRes()
281296

282297
handleDevToolsRequest(req, res as any, next as any, cb as any)
283298

284299
expect(cb).not.toHaveBeenCalled()
285-
expect(res.setHeader).not.toHaveBeenCalled()
286-
expect(res.write).not.toHaveBeenCalled()
287-
expect(res.end).not.toHaveBeenCalled()
300+
expect(res.statusCode).toBe(400)
301+
expect(res.end).toHaveBeenCalled()
288302
expect(next).not.toHaveBeenCalled()
289303
})
290304

‎packages/devtools-bundler-core/src/utils.ts‎

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { existsSync } from 'node:fs'
12
import fs from 'node:fs/promises'
23
import { normalizePath } from './normalize-path'
34
import type { IncomingMessage, ServerResponse } from 'node:http'
@@ -33,13 +34,10 @@ export const handleDevToolsRequest = (
3334
if (req.url?.includes('__tsd/open-source')) {
3435
const searchParams = new URLSearchParams(req.url.split('?')[1])
3536

36-
const source = searchParams.get('source')
37-
if (!source) {
38-
return
39-
}
40-
41-
const parsed = parseOpenSourceParam(source)
37+
const parsed = parseOpenSourceParam(searchParams.get('source') ?? '')
4238
if (!parsed) {
39+
res.statusCode = 400
40+
res.end()
4341
return
4442
}
4543
const { file, line, column } = parsed
@@ -48,7 +46,7 @@ export const handleDevToolsRequest = (
4846
type: 'open-source',
4947
routine: 'open-source',
5048
data: {
51-
source: file ? normalizePath(`${process.cwd()}/${file}`) : undefined,
49+
source: file ? resolveSourceFile(file) : undefined,
5250
line,
5351
column,
5452
},
@@ -133,6 +131,17 @@ export const handleDevToolsRequest = (
133131
})
134132
}
135133

134+
/**
135+
* Injected sources are relative to cwd. A file outside cwd (a monorepo package,
136+
* or Vite run from another directory) keeps its absolute path instead.
137+
*/
138+
const resolveSourceFile = (file: string) => {
139+
const fromCwd = normalizePath(`${process.cwd()}/${file}`)
140+
return existsSync(fromCwd) || !existsSync(file)
141+
? fromCwd
142+
: normalizePath(file)
143+
}
144+
136145
export const parseOpenSourceParam = (source: string) => {
137146
// Capture everything up to the last two colon-separated numeric parts as the file.
138147
// This supports filenames that may themselves contain colons.

0 commit comments

Comments
 (0)