|
| 1 | +import { mkdtempSync, rmSync, writeFileSync } from 'node:fs' |
| 2 | +import { tmpdir } from 'node:os' |
| 3 | +import { join } from 'node:path' |
| 4 | +import { afterAll, describe, expect, it } from 'vitest' |
| 5 | +import { resolvePtyCommand } from '../pty-command' |
| 6 | + |
| 7 | +const bin = mkdtempSync(join(tmpdir(), 'devframe-pty-command-')) |
| 8 | +writeFileSync(join(bin, 'pnpm.cmd'), '') |
| 9 | +writeFileSync(join(bin, 'legacy.bat'), '') |
| 10 | +writeFileSync(join(bin, 'node.exe'), '') |
| 11 | + |
| 12 | +afterAll(() => rmSync(bin, { recursive: true, force: true })) |
| 13 | + |
| 14 | +const win32 = { cwd: tmpdir(), platform: 'win32' as const, env: { Path: bin, ComSpec: 'C:\\Windows\\system32\\cmd.exe' } } |
| 15 | + |
| 16 | +describe('resolvePtyCommand', () => { |
| 17 | + it('runs .cmd shims through ComSpec on win32', () => { |
| 18 | + expect(resolvePtyCommand('pnpm', ['exec', 'storybook', 'dev', '-p', '6006'], win32)).toEqual({ |
| 19 | + command: 'C:\\Windows\\system32\\cmd.exe', |
| 20 | + args: ['/d', '/c', 'pnpm', 'exec', 'storybook', 'dev', '-p', '6006'], |
| 21 | + }) |
| 22 | + }) |
| 23 | + |
| 24 | + it('runs .bat files through ComSpec on win32', () => { |
| 25 | + expect(resolvePtyCommand('legacy', [], win32).command).toBe('C:\\Windows\\system32\\cmd.exe') |
| 26 | + }) |
| 27 | + |
| 28 | + it('falls back to cmd.exe without ComSpec', () => { |
| 29 | + expect(resolvePtyCommand('pnpm', [], { ...win32, env: { PATH: bin } }).command).toBe('cmd.exe') |
| 30 | + }) |
| 31 | + |
| 32 | + it('runs unresolved commands through cmd.exe so a typo fails like a shell would', () => { |
| 33 | + expect(resolvePtyCommand('does-not-exist', ['x'], win32).args).toEqual(['/d', '/c', 'does-not-exist', 'x']) |
| 34 | + }) |
| 35 | + |
| 36 | + it('spawns real executables directly on win32', () => { |
| 37 | + expect(resolvePtyCommand('node', ['-v'], win32)).toEqual({ command: 'node', args: ['-v'] }) |
| 38 | + expect(resolvePtyCommand(join(bin, 'node.exe'), ['-v'], win32)).toEqual({ command: join(bin, 'node.exe'), args: ['-v'] }) |
| 39 | + }) |
| 40 | + |
| 41 | + it('does not wrap cmd.exe itself', () => { |
| 42 | + expect(resolvePtyCommand('cmd.exe', ['/c', 'dir'], win32)).toEqual({ command: 'cmd.exe', args: ['/c', 'dir'] }) |
| 43 | + expect(resolvePtyCommand('C:\\Windows\\system32\\cmd.exe', [], win32).args).toEqual([]) |
| 44 | + }) |
| 45 | + |
| 46 | + it('escapes cmd metacharacters in bare arguments, twice for batch shims', () => { |
| 47 | + expect(resolvePtyCommand('does-not-exist', ['a&b', 'x(y)'], win32).args).toEqual(['/d', '/c', 'does-not-exist', 'a^&b', 'x^(y^)']) |
| 48 | + expect(resolvePtyCommand('pnpm', ['a&b', '50%'], win32).args).toEqual(['/d', '/c', 'pnpm', 'a^^^&b', '50^^^%']) |
| 49 | + }) |
| 50 | + |
| 51 | + it('leaves arguments zigpty quotes untouched', () => { |
| 52 | + expect(resolvePtyCommand('pnpm', ['a b&c', '', 'say "hi"', 'a\tb&c'], win32).args).toEqual(['/d', '/c', 'pnpm', 'a b&c', '', 'say "hi"', 'a\tb&c']) |
| 53 | + }) |
| 54 | + |
| 55 | + it('leaves arguments with backslashes untouched, since zigpty quotes them', () => { |
| 56 | + expect(resolvePtyCommand('pnpm', ['src\\**\\*.ts', 'dir\\file(1).txt'], win32).args).toEqual(['/d', '/c', 'pnpm', 'src\\**\\*.ts', 'dir\\file(1).txt']) |
| 57 | + expect(resolvePtyCommand('C:\\a(1)\\x.cmd', [], win32).args).toEqual(['/d', '/c', 'C:\\a(1)\\x.cmd']) |
| 58 | + }) |
| 59 | + |
| 60 | + it('skips the cwd when NoDefaultCurrentDirectoryInExePath is set', () => { |
| 61 | + const env = { NoDefaultCurrentDirectoryInExePath: '1', ComSpec: 'cmd.exe' } |
| 62 | + expect(resolvePtyCommand('node', [], { ...win32, cwd: bin, env }).command).toBe('cmd.exe') |
| 63 | + expect(resolvePtyCommand('node', [], { ...win32, cwd: bin, env: { ComSpec: 'cmd.exe' } }).command).toBe('node') |
| 64 | + }) |
| 65 | + |
| 66 | + it('leaves the command unchanged on other platforms', () => { |
| 67 | + for (const platform of ['linux', 'darwin'] as const) |
| 68 | + expect(resolvePtyCommand('pnpm', ['a&b'], { ...win32, platform })).toEqual({ command: 'pnpm', args: ['a&b'] }) |
| 69 | + }) |
| 70 | +}) |
0 commit comments