Skip to content
Closed
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
22 changes: 10 additions & 12 deletions src/commands.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { basename, dirname, join, relative } from 'node:path'
import * as process from 'node:process'
import { promisify } from 'node:util'
import { spawn } from 'node:child_process'
import { createReadStream, existsSync, readdir as readdirC, statSync } from 'node:fs'
Expand All @@ -8,11 +7,12 @@ import { getStatsFromTree, processTraceFiles, showTree, treeIdNodes } from './tr
import { getTracePanel, prepareWebView } from './webview'
import { getCurrentConfig } from './configuration'
import { log } from './logger'
import { createTraceInvocation } from './traceInvocation'
import type { CommandId } from './constants'
import { addTraceFile, getWorkspacePath, openTerminal, openTraceDirectoryExternal, setLastMessageTrigger } from './storage'
import { addTraceDiagnostics, clearTaceDiagnostics } from './traceDiagnostics'
import { setStatusBarState } from './statusBar'
import { afterWatches, projectPath, saveName, state, traceFiles, traceRunning } from './appState'
import { afterWatches, saveName, state, traceFiles, traceRunning } from './appState'

const readdir = promisify(readdirC)

Expand Down Expand Up @@ -128,27 +128,25 @@ async function runTrace(args?: unknown[]) {
return
}

const quotedTraceDir = `'${traceDir}'`
// eslint-disable-next-line no-template-curly-in-string
const fullCmd = `(cd '${newDirName ?? workspacePath}'; ${traceCmd.replace('${traceDir}', quotedTraceDir)})`

log(fullCmd)

const newProjectPath = newDirName ?? projectPath.value
if (!newProjectPath) {
const invocation = createTraceInvocation({ traceCmd, traceDir, workspacePath, directory: newDirName })
if (!invocation) {
vscode.window.showErrorMessage('could not get project path from workspace folders')
return
}

const { command: fullCmd, cwd, shell } = invocation
log(fullCmd)

traceRunning.value = true

traceFiles.value = {}
treeIdNodes.clear()

setStatusBarState('traceError', false)

log(`shell: ${process.env.SHELL}`)
const cmdProcess = spawn(fullCmd, [], { cwd: newProjectPath, shell: process.env.SHELL })
log(`cwd: ${cwd}`)
log(`shell: ${shell === true ? 'default' : shell}`)
const cmdProcess = spawn(fullCmd, [], { cwd, shell })

let err = ''
cmdProcess.stderr.on('data', data => err += data.toString())
Expand Down
31 changes: 31 additions & 0 deletions src/traceInvocation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import * as process from 'node:process'

interface TraceInvocationOptions {
traceCmd: string
traceDir: string
workspacePath: string
directory?: string
}

export function createTraceInvocation(
{ traceCmd, traceDir, workspacePath, directory }: TraceInvocationOptions,
platform: NodeJS.Platform = process.platform,
shell = process.env.SHELL,
) {
// projectPath is extension storage; the compiler must run in the source tree.
const cwd = directory ?? workspacePath
if (!cwd)
return

const quotedTraceDir = platform === 'win32'
? `"${traceDir}"`
: `'${traceDir.replace(/'/g, `'\\''`)}'`

return {
// A replacement callback preserves literal dollar sequences in path names.
// eslint-disable-next-line no-template-curly-in-string
command: traceCmd.replace('${traceDir}', () => quotedTraceDir),
cwd,
shell: platform === 'win32' ? true : shell || true,
}
}
47 changes: 47 additions & 0 deletions test/traceInvocation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { strict as assert } from 'node:assert'
import { describe, it } from 'vitest'
import { createTraceInvocation } from '../src/traceInvocation'

// eslint-disable-next-line no-template-curly-in-string
const traceCmd = 'npx tsc --noEmit --generateTrace ${traceDir}'

const windowsOptions = {
traceCmd,
workspacePath: 'P:\\My projects\\app',
traceDir: 'C:\\Users\\Name With Space\\globalStorage\\app\\default\\traces',
}

describe('trace invocation', () => {
it('runs command-palette tracing in the workspace on another drive', () => {
assert.deepEqual(createTraceInvocation(windowsOptions, 'win32', ''), {
command: `npx tsc --noEmit --generateTrace "${windowsOptions.traceDir}"`,
cwd: windowsOptions.workspacePath,
shell: true,
})
})

it('runs explorer tracing in the selected directory without shell cd', () => {
const directory = 'D:\\Other projects\\it\'s here'
const invocation = createTraceInvocation({ ...windowsOptions, directory }, 'win32', '/bin/bash')
assert.equal(invocation?.cwd, directory)
assert.equal(invocation?.shell, true)
assert.equal(invocation?.command, `npx tsc --noEmit --generateTrace "${windowsOptions.traceDir}"`)
})

it('preserves POSIX quoting, literal dollar sequences, and the configured shell', () => {
assert.deepEqual(createTraceInvocation({
traceCmd,
workspacePath: '/work/my project',
traceDir: '/tmp/it\'s $& data/traces',
}, 'linux', '/bin/sh'), {
command: 'npx tsc --noEmit --generateTrace \'/tmp/it\'\\\'\'s $& data/traces\'',
cwd: '/work/my project',
shell: '/bin/sh',
})
})

it('does not create an invocation without a working directory', () => {
assert.equal(createTraceInvocation({ ...windowsOptions, workspacePath: '' }), undefined)
assert.equal(createTraceInvocation(windowsOptions, 'linux', '')?.shell, true)
})
})
Loading