Skip to content

Commit 3967b9d

Browse files
committed
fix(ci): harden agent workspace verify npm invocation on windows
1 parent 909f2e5 commit 3967b9d

3 files changed

Lines changed: 159 additions & 22 deletions

File tree

scripts/verify-agent-workspace-browser.js

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,63 @@ function readText(filePath) {
1212
return fs.readFileSync(filePath, 'utf8');
1313
}
1414

15+
function formatNpmDisplay(npmArgs) {
16+
return `npm ${npmArgs.join(' ')}`;
17+
}
18+
19+
function quoteForCmd(arg) {
20+
if (!/[\s"&^|<>]/.test(arg)) {
21+
return arg;
22+
}
23+
return `"${arg.replace(/"/g, '\\"')}"`;
24+
}
25+
26+
function resolveNpmInvocation(npmArgs) {
27+
const npmExecPath = process.env.npm_execpath;
28+
if (npmExecPath && fs.existsSync(npmExecPath)) {
29+
return {
30+
command: process.execPath,
31+
args: [npmExecPath, ...npmArgs],
32+
display: formatNpmDisplay(npmArgs),
33+
};
34+
}
35+
if (process.platform === 'win32') {
36+
return {
37+
command: process.env.ComSpec || 'cmd.exe',
38+
args: ['/d', '/s', '/c', `npm ${npmArgs.map(quoteForCmd).join(' ')}`],
39+
display: formatNpmDisplay(npmArgs),
40+
};
41+
}
42+
return {
43+
command: 'npm',
44+
args: npmArgs,
45+
display: formatNpmDisplay(npmArgs),
46+
};
47+
}
48+
1549
function runNpm(repoRoot, npmArgs) {
16-
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
17-
const result = spawnSync(npmCommand, npmArgs, {
50+
const invocation = resolveNpmInvocation(npmArgs);
51+
const result = spawnSync(invocation.command, invocation.args, {
1852
cwd: repoRoot,
1953
stdio: 'inherit',
2054
env: process.env,
55+
windowsHide: true,
2156
});
22-
if (result.status !== 0) {
23-
throw new Error(`Command failed: ${npmCommand} ${npmArgs.join(' ')}`);
57+
if (result.error || result.status !== 0) {
58+
const details = [];
59+
if (result.status !== null && result.status !== undefined) {
60+
details.push(`status=${result.status}`);
61+
}
62+
if (result.signal) {
63+
details.push(`signal=${result.signal}`);
64+
}
65+
if (result.error) {
66+
details.push(`error=${result.error.message}`);
67+
if (result.error.code) {
68+
details.push(`error_code=${result.error.code}`);
69+
}
70+
}
71+
throw new Error(`Command failed: ${invocation.display}${details.length ? ` (${details.join(', ')})` : ''}`);
2472
}
2573
}
2674

@@ -79,4 +127,3 @@ if (require.main === module) {
79127
module.exports = {
80128
verifyAgentWorkspaceBrowser,
81129
};
82-

scripts/verify-agent-workspace-runtime.js

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,63 @@ function readText(filePath) {
1212
return fs.readFileSync(filePath, 'utf8');
1313
}
1414

15+
function formatNpmDisplay(npmArgs) {
16+
return `npm ${npmArgs.join(' ')}`;
17+
}
18+
19+
function quoteForCmd(arg) {
20+
if (!/[\s"&^|<>]/.test(arg)) {
21+
return arg;
22+
}
23+
return `"${arg.replace(/"/g, '\\"')}"`;
24+
}
25+
26+
function resolveNpmInvocation(npmArgs) {
27+
const npmExecPath = process.env.npm_execpath;
28+
if (npmExecPath && fs.existsSync(npmExecPath)) {
29+
return {
30+
command: process.execPath,
31+
args: [npmExecPath, ...npmArgs],
32+
display: formatNpmDisplay(npmArgs),
33+
};
34+
}
35+
if (process.platform === 'win32') {
36+
return {
37+
command: process.env.ComSpec || 'cmd.exe',
38+
args: ['/d', '/s', '/c', `npm ${npmArgs.map(quoteForCmd).join(' ')}`],
39+
display: formatNpmDisplay(npmArgs),
40+
};
41+
}
42+
return {
43+
command: 'npm',
44+
args: npmArgs,
45+
display: formatNpmDisplay(npmArgs),
46+
};
47+
}
48+
1549
function runNpm(repoRoot, npmArgs) {
16-
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
17-
const result = spawnSync(npmCommand, npmArgs, {
50+
const invocation = resolveNpmInvocation(npmArgs);
51+
const result = spawnSync(invocation.command, invocation.args, {
1852
cwd: repoRoot,
1953
stdio: 'inherit',
2054
env: process.env,
55+
windowsHide: true,
2156
});
22-
if (result.status !== 0) {
23-
throw new Error(`Command failed: ${npmCommand} ${npmArgs.join(' ')}`);
57+
if (result.error || result.status !== 0) {
58+
const details = [];
59+
if (result.status !== null && result.status !== undefined) {
60+
details.push(`status=${result.status}`);
61+
}
62+
if (result.signal) {
63+
details.push(`signal=${result.signal}`);
64+
}
65+
if (result.error) {
66+
details.push(`error=${result.error.message}`);
67+
if (result.error.code) {
68+
details.push(`error_code=${result.error.code}`);
69+
}
70+
}
71+
throw new Error(`Command failed: ${invocation.display}${details.length ? ` (${details.join(', ')})` : ''}`);
2472
}
2573
}
2674

@@ -76,4 +124,3 @@ if (require.main === module) {
76124
module.exports = {
77125
verifyAgentWorkspaceRuntime,
78126
};
79-

scripts/verify-agent-workspace-tauri.js

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,64 @@ function readText(filePath) {
1212
return fs.readFileSync(filePath, 'utf8');
1313
}
1414

15+
function formatNpmDisplay(npmArgs) {
16+
return `npm ${npmArgs.join(' ')}`;
17+
}
18+
19+
function quoteForCmd(arg) {
20+
if (!/[\s"&^|<>]/.test(arg)) {
21+
return arg;
22+
}
23+
return `"${arg.replace(/"/g, '\\"')}"`;
24+
}
25+
26+
function resolveNpmInvocation(npmArgs) {
27+
const npmExecPath = process.env.npm_execpath;
28+
if (npmExecPath && fs.existsSync(npmExecPath)) {
29+
return {
30+
command: process.execPath,
31+
args: [npmExecPath, ...npmArgs],
32+
display: formatNpmDisplay(npmArgs),
33+
};
34+
}
35+
if (process.platform === 'win32') {
36+
return {
37+
command: process.env.ComSpec || 'cmd.exe',
38+
args: ['/d', '/s', '/c', `npm ${npmArgs.map(quoteForCmd).join(' ')}`],
39+
display: formatNpmDisplay(npmArgs),
40+
};
41+
}
42+
return {
43+
command: 'npm',
44+
args: npmArgs,
45+
display: formatNpmDisplay(npmArgs),
46+
};
47+
}
48+
1549
function runNpm(repoRoot, npmArgs) {
16-
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
17-
const result = spawnSync(npmCommand, npmArgs, {
50+
const invocation = resolveNpmInvocation(npmArgs);
51+
const result = spawnSync(invocation.command, invocation.args, {
1852
cwd: repoRoot,
1953
stdio: 'inherit',
2054
env: process.env,
55+
windowsHide: true,
2156
});
22-
return result.status === 0;
57+
if (result.error || result.status !== 0) {
58+
const details = [];
59+
if (result.status !== null && result.status !== undefined) {
60+
details.push(`status=${result.status}`);
61+
}
62+
if (result.signal) {
63+
details.push(`signal=${result.signal}`);
64+
}
65+
if (result.error) {
66+
details.push(`error=${result.error.message}`);
67+
if (result.error.code) {
68+
details.push(`error_code=${result.error.code}`);
69+
}
70+
}
71+
throw new Error(`Command failed: ${invocation.display}${details.length ? ` (${details.join(', ')})` : ''}`);
72+
}
2373
}
2474

2575
function writeReport(reportPath, payload) {
@@ -51,25 +101,19 @@ function verifyAgentWorkspaceTauri(repoRoot = path.resolve(__dirname, '..'), opt
51101
assert(tauriSource.includes('toggle_pathmode_window'), 'src-tauri/lib.rs must register toggle_pathmode_window command.');
52102
assert(tauriSource.includes('open_native_pathmode'), 'src-tauri/lib.rs must register open_native_pathmode command.');
53103

54-
const sourceContractPass = runNpm(repoRoot, [
104+
runNpm(repoRoot, [
55105
'run',
56106
'test',
57107
'--',
58108
'src/runtime.capabilities.test.ts',
59109
'src/agent_workspace.runtime.integration.test.ts',
60110
'--runInBand',
61111
]);
62-
if (!sourceContractPass) {
63-
throw new Error('Source-level tauri contract tests failed.');
64-
}
65112

66113
let tauriSuiteStatus = 'skipped';
67114
let tauriSuiteMessage = 'Skipped full Rust/Tauri suite (source-level contract mode).';
68115
if (strict) {
69-
const strictPass = runNpm(repoRoot, ['run', 'test:tauri']);
70-
if (!strictPass) {
71-
throw new Error('Strict mode enabled and npm run test:tauri failed.');
72-
}
116+
runNpm(repoRoot, ['run', 'test:tauri']);
73117
tauriSuiteStatus = 'passed';
74118
tauriSuiteMessage = 'Strict Rust/Tauri suite passed.';
75119
}
@@ -112,4 +156,3 @@ if (require.main === module) {
112156
module.exports = {
113157
verifyAgentWorkspaceTauri,
114158
};
115-

0 commit comments

Comments
 (0)