@@ -1,9 +1,8 @@
|
||||
import { spawnSync } from "child_process";
|
||||
import { accessSync, constants, existsSync, readFileSync, realpathSync } from "fs";
|
||||
import { homedir } from "os";
|
||||
import { basename, dirname, join, resolve, sep, win32 } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { resolveSpawnCommand } from "./utils/child-process.js";
|
||||
import { spawnProcessSync } from "./utils/child-process.js";
|
||||
|
||||
// =============================================================================
|
||||
// Package Detection
|
||||
@@ -151,16 +150,7 @@ function readCommandOutput(
|
||||
args: string[],
|
||||
options: { requireSuccess?: boolean } = {},
|
||||
): string | undefined {
|
||||
let resolved: ReturnType<typeof resolveSpawnCommand>;
|
||||
try {
|
||||
resolved = resolveSpawnCommand(command, args);
|
||||
} catch (error) {
|
||||
if (options.requireSuccess) {
|
||||
throw error;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
const result = spawnSync(resolved.command, resolved.args, {
|
||||
const result = spawnProcessSync(command, args, {
|
||||
encoding: "utf-8",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type ChildProcess, type ChildProcessByStdio, spawn, spawnSync } from "node:child_process";
|
||||
import type { ChildProcess, ChildProcessByStdio } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
||||
import { homedir, tmpdir } from "node:os";
|
||||
@@ -28,7 +28,7 @@ import { globSync } from "glob";
|
||||
import ignore from "ignore";
|
||||
import { minimatch } from "minimatch";
|
||||
import { CONFIG_DIR_NAME } from "../config.js";
|
||||
import { resolveSpawnCommand } from "../utils/child-process.js";
|
||||
import { spawnProcess, spawnProcessSync } from "../utils/child-process.js";
|
||||
import { type GitSource, parseGitUrl } from "../utils/git.js";
|
||||
import { canonicalizePath, isLocalPath } from "../utils/paths.js";
|
||||
import { isStdoutTakenOver } from "./output-guard.js";
|
||||
@@ -2409,8 +2409,7 @@ export class DefaultPackageManager implements PackageManager {
|
||||
|
||||
private spawnCommand(command: string, args: string[], options?: { cwd?: string }): ChildProcess {
|
||||
const env = getEnv();
|
||||
const resolved = resolveSpawnCommand(command, args, { env });
|
||||
return spawn(resolved.command, resolved.args, {
|
||||
return spawnProcess(command, args, {
|
||||
cwd: options?.cwd,
|
||||
stdio: isStdoutTakenOver() ? ["ignore", 2, 2] : "inherit",
|
||||
env,
|
||||
@@ -2424,8 +2423,7 @@ export class DefaultPackageManager implements PackageManager {
|
||||
): ChildProcessByStdio<null, Readable, Readable> {
|
||||
const baseEnv = getEnv();
|
||||
const env = options?.env ? { ...baseEnv, ...options.env } : baseEnv;
|
||||
const resolved = resolveSpawnCommand(command, args, { env });
|
||||
return spawn(resolved.command, resolved.args, {
|
||||
return spawnProcess(command, args, {
|
||||
cwd: options?.cwd,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
env,
|
||||
@@ -2492,8 +2490,7 @@ export class DefaultPackageManager implements PackageManager {
|
||||
|
||||
private runCommandSync(command: string, args: string[]): string {
|
||||
const env = getEnv();
|
||||
const resolved = resolveSpawnCommand(command, args, { env });
|
||||
const result = spawnSync(resolved.command, resolved.args, {
|
||||
const result = spawnProcessSync(command, args, {
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
encoding: "utf-8",
|
||||
env,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import chalk from "chalk";
|
||||
import { spawn } from "child_process";
|
||||
import { selectConfig } from "./cli/config-selector.js";
|
||||
import {
|
||||
APP_NAME,
|
||||
@@ -14,7 +13,7 @@ import {
|
||||
} from "./config.js";
|
||||
import { DefaultPackageManager } from "./core/package-manager.js";
|
||||
import { SettingsManager } from "./core/settings-manager.js";
|
||||
import { resolveSpawnCommand } from "./utils/child-process.js";
|
||||
import { spawnProcess } from "./utils/child-process.js";
|
||||
import { getLatestPiRelease, isNewerPackageVersion } from "./utils/version-check.js";
|
||||
import {
|
||||
cleanupWindowsSelfUpdateQuarantine,
|
||||
@@ -322,8 +321,7 @@ async function runSelfUpdate(command: SelfUpdateCommand): Promise<void> {
|
||||
console.log(chalk.dim(`Updating ${APP_NAME} with ${command.display}...`));
|
||||
for (const step of command.steps ?? [command]) {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const resolved = resolveSpawnCommand(step.command, step.args);
|
||||
const child = spawn(resolved.command, resolved.args, {
|
||||
const child = spawnProcess(step.command, step.args, {
|
||||
stdio: "inherit",
|
||||
});
|
||||
child.on("error", (error) => {
|
||||
|
||||
@@ -1,84 +1,38 @@
|
||||
import type { ChildProcess } from "node:child_process";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { dirname, extname, join, resolve, sep } from "node:path";
|
||||
import {
|
||||
type ChildProcess,
|
||||
type ChildProcessByStdio,
|
||||
spawn as nodeSpawn,
|
||||
spawnSync as nodeSpawnSync,
|
||||
type SpawnOptions,
|
||||
type SpawnOptionsWithStdioTuple,
|
||||
type SpawnSyncOptionsWithStringEncoding,
|
||||
type SpawnSyncReturns,
|
||||
type StdioNull,
|
||||
type StdioPipe,
|
||||
} from "node:child_process";
|
||||
import type { Readable } from "node:stream";
|
||||
import crossSpawn from "cross-spawn";
|
||||
|
||||
const EXIT_STDIO_GRACE_MS = 100;
|
||||
const WINDOWS_COMMAND_EXTENSIONS = [".exe", ".cmd", ".bat", ""];
|
||||
const WINDOWS_COMMAND_SHIM_RE = /\.(?:cmd|bat)$/i;
|
||||
const NODE_SHIM_SCRIPT_RE = /(?:%~dp0|%dp0%|%basedir%)[^"'\r\n<>|&]*?\.(?:cjs|mjs|js)/gi;
|
||||
|
||||
export interface ResolvedSpawnCommand {
|
||||
command: string;
|
||||
args: string[];
|
||||
}
|
||||
|
||||
function findWindowsCommand(command: string, env: NodeJS.ProcessEnv): string | undefined {
|
||||
const candidates = extname(command)
|
||||
? [command]
|
||||
: WINDOWS_COMMAND_EXTENSIONS.map((extension) => `${command}${extension}`);
|
||||
const hasPath = command.includes("/") || command.includes("\\") || /^[a-zA-Z]:/.test(command);
|
||||
if (hasPath) {
|
||||
const match = candidates.find((candidate) => existsSync(candidate));
|
||||
return match ? resolve(match) : undefined;
|
||||
}
|
||||
|
||||
const pathValue = env.PATH ?? env.Path ?? env.path;
|
||||
if (!pathValue) return undefined;
|
||||
for (const dir of pathValue.split(";")) {
|
||||
for (const candidate of candidates) {
|
||||
const path = join(dir, candidate);
|
||||
if (existsSync(path)) return resolve(path);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function expandShimPath(path: string, shimPath: string): string {
|
||||
const shimDir = dirname(shimPath);
|
||||
return resolve(
|
||||
path
|
||||
.replace(/%~dp0[\\/]?/gi, `${shimDir}${sep}`)
|
||||
.replace(/%dp0%[\\/]?/gi, `${shimDir}${sep}`)
|
||||
.replace(/%basedir%[\\/]?/gi, `${shimDir}${sep}`)
|
||||
.replace(/\\/g, sep),
|
||||
);
|
||||
}
|
||||
|
||||
function findNodeShimScript(shimPath: string): string | undefined {
|
||||
const matches = [...readFileSync(shimPath, "utf-8").matchAll(NODE_SHIM_SCRIPT_RE)];
|
||||
for (const match of matches.reverse()) {
|
||||
const scriptPath = expandShimPath(match[0], shimPath);
|
||||
if (existsSync(scriptPath)) return scriptPath;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function resolveSpawnCommand(
|
||||
export function spawnProcess(
|
||||
command: string,
|
||||
args: string[],
|
||||
options: { env?: NodeJS.ProcessEnv } = {},
|
||||
): ResolvedSpawnCommand {
|
||||
if (process.platform !== "win32") {
|
||||
return { command, args };
|
||||
}
|
||||
options: SpawnOptionsWithStdioTuple<StdioNull, StdioPipe, StdioPipe>,
|
||||
): ChildProcessByStdio<null, Readable, Readable>;
|
||||
export function spawnProcess(command: string, args: string[], options: SpawnOptions): ChildProcess;
|
||||
export function spawnProcess(command: string, args: string[], options: SpawnOptions): ChildProcess {
|
||||
return process.platform === "win32" ? crossSpawn(command, args, options) : nodeSpawn(command, args, options);
|
||||
}
|
||||
|
||||
const env = options.env ?? process.env;
|
||||
const resolvedCommand = findWindowsCommand(command, env);
|
||||
if (!resolvedCommand) {
|
||||
return { command, args };
|
||||
}
|
||||
|
||||
if (!WINDOWS_COMMAND_SHIM_RE.test(resolvedCommand)) {
|
||||
return { command: resolvedCommand, args };
|
||||
}
|
||||
|
||||
const script = findNodeShimScript(resolvedCommand);
|
||||
if (!script) {
|
||||
throw new Error(`Refusing to run Windows command shim without a shell: ${resolvedCommand}`);
|
||||
}
|
||||
const localNode = join(dirname(resolvedCommand), "node.exe");
|
||||
const nodeCommand = existsSync(localNode) ? localNode : (findWindowsCommand("node.exe", env) ?? "node");
|
||||
return { command: nodeCommand, args: [script, ...args] };
|
||||
export function spawnProcessSync(
|
||||
command: string,
|
||||
args: string[],
|
||||
options: SpawnSyncOptionsWithStringEncoding,
|
||||
): SpawnSyncReturns<string> {
|
||||
return process.platform === "win32"
|
||||
? crossSpawn.sync(command, args, options)
|
||||
: nodeSpawnSync(command, args, options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user