Initial curated itworx-knowledge-hub source from 3b1751d0d9dee551aa6994eee0093704dc334817
This commit is contained in:
commit
ad8ad252a9
825 files changed
+124216
No files matched your search
@@ -0,0 +1,106 @@
|
||||
import { spawn, spawnSync } from "node:child_process";
|
||||
import process from "node:process";
|
||||
|
||||
export function commandExists(command) {
|
||||
const locator = process.platform === "win32" ? "where" : "which";
|
||||
const result = spawnSync(locator, [command], {
|
||||
stdio: "ignore",
|
||||
shell: false
|
||||
});
|
||||
return result.status === 0;
|
||||
}
|
||||
|
||||
// On Windows, some tools (corepack, pnpm, npm, ...) are installed as `.cmd`/`.bat`
|
||||
// shims. Those are not real PE executables, so libuv/CreateProcess cannot launch
|
||||
// them directly: `child_process.spawn` fails with ENOENT even though the same
|
||||
// command works in an interactive shell. Real executables (dotnet.exe, docker.exe,
|
||||
// node.exe, python.exe) do not have this problem and must NOT be routed through a
|
||||
// shell, because shell-quoting a path that contains spaces (e.g. "C:\Program
|
||||
// Files\...") breaks it. Detect which case applies per command instead of
|
||||
// guessing globally.
|
||||
function requiresWindowsShell(command) {
|
||||
if (process.platform !== "win32") {
|
||||
return false;
|
||||
}
|
||||
if (/[\\/]/.test(command)) {
|
||||
// Already a path; trust its extension.
|
||||
return /\.(cmd|bat)$/i.test(command);
|
||||
}
|
||||
const located = spawnSync("where", [command], {
|
||||
encoding: "utf8",
|
||||
shell: false
|
||||
});
|
||||
if (located.status !== 0) {
|
||||
return false;
|
||||
}
|
||||
// `where` can list several matches on Windows (e.g. an extension-less shebang
|
||||
// launcher script alongside a `.cmd` shim for the same tool). Node can only
|
||||
// ever run the `.cmd`/`.bat` variant through a shell, so treat the command as
|
||||
// shell-requiring if *any* match needs one, not just the first line.
|
||||
const matches = located.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
||||
return matches.some((match) => /\.(cmd|bat)$/i.test(match));
|
||||
}
|
||||
|
||||
function quoteForWindowsShell(value) {
|
||||
if (value === "") {
|
||||
return '""';
|
||||
}
|
||||
if (!/[\s"^&|<>()%]/.test(value)) {
|
||||
return value;
|
||||
}
|
||||
return `"${value.replace(/"/g, '\\"')}"`;
|
||||
}
|
||||
|
||||
export async function run(command, args = [], options = {}) {
|
||||
const printable = [command, ...args].join(" ");
|
||||
if (!options.quiet) {
|
||||
process.stdout.write(`\n> ${printable}\n`);
|
||||
}
|
||||
|
||||
const useShell = requiresWindowsShell(command);
|
||||
const spawnCommand = useShell
|
||||
? [command, ...args].map(quoteForWindowsShell).join(" ")
|
||||
: command;
|
||||
const spawnArgs = useShell ? [] : args;
|
||||
|
||||
return await new Promise((resolve, reject) => {
|
||||
const child = spawn(spawnCommand, spawnArgs, {
|
||||
cwd: options.cwd ?? process.cwd(),
|
||||
env: { ...process.env, ...options.env },
|
||||
stdio: options.capture ? ["ignore", "pipe", "pipe"] : "inherit",
|
||||
shell: useShell
|
||||
});
|
||||
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
|
||||
if (options.capture) {
|
||||
child.stdout?.on("data", (chunk) => {
|
||||
stdout += chunk.toString();
|
||||
});
|
||||
child.stderr?.on("data", (chunk) => {
|
||||
stderr += chunk.toString();
|
||||
});
|
||||
}
|
||||
|
||||
child.on("error", reject);
|
||||
child.on("close", (code, signal) => {
|
||||
if (code === 0) {
|
||||
resolve({ code, stdout, stderr });
|
||||
return;
|
||||
}
|
||||
|
||||
const suffix = signal ? ` (signal ${signal})` : "";
|
||||
const error = new Error(`Command failed (${code}${suffix}): ${printable}`);
|
||||
error.code = code;
|
||||
error.stdout = stdout;
|
||||
error.stderr = stderr;
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function capture(command, args = [], options = {}) {
|
||||
const result = await run(command, args, { ...options, capture: true, quiet: true });
|
||||
return result.stdout.trim();
|
||||
}
|
||||
Reference in new issue
Block a user