Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/open-source-outside-cwd.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@tanstack/devtools-bundler-core': patch
'@tanstack/devtools-vite': patch
'@tanstack/devtools-rspack': patch
---

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.
30 changes: 22 additions & 8 deletions packages/devtools-bundler-core/src/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { EventEmitter } from 'node:events'
import { resolve } from 'node:path'
import { beforeEach, describe, expect, it, test, vi } from 'vitest'
import {
handleDevToolsRequest,
Expand Down Expand Up @@ -200,6 +201,7 @@ function createMockReq(url?: string) {

function createMockRes() {
return {
statusCode: 200,
setHeader: vi.fn(),
write: vi.fn(),
end: vi.fn(),
Expand Down Expand Up @@ -261,30 +263,42 @@ describe('handleDevToolsRequest', () => {
expect(next).not.toHaveBeenCalled()
})

it('does nothing for __tsd/open-source when source is missing', () => {
it('keeps the absolute path of a source file outside cwd', () => {
// A monorepo package, or Vite run from another directory: the injected
// source is not under cwd, so it keeps its absolute path.
const file = normalizePath(resolve(process.cwd(), '../../package.json'))
const req = createMockReq(
`/__tsd/open-source?source=${encodeURIComponent(`${file}:1:1`)}`,
)
const res = createMockRes()

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

expect(cb.mock.calls[0]?.[0].data.source).toBe(file)
})

it('answers 400 for __tsd/open-source when source is missing', () => {
const req = createMockReq('/__tsd/open-source')
const res = createMockRes()

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

expect(cb).not.toHaveBeenCalled()
expect(res.setHeader).not.toHaveBeenCalled()
expect(res.write).not.toHaveBeenCalled()
expect(res.end).not.toHaveBeenCalled()
expect(res.statusCode).toBe(400)
expect(res.end).toHaveBeenCalled()
expect(next).not.toHaveBeenCalled()
})

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

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

expect(cb).not.toHaveBeenCalled()
expect(res.setHeader).not.toHaveBeenCalled()
expect(res.write).not.toHaveBeenCalled()
expect(res.end).not.toHaveBeenCalled()
expect(res.statusCode).toBe(400)
expect(res.end).toHaveBeenCalled()
expect(next).not.toHaveBeenCalled()
})

Expand Down
23 changes: 16 additions & 7 deletions packages/devtools-bundler-core/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { existsSync } from 'node:fs'
import fs from 'node:fs/promises'
import { normalizePath } from './normalize-path'
import type { IncomingMessage, ServerResponse } from 'node:http'
Expand Down Expand Up @@ -33,13 +34,10 @@ export const handleDevToolsRequest = (
if (req.url?.includes('__tsd/open-source')) {
const searchParams = new URLSearchParams(req.url.split('?')[1])

const source = searchParams.get('source')
if (!source) {
return
}

const parsed = parseOpenSourceParam(source)
const parsed = parseOpenSourceParam(searchParams.get('source') ?? '')
if (!parsed) {
res.statusCode = 400
res.end()
return
}
const { file, line, column } = parsed
Expand All @@ -48,7 +46,7 @@ export const handleDevToolsRequest = (
type: 'open-source',
routine: 'open-source',
data: {
source: file ? normalizePath(`${process.cwd()}/${file}`) : undefined,
source: file ? resolveSourceFile(file) : undefined,
line,
column,
},
Expand Down Expand Up @@ -133,6 +131,17 @@ export const handleDevToolsRequest = (
})
}

/**
* Injected sources are relative to cwd. A file outside cwd (a monorepo package,
* or Vite run from another directory) keeps its absolute path instead.
*/
const resolveSourceFile = (file: string) => {
const fromCwd = normalizePath(`${process.cwd()}/${file}`)
return existsSync(fromCwd) || !existsSync(file)
? fromCwd
: normalizePath(file)
}

export const parseOpenSourceParam = (source: string) => {
// Capture everything up to the last two colon-separated numeric parts as the file.
// This supports filenames that may themselves contain colons.
Expand Down
Loading