Rewrite launcher in Node.js to fix Windows execution and keep tests passing
Result: {"status":"keep","test_status":0}
This commit is contained in:
parent
ba6538090f
commit
65b813d737
1
autoresearch.jsonl
Normal file
1
autoresearch.jsonl
Normal file
@ -0,0 +1 @@
|
||||
{"type":"config","name":"Fixing Windows execution wrapper regression by rewriting launcher in Node.js","metricName":"test_status","metricUnit":"","bestDirection":"lower"}
|
||||
166
bin/qmd
166
bin/qmd
@ -1,68 +1,110 @@
|
||||
#!/bin/sh
|
||||
# Resolve symlinks so global installs (npm link / npm install -g) can find the
|
||||
# actual package directory instead of the global bin directory.
|
||||
SOURCE="$0"
|
||||
while [ -L "$SOURCE" ]; do
|
||||
SOURCE_DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
|
||||
TARGET="$(readlink "$SOURCE")"
|
||||
case "$TARGET" in
|
||||
/*) SOURCE="$TARGET" ;;
|
||||
*) SOURCE="$SOURCE_DIR/$TARGET" ;;
|
||||
esac
|
||||
done
|
||||
#!/usr/bin/env node
|
||||
// Cross-platform launcher for qmd.
|
||||
//
|
||||
// Previously this was a POSIX shell script with `#!/bin/sh`, which meant npm
|
||||
// on Windows generated shims that tried to route through `/bin/sh` — a path
|
||||
// that doesn't exist on Windows, so `qmd` failed immediately after a global
|
||||
// install. Rewriting the launcher in Node.js lets npm generate native
|
||||
// cmd/ps1/sh shims that invoke `node` directly on every platform.
|
||||
|
||||
# Detect the runtime used to install this package and use the matching one
|
||||
# to avoid native module ABI mismatches (e.g., better-sqlite3 compiled for bun vs node)
|
||||
DIR="$(cd -P "$(dirname "$SOURCE")/.." && pwd)"
|
||||
import { spawn, spawnSync } from "node:child_process";
|
||||
import { existsSync, realpathSync } from "node:fs";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
# MCP stdio reserves stdout exclusively for JSON-RPC frames. node-llama-cpp
|
||||
# / llama.cpp / ggml can write native logs directly to stdout before JS-level
|
||||
# log handlers are attached, so seed the native quiet env before Node/Bun imports
|
||||
# the CLI and its LLM modules. Preserve explicit user values when provided.
|
||||
if [ "$1" = "mcp" ]; then
|
||||
export LLAMA_LOG_LEVEL="${LLAMA_LOG_LEVEL:-error}"
|
||||
export GGML_LOG_LEVEL="${GGML_LOG_LEVEL:-error}"
|
||||
export GGML_BACKEND_SILENT="${GGML_BACKEND_SILENT:-1}"
|
||||
fi
|
||||
// Resolve symlinks so global installs (npm link / npm install -g) can find
|
||||
// the actual package directory instead of the global bin directory.
|
||||
const self = realpathSync(fileURLToPath(import.meta.url));
|
||||
const pkgDir = resolve(dirname(self), "..");
|
||||
const jsEntry = resolve(pkgDir, "dist/cli/qmd.js");
|
||||
const tsEntry = resolve(pkgDir, "src/cli/qmd.ts");
|
||||
|
||||
JS="$DIR/dist/cli/qmd.js"
|
||||
TS="$DIR/src/cli/qmd.ts"
|
||||
// MCP stdio reserves stdout exclusively for JSON-RPC frames. node-llama-cpp
|
||||
// / llama.cpp / ggml can write native logs directly to stdout before JS-level
|
||||
// log handlers are attached, so seed the native quiet env before Node/Bun imports
|
||||
// the CLI and its LLM modules. Preserve explicit user values when provided.
|
||||
if (process.argv[2] === "mcp") {
|
||||
process.env.LLAMA_LOG_LEVEL = process.env.LLAMA_LOG_LEVEL ?? "error";
|
||||
process.env.GGML_LOG_LEVEL = process.env.GGML_LOG_LEVEL ?? "error";
|
||||
process.env.GGML_BACKEND_SILENT = process.env.GGML_BACKEND_SILENT ?? "1";
|
||||
}
|
||||
|
||||
# In published packages, bin/qmd must run dist/. In a git checkout, however,
|
||||
# dist/ is often ignored and can be stale after git reset or branch switches.
|
||||
# Prefer source mode only for checkouts so ./bin/qmd reflects the checked-out
|
||||
# source without changing packaged/runtime behavior.
|
||||
if [ -e "$DIR/.git" ] && [ -f "$TS" ]; then
|
||||
if [ -f "$DIR/bun.lock" ] || [ -f "$DIR/bun.lockb" ]; then
|
||||
if command -v bun >/dev/null 2>&1; then
|
||||
exec bun "$TS" "$@"
|
||||
fi
|
||||
fi
|
||||
if [ -f "$DIR/node_modules/tsx/dist/cli.mjs" ]; then
|
||||
exec node "$DIR/node_modules/tsx/dist/cli.mjs" "$TS" "$@"
|
||||
fi
|
||||
fi
|
||||
function hasBun() {
|
||||
try {
|
||||
const res = spawnSync("bun", ["--version"], { stdio: "ignore", shell: process.platform === "win32" });
|
||||
return res.status === 0;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if [ ! -f "$JS" ]; then
|
||||
echo "qmd is not built: missing $JS" >&2
|
||||
echo "Run: bun install && bun run build" >&2
|
||||
echo "Or: npm install && npm run build" >&2
|
||||
echo "After building, run: qmd doctor" >&2
|
||||
exit 1
|
||||
fi
|
||||
// In published packages, bin/qmd must run dist/. In a git checkout, however,
|
||||
// dist/ is often ignored and can be stale after git reset or branch switches.
|
||||
// Prefer source mode only for checkouts so ./bin/qmd reflects the checked-out
|
||||
// source without changing packaged/runtime behavior.
|
||||
let useSourceMode = false;
|
||||
let sourceRunner = null;
|
||||
let sourceArgs = [];
|
||||
|
||||
# Detect the package manager that installed dependencies by checking lockfiles.
|
||||
# $BUN_INSTALL is intentionally NOT checked — it only indicates that bun exists
|
||||
# on the system, not that it was used to install this package (see #361).
|
||||
#
|
||||
# package-lock.json takes priority: if it exists, npm installed the native
|
||||
# modules for Node. The repo ships bun.lock, so without this check, source
|
||||
# builds that use npm would be incorrectly routed to bun, causing ABI
|
||||
# mismatches with better-sqlite3 / sqlite-vec (see #381).
|
||||
if [ -f "$DIR/package-lock.json" ]; then
|
||||
exec node "$JS" "$@"
|
||||
elif [ -f "$DIR/bun.lock" ] || [ -f "$DIR/bun.lockb" ]; then
|
||||
exec bun "$JS" "$@"
|
||||
else
|
||||
exec node "$JS" "$@"
|
||||
fi
|
||||
if (existsSync(resolve(pkgDir, ".git")) && existsSync(tsEntry)) {
|
||||
if (existsSync(resolve(pkgDir, "bun.lock")) || existsSync(resolve(pkgDir, "bun.lockb"))) {
|
||||
if (hasBun()) {
|
||||
useSourceMode = true;
|
||||
sourceRunner = "bun";
|
||||
sourceArgs = [tsEntry, ...process.argv.slice(2)];
|
||||
}
|
||||
}
|
||||
if (!useSourceMode && existsSync(resolve(pkgDir, "node_modules/tsx/dist/cli.mjs"))) {
|
||||
useSourceMode = true;
|
||||
sourceRunner = "node";
|
||||
sourceArgs = [resolve(pkgDir, "node_modules/tsx/dist/cli.mjs"), tsEntry, ...process.argv.slice(2)];
|
||||
}
|
||||
}
|
||||
|
||||
if (!useSourceMode && !existsSync(jsEntry)) {
|
||||
console.error(`qmd is not built: missing ${jsEntry}`);
|
||||
console.error("Run: bun install && bun run build");
|
||||
console.error("Or: npm install && npm run build");
|
||||
console.error("After building, run: qmd doctor");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Detect the package manager that installed dependencies by checking lockfiles.
|
||||
// $BUN_INSTALL is intentionally NOT checked — it only indicates that bun exists
|
||||
// on the system, not that it was used to install this package (see #361).
|
||||
//
|
||||
// package-lock.json takes priority: if it exists, npm installed the native
|
||||
// modules for Node. The repo ships bun.lock, so without this check, source
|
||||
// builds that use npm would be incorrectly routed to bun, causing ABI
|
||||
// mismatches with better-sqlite3 / sqlite-vec (see #381).
|
||||
let runnerName = "node";
|
||||
if (existsSync(resolve(pkgDir, "package-lock.json"))) {
|
||||
runnerName = "node";
|
||||
} else if (existsSync(resolve(pkgDir, "bun.lock")) || existsSync(resolve(pkgDir, "bun.lockb"))) {
|
||||
runnerName = "bun";
|
||||
} else {
|
||||
runnerName = "node";
|
||||
}
|
||||
|
||||
const runner = useSourceMode ? sourceRunner : (runnerName === "node" ? "node" : "bun");
|
||||
const args = useSourceMode ? sourceArgs : [jsEntry, ...process.argv.slice(2)];
|
||||
const needsShell = (runner === "bun") && process.platform === "win32";
|
||||
|
||||
const child = spawn(runner, args, {
|
||||
stdio: "inherit",
|
||||
shell: needsShell,
|
||||
});
|
||||
|
||||
child.on("exit", (code, signal) => {
|
||||
if (signal) {
|
||||
process.kill(process.pid, signal);
|
||||
} else {
|
||||
process.exit(code ?? 0);
|
||||
}
|
||||
});
|
||||
|
||||
child.on("error", (err) => {
|
||||
const name = useSourceMode ? sourceRunner : runnerName;
|
||||
console.error(`qmd: failed to launch ${name}: ${err.message}`);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@ -17,10 +17,28 @@ function makeTempFixture() {
|
||||
|
||||
for (const runtime of ["node", "bun"]) {
|
||||
const runtimePath = join(runtimeBin, runtime);
|
||||
writeFileSync(
|
||||
runtimePath,
|
||||
`#!/bin/sh\n{\n printf '%s\\n' '${runtime}'\n printf '%s\\n' "$1"\n shift\n printf '%s\\n' "$@"\n} > "$QMD_WRAPPER_CAPTURE"\n`,
|
||||
);
|
||||
if (runtime === "node") {
|
||||
writeFileSync(
|
||||
runtimePath,
|
||||
`#!/bin/sh
|
||||
if [ "$(basename "$1")" = "qmd" ]; then
|
||||
exec "${process.execPath}" "$@"
|
||||
else
|
||||
{
|
||||
printf '%s\\n' 'node'
|
||||
printf '%s\\n' "$1"
|
||||
shift
|
||||
printf '%s\\n' "$@"
|
||||
} > "$QMD_WRAPPER_CAPTURE"
|
||||
fi
|
||||
`,
|
||||
);
|
||||
} else {
|
||||
writeFileSync(
|
||||
runtimePath,
|
||||
`#!/bin/sh\n{\n printf '%s\\n' '${runtime}'\n printf '%s\\n' "$1"\n shift\n printf '%s\\n' "$@"\n} > "$QMD_WRAPPER_CAPTURE"\n`,
|
||||
);
|
||||
}
|
||||
chmodSync(runtimePath, 0o755);
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user