fix(scripts): quote Windows shell invocations
This commit is contained in:
parent
58a0eb603d
commit
636beb5e42
11 changed files with 99 additions and 57 deletions
|
|
@ -2,9 +2,10 @@
|
|||
|
||||
import path from 'node:path';
|
||||
import process from 'node:process';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { spawnWithWindowsShell } from './lib/windows-shell-spawn.mjs';
|
||||
|
||||
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const repoRoot = path.resolve(scriptDir, '..');
|
||||
const standalonePort = process.env.STANDALONE_PORT?.trim() || '3456';
|
||||
|
|
@ -13,22 +14,11 @@ const corsOrigin =
|
|||
process.env.CORS_ORIGIN?.trim() ||
|
||||
`http://127.0.0.1:${webPort},http://localhost:${webPort}`;
|
||||
|
||||
const WINDOWS_SHELL_COMMANDS = new Set(['pnpm', 'npm', 'npx', 'yarn', 'yarnpkg', 'corepack']);
|
||||
|
||||
function shouldUseWindowsShell(cmd) {
|
||||
if (process.platform !== 'win32') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return WINDOWS_SHELL_COMMANDS.has(path.basename(cmd).toLowerCase());
|
||||
}
|
||||
|
||||
function spawnProcess(cmd, args, env) {
|
||||
return spawn(cmd, args, {
|
||||
return spawnWithWindowsShell(cmd, args, {
|
||||
cwd: repoRoot,
|
||||
env: { ...process.env, ...env },
|
||||
stdio: 'inherit',
|
||||
shell: shouldUseWindowsShell(cmd),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,12 @@ import fs from 'node:fs';
|
|||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import process from 'node:process';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { once } from 'node:events';
|
||||
import readline from 'node:readline';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { spawnSyncWithWindowsShell } from './lib/windows-shell-spawn.mjs';
|
||||
|
||||
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const uiRepoRoot = path.resolve(scriptDir, '..');
|
||||
const runtimeRepoRoot = process.env.CLAUDE_DEV_RUNTIME_ROOT?.trim() ?? '';
|
||||
|
|
@ -22,26 +23,10 @@ const scriptArgs = process.argv.slice(2);
|
|||
const shouldPrintRuntimePath = scriptArgs.includes('--print-runtime-path');
|
||||
const electronViteArgs = scriptArgs.filter((arg) => arg !== '--print-runtime-path' && arg !== '--');
|
||||
const runtimeDisplayName = 'teams orchestrator';
|
||||
const WINDOWS_SHELL_COMMANDS = new Set(['pnpm', 'npm', 'npx', 'yarn', 'yarnpkg', 'corepack']);
|
||||
|
||||
function shouldUseWindowsShell(cmd) {
|
||||
if (process.platform !== 'win32') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const extension = path.extname(cmd).toLowerCase();
|
||||
if (extension === '.cmd' || extension === '.bat') {
|
||||
return true;
|
||||
}
|
||||
|
||||
const commandName = path.basename(cmd).toLowerCase();
|
||||
return WINDOWS_SHELL_COMMANDS.has(commandName);
|
||||
}
|
||||
|
||||
function runOrExit(cmd, args, options = {}) {
|
||||
const result = spawnSync(cmd, args, {
|
||||
const result = spawnSyncWithWindowsShell(cmd, args, {
|
||||
stdio: 'inherit',
|
||||
shell: shouldUseWindowsShell(cmd),
|
||||
...options,
|
||||
});
|
||||
|
||||
|
|
@ -56,9 +41,8 @@ function runOrExit(cmd, args, options = {}) {
|
|||
}
|
||||
|
||||
function runAndCapture(cmd, args, options = {}) {
|
||||
const result = spawnSync(cmd, args, {
|
||||
const result = spawnSyncWithWindowsShell(cmd, args, {
|
||||
encoding: 'utf8',
|
||||
shell: shouldUseWindowsShell(cmd),
|
||||
...options,
|
||||
});
|
||||
|
||||
|
|
|
|||
59
scripts/lib/windows-shell-spawn.mjs
Normal file
59
scripts/lib/windows-shell-spawn.mjs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import { spawn, spawnSync } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import process from 'node:process';
|
||||
|
||||
const WINDOWS_SHELL_COMMANDS = new Set(['pnpm', 'npm', 'npx', 'yarn', 'yarnpkg', 'corepack']);
|
||||
|
||||
export function quoteWindowsCmdArg(value) {
|
||||
const text = String(value);
|
||||
if (text.length === 0) {
|
||||
return '""';
|
||||
}
|
||||
if (!/[ \t\r\n"&|<>^()%!]/.test(text)) {
|
||||
return text;
|
||||
}
|
||||
return `"${text.replace(/%/g, '%%').replace(/(["^&|<>])/g, '^$1')}"`;
|
||||
}
|
||||
|
||||
export function shouldUseWindowsShell(command) {
|
||||
if (process.platform !== 'win32') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const extension = path.extname(command).toLowerCase();
|
||||
if (extension === '.cmd' || extension === '.bat') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return WINDOWS_SHELL_COMMANDS.has(path.basename(command).toLowerCase());
|
||||
}
|
||||
|
||||
function toWindowsShellCommand(command, args) {
|
||||
return [command, ...args].map(quoteWindowsCmdArg).join(' ');
|
||||
}
|
||||
|
||||
export function spawnWithWindowsShell(command, args, options = {}) {
|
||||
if (!shouldUseWindowsShell(command)) {
|
||||
return spawn(command, args, options);
|
||||
}
|
||||
|
||||
const safeOptions = { ...options };
|
||||
delete safeOptions.shell;
|
||||
return spawn(toWindowsShellCommand(command, args), {
|
||||
...safeOptions,
|
||||
shell: true,
|
||||
});
|
||||
}
|
||||
|
||||
export function spawnSyncWithWindowsShell(command, args, options = {}) {
|
||||
if (!shouldUseWindowsShell(command)) {
|
||||
return spawnSync(command, args, options);
|
||||
}
|
||||
|
||||
const safeOptions = { ...options };
|
||||
delete safeOptions.shell;
|
||||
return spawnSync(toWindowsShellCommand(command, args), {
|
||||
...safeOptions,
|
||||
shell: true,
|
||||
});
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import process from 'node:process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { resolveLiveSmokeOrchestratorCliPath } from './lib/live-smoke-runtime.mjs';
|
||||
import { spawnSyncWithWindowsShell } from './lib/windows-shell-spawn.mjs';
|
||||
|
||||
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const repoRoot = path.resolve(scriptDir, '..');
|
||||
|
|
@ -26,7 +26,7 @@ if (!env.CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH?.trim()) {
|
|||
console.log('Running agent CLI launch live smoke');
|
||||
console.log(`Claude runtime: ${env.CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH}`);
|
||||
|
||||
const result = spawnSync(
|
||||
const result = spawnSyncWithWindowsShell(
|
||||
'pnpm',
|
||||
[
|
||||
'exec',
|
||||
|
|
@ -42,7 +42,6 @@ const result = spawnSync(
|
|||
cwd: repoRoot,
|
||||
env,
|
||||
stdio: 'inherit',
|
||||
shell: process.platform === 'win32',
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import process from 'node:process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
|
@ -10,6 +9,7 @@ import {
|
|||
exitForSkippedPreflight,
|
||||
preflightOpenCodeLiveEnvironment,
|
||||
} from './lib/opencode-live-preflight.mjs';
|
||||
import { spawnSyncWithWindowsShell } from './lib/windows-shell-spawn.mjs';
|
||||
|
||||
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const repoRoot = path.resolve(scriptDir, '..');
|
||||
|
|
@ -40,7 +40,7 @@ console.log(`Multi-lane: ${env.OPENCODE_E2E_MIXED_RECOVERY_MULTI === '1' ? 'enab
|
|||
const preflight = await preflightOpenCodeLiveEnvironment({ repoRoot });
|
||||
exitForSkippedPreflight(preflight);
|
||||
|
||||
const result = spawnSync(
|
||||
const result = spawnSyncWithWindowsShell(
|
||||
'pnpm',
|
||||
[
|
||||
'exec',
|
||||
|
|
@ -56,7 +56,6 @@ const result = spawnSync(
|
|||
cwd: repoRoot,
|
||||
env,
|
||||
stdio: 'inherit',
|
||||
shell: process.platform === 'win32',
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import process from 'node:process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
|
@ -10,6 +9,7 @@ import {
|
|||
exitForSkippedPreflight,
|
||||
preflightOpenCodeLiveEnvironment,
|
||||
} from './lib/opencode-live-preflight.mjs';
|
||||
import { spawnSyncWithWindowsShell } from './lib/windows-shell-spawn.mjs';
|
||||
|
||||
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const repoRoot = path.resolve(scriptDir, '..');
|
||||
|
|
@ -46,7 +46,7 @@ console.log(`Orchestrator CLI: ${env.CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH}`)
|
|||
const preflight = await preflightOpenCodeLiveEnvironment({ repoRoot });
|
||||
exitForSkippedPreflight(preflight);
|
||||
|
||||
const result = spawnSync(
|
||||
const result = spawnSyncWithWindowsShell(
|
||||
'pnpm',
|
||||
[
|
||||
'exec',
|
||||
|
|
@ -62,7 +62,6 @@ const result = spawnSync(
|
|||
cwd: repoRoot,
|
||||
env,
|
||||
stdio: 'inherit',
|
||||
shell: process.platform === 'win32',
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import process from 'node:process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
|
@ -10,6 +9,7 @@ import {
|
|||
exitForSkippedPreflight,
|
||||
preflightOpenCodeLiveEnvironment,
|
||||
} from './lib/opencode-live-preflight.mjs';
|
||||
import { spawnSyncWithWindowsShell } from './lib/windows-shell-spawn.mjs';
|
||||
|
||||
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const repoRoot = path.resolve(scriptDir, '..');
|
||||
|
|
@ -38,7 +38,7 @@ console.log(`Orchestrator CLI: ${env.CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH}`)
|
|||
const preflight = await preflightOpenCodeLiveEnvironment({ repoRoot });
|
||||
exitForSkippedPreflight(preflight);
|
||||
|
||||
const result = spawnSync(
|
||||
const result = spawnSyncWithWindowsShell(
|
||||
'pnpm',
|
||||
[
|
||||
'exec',
|
||||
|
|
@ -54,7 +54,6 @@ const result = spawnSync(
|
|||
cwd: repoRoot,
|
||||
env,
|
||||
stdio: 'inherit',
|
||||
shell: process.platform === 'win32',
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import process from 'node:process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
|
@ -10,6 +9,7 @@ import {
|
|||
exitForSkippedPreflight,
|
||||
preflightOpenCodeLiveEnvironment,
|
||||
} from './lib/opencode-live-preflight.mjs';
|
||||
import { spawnSyncWithWindowsShell } from './lib/windows-shell-spawn.mjs';
|
||||
|
||||
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const repoRoot = path.resolve(scriptDir, '..');
|
||||
|
|
@ -36,7 +36,7 @@ console.log(`Orchestrator CLI: ${env.CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH}`)
|
|||
const preflight = await preflightOpenCodeLiveEnvironment({ repoRoot });
|
||||
exitForSkippedPreflight(preflight);
|
||||
|
||||
const result = spawnSync(
|
||||
const result = spawnSyncWithWindowsShell(
|
||||
'pnpm',
|
||||
[
|
||||
'exec',
|
||||
|
|
@ -52,7 +52,6 @@ const result = spawnSync(
|
|||
cwd: repoRoot,
|
||||
env,
|
||||
stdio: 'inherit',
|
||||
shell: process.platform === 'win32',
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import process from 'node:process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
|
@ -10,6 +9,7 @@ import {
|
|||
exitForSkippedPreflight,
|
||||
preflightOpenCodeLiveEnvironment,
|
||||
} from './lib/opencode-live-preflight.mjs';
|
||||
import { spawnSyncWithWindowsShell } from './lib/windows-shell-spawn.mjs';
|
||||
|
||||
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const repoRoot = path.resolve(scriptDir, '..');
|
||||
|
|
@ -38,7 +38,7 @@ console.log(`Orchestrator CLI: ${env.CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH}`)
|
|||
const preflight = await preflightOpenCodeLiveEnvironment({ repoRoot });
|
||||
exitForSkippedPreflight(preflight);
|
||||
|
||||
const result = spawnSync(
|
||||
const result = spawnSyncWithWindowsShell(
|
||||
'pnpm',
|
||||
[
|
||||
'exec',
|
||||
|
|
@ -54,7 +54,6 @@ const result = spawnSync(
|
|||
cwd: repoRoot,
|
||||
env,
|
||||
stdio: 'inherit',
|
||||
shell: process.platform === 'win32',
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { fileURLToPath } from 'node:url';
|
|||
|
||||
import { resolveLiveSmokeOrchestratorCliPath } from './lib/live-smoke-runtime.mjs';
|
||||
import { preflightOpenCodeLiveEnvironment } from './lib/opencode-live-preflight.mjs';
|
||||
import { spawnSyncWithWindowsShell } from './lib/windows-shell-spawn.mjs';
|
||||
|
||||
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const repoRoot = path.resolve(scriptDir, '..');
|
||||
|
|
@ -69,7 +70,7 @@ if (preflight.skipped.length > 0 && process.env.PROVIDER_LAUNCH_STRESS_STRICT ==
|
|||
env.PROVIDER_LAUNCH_STRESS_ORDER = preflight.order.join(',');
|
||||
console.log(`Runnable order: ${env.PROVIDER_LAUNCH_STRESS_ORDER}`);
|
||||
|
||||
const result = spawnSync(
|
||||
const result = spawnSyncWithWindowsShell(
|
||||
'pnpm',
|
||||
[
|
||||
'exec',
|
||||
|
|
@ -85,7 +86,6 @@ const result = spawnSync(
|
|||
cwd: repoRoot,
|
||||
env,
|
||||
stdio: 'inherit',
|
||||
shell: process.platform === 'win32',
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -312,10 +312,25 @@ function spawnRealCli(
|
|||
) {
|
||||
const spawnOptions = options ?? {};
|
||||
const needsWindowsCommandShell = process.platform === 'win32' && /\.(bat|cmd)$/i.test(command);
|
||||
return spawn(command, [...args], {
|
||||
...spawnOptions,
|
||||
...(needsWindowsCommandShell ? { shell: true } : {}),
|
||||
});
|
||||
if (needsWindowsCommandShell) {
|
||||
const commandLine = [command, ...args].map(quoteWindowsCmdArg).join(' ');
|
||||
return spawn(commandLine, {
|
||||
...spawnOptions,
|
||||
shell: true,
|
||||
});
|
||||
}
|
||||
|
||||
return spawn(command, [...args], spawnOptions);
|
||||
}
|
||||
|
||||
function quoteWindowsCmdArg(value: string) {
|
||||
if (value.length === 0) {
|
||||
return '""';
|
||||
}
|
||||
if (!/[ \t\r\n"&|<>^()%!]/.test(value)) {
|
||||
return value;
|
||||
}
|
||||
return `"${value.replace(/%/g, '%%').replace(/(["^&|<>])/g, '^$1')}"`;
|
||||
}
|
||||
|
||||
async function removeTempRoot(dirPath: string): Promise<void> {
|
||||
|
|
|
|||
Loading…
Reference in a new issue