From add7c5ce1e7d6ac5eec01eaf2dffd64cc012cce6 Mon Sep 17 00:00:00 2001 From: open-fox Date: Wed, 8 Jul 2026 21:27:09 +0800 Subject: [PATCH] fix: run commands through the shell on Windows - Resolve `.cmd` shims from PATH by enabling shell execution on win32 - Replace execFileSync version checks with the shared spawn wrapper - Handle string stdout in command error formatting --- src/commands.ts | 38 ++++++++++++++++++++++++++++++-------- src/utils.ts | 3 ++- test/index.test.ts | 8 +++++++- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/commands.ts b/src/commands.ts index 88b4c3e..99fb6a3 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -1,4 +1,5 @@ -import { execFileSync, spawnSync, type SpawnSyncReturns } from 'node:child_process'; +import { spawnSync, type SpawnSyncReturns } from 'node:child_process'; +import type { SpawnSyncOptions } from 'node:child_process'; import os from 'node:os'; import process from 'node:process'; @@ -79,7 +80,7 @@ export function runCommandAndEcho( export function runInheritedRaw(command: string, args: string[], cwd: string): void { printCommand(command, args); - const result = spawnSync(command, args, { cwd, stdio: 'inherit' }); + const result = spawnCommand(command, args, { cwd, stdio: 'inherit' }); if (result.status !== 0) { throw commandError(command, args, result); } @@ -87,7 +88,7 @@ export function runInheritedRaw(command: string, args: string[], cwd: string): v export function runQuiet(command: string, args: string[], cwd: string): void { printCommand(command, args); - const result = spawnSync(command, args, { cwd, stdio: 'ignore' }); + const result = spawnCommand(command, args, { cwd, stdio: 'ignore' }); if (result.status !== 0) { throw new Error(`Command failed: ${[command, ...args].join(' ')}`); } @@ -107,14 +108,20 @@ function quoteArg(value: string): string { } export function commandExists(command: string): boolean { - return spawnSync(command, ['--version'], { stdio: 'ignore' }).status === 0; + return spawnCommand(command, ['--version'], { stdio: 'ignore' }).status === 0; } export function execVersion(command: string): string { - return execFileSync(command, ['--version'], { + const result = spawnCommand(command, ['--version'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], - }).trim(); + }); + + if (result.status !== 0 || typeof result.stdout !== 'string') { + throw commandError(command, ['--version'], result); + } + + return result.stdout.trim(); } function spawnWithConfig( @@ -124,7 +131,7 @@ function spawnWithConfig( config: RuntimeConfig, stdio: 'pipe' | 'inherit' | ['ignore', 'inherit', 'inherit'] ): SpawnSyncReturns { - return spawnSync(command, args, { + return spawnCommand(command, args, { cwd, env: { ...process.env, @@ -134,13 +141,28 @@ function spawnWithConfig( }, stdio, maxBuffer: 64 * 1024 * 1024, + }) as SpawnSyncReturns; +} + +export function shellForPlatform(platform: NodeJS.Platform): boolean { + return platform === 'win32'; +} + +function spawnCommand( + command: string, + args: string[], + options: SpawnSyncOptions +): SpawnSyncReturns { + return spawnSync(command, args, { + ...options, + shell: shellForPlatform(process.platform), }); } function commandError( command: string, args: string[], - result: SpawnSyncReturns + result: SpawnSyncReturns ): Error { const stdout = bufferToString(result.stdout).trim(); const stderr = bufferToString(result.stderr).trim(); diff --git a/src/utils.ts b/src/utils.ts index 5b69c1f..c0f37a6 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -30,6 +30,7 @@ export function maskSecret(value: string): string { return `${value.slice(0, 4)}...${value.slice(-4)}`; } -export function bufferToString(value: Buffer | null): string { +export function bufferToString(value: Buffer | string | null): string { + if (typeof value === 'string') return value; return value ? value.toString('utf8') : ''; } diff --git a/test/index.test.ts b/test/index.test.ts index 79bd0a6..7bf4e1d 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -12,7 +12,7 @@ import { parseD1DatabaseId, parseKVNamespaceId, } from '../src/cloudflare.ts'; -import { runCommand } from '../src/commands.ts'; +import { runCommand, shellForPlatform } from '../src/commands.ts'; import { ensureEnvFiles, formatEnvValue } from '../src/env.ts'; import { isCliEntrypoint } from '../src/index.ts'; import { getInstallPlan } from '../src/preflight.ts'; @@ -301,6 +301,12 @@ describe('wrangler config writing', () => { }); describe('command runner', () => { + it('uses the shell on Windows so .cmd shims can be resolved from PATH', () => { + expect(shellForPlatform('win32')).toBe(true); + expect(shellForPlatform('darwin')).toBe(false); + expect(shellForPlatform('linux')).toBe(false); + }); + it('prints the command and injects Cloudflare environment variables', () => { const config = createTestConfig(); const log = vi.spyOn(console, 'log').mockImplementation(() => undefined);