mirror of
https://github.com/JuliusBrussee/caveman.git
synced 2026-08-11 13:21:09 +02:00
fix(mcp-shrink): shell:true on Windows so npx/.cmd upstream resolves
Folds in #387 — extract spawn options to spawn-options.js; shell:true only on win32 (PATHEXT resolution), POSIX unchanged. Args still come from installer-controlled config (trust boundary unchanged). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
46de578a7e
commit
6ce47d4445
@@ -40,9 +40,9 @@ const debug = process.env.CAVEMAN_SHRINK_DEBUG === '1';
|
||||
const fields = (process.env.CAVEMAN_SHRINK_FIELDS || 'description')
|
||||
.split(',').map(s => s.trim()).filter(Boolean);
|
||||
|
||||
const upstream = spawn(args[0], args.slice(1), {
|
||||
stdio: ['pipe', 'pipe', 'inherit'],
|
||||
});
|
||||
const { getSpawnOptions } = require('./spawn-options');
|
||||
|
||||
const upstream = spawn(args[0], args.slice(1), getSpawnOptions());
|
||||
|
||||
upstream.on('error', err => {
|
||||
process.stderr.write(`caveman-shrink: failed to spawn upstream: ${err.message}\n`);
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// Spawn options for the upstream MCP child process.
|
||||
//
|
||||
// Windows: spawn('npx', ...) (and any .cmd shim such as 'gemini') hits ENOENT
|
||||
// because PATHEXT resolution only happens when child_process spawns through
|
||||
// a shell. POSIX systems resolve fine without a shell. Keep shell:false on
|
||||
// POSIX to avoid argv quoting surprises.
|
||||
//
|
||||
// Exported standalone so the behavior is unit-testable without re-running
|
||||
// the CLI entry point (index.js exits immediately when args are empty).
|
||||
|
||||
'use strict';
|
||||
|
||||
function getSpawnOptions(platform = process.platform) {
|
||||
return {
|
||||
stdio: ['pipe', 'pipe', 'inherit'],
|
||||
shell: platform === 'win32',
|
||||
windowsHide: true,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { getSpawnOptions };
|
||||
@@ -9,6 +9,9 @@ const ROOT = path.resolve(__dirname, '..');
|
||||
const { compress, compressDescriptionsInPlace } = require(
|
||||
path.join(ROOT, 'src', 'mcp-servers', 'caveman-shrink', 'compress.js')
|
||||
);
|
||||
const { getSpawnOptions } = require(
|
||||
path.join(ROOT, 'src', 'mcp-servers', 'caveman-shrink', 'spawn-options.js')
|
||||
);
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
@@ -123,5 +126,34 @@ test('compressDescriptionsInPlace skips non-string description fields', () => {
|
||||
assert.deepStrictEqual(obj.description, { not: 'a string' });
|
||||
});
|
||||
|
||||
// spawn-options: upstream MCP child process spawn flags.
|
||||
// Confirms shell:true on Windows (so npx and other .cmd shims resolve) and
|
||||
// shell:false on POSIX.
|
||||
|
||||
test('win32 enables shell so npx and .cmd shims resolve', () => {
|
||||
const opts = getSpawnOptions('win32');
|
||||
assert.equal(opts.shell, true);
|
||||
assert.equal(opts.windowsHide, true);
|
||||
assert.deepEqual(opts.stdio, ['pipe', 'pipe', 'inherit']);
|
||||
});
|
||||
|
||||
test('linux keeps shell off to avoid argv quoting surprises', () => {
|
||||
const opts = getSpawnOptions('linux');
|
||||
assert.equal(opts.shell, false);
|
||||
assert.deepEqual(opts.stdio, ['pipe', 'pipe', 'inherit']);
|
||||
});
|
||||
|
||||
test('darwin keeps shell off', () => {
|
||||
const opts = getSpawnOptions('darwin');
|
||||
assert.equal(opts.shell, false);
|
||||
});
|
||||
|
||||
test('defaults to current platform when no arg passed', () => {
|
||||
const opts = getSpawnOptions();
|
||||
assert.equal(opts.shell, process.platform === 'win32');
|
||||
assert.equal(opts.windowsHide, true);
|
||||
assert.deepEqual(opts.stdio, ['pipe', 'pipe', 'inherit']);
|
||||
});
|
||||
|
||||
console.log(`\n${passed} passed, ${failed} failed`);
|
||||
process.exit(failed ? 1 : 0);
|
||||
|
||||
Reference in New Issue
Block a user