UNPKG

kaven-utils

Version:

Utils for Node.js.

205 lines (204 loc) 8.05 kB
/******************************************************************** * @author: Kaven * @email: kaven@wuwenkai.com * @website: http://blog.kaven.xyz * @file: [Kaven-Utils] /src/KavenUtility.ChildProcess.ts * @create: 2023-11-25 22:29:30.048 * @modify: 2025-10-23 23:25:06.133 * @version: 6.1.2 * @times: 43 * @lines: 240 * @copyright: Copyright © 2023-2025 Kaven. All Rights Reserved. * @description: [description] * @license: [license] ********************************************************************/ import pkgIconvLite from "iconv-lite"; import { KavenCache } from "kaven-basic"; import { exec, execSync, spawn } from "node:child_process"; import { EOL } from "node:os"; import { PassThrough, Writable } from "node:stream"; import { IsWin32 } from "./KavenUtility.Constant.js"; /** * @since 6.0.5 * @version 2025-10-11 */ export function Tee(...targets) { return new Writable({ write(chunk, enc, cb) { for (const t of targets) { t.write(chunk, enc); } cb(); }, final(cb) { for (const t of targets) { t.end(); } cb(); }, }); } /** * @since 6.0.5 * @version 2025-10-11 */ export function GetSystemEncoding(options) { const key = "KavenUtility.ChildProcess.GetSystemEncoding"; let cachedEncoding = KavenCache.GetCache(key); if (cachedEncoding) { return cachedEncoding; // ✅ cached result } try { if (IsWin32) { const output = execSync("chcp", { encoding: "utf8" }); const match = output.match(/:\s*(\d+)/); const cp = match ? parseInt(match[1], 10) : 65001; const map = { 65001: "utf8", 936: "gbk", 950: "big5", 932: "shift_jis", 1252: "windows-1252", 437: "ibm437", }; cachedEncoding = map[cp] || "utf8"; } else { // Unix-like: locale charmap is usually constant too const output = execSync("locale charmap", { encoding: "utf8" }).trim(); cachedEncoding = output || "utf8"; } } catch (ex) { options?.logger?.Warn(ex); cachedEncoding = "utf8"; } KavenCache.SetCacheWithHour(key, cachedEncoding, 2); return cachedEncoding; } export async function Execute(p1, p2) { const decodeStream = pkgIconvLite.decodeStream; return new Promise((resolve, reject) => { try { let command = typeof p1 === "string" ? p1 : p1.command; const options = typeof p1 === "string" ? (p2 ?? {}) : (p1.options ?? {}); const logger = options.logger; logger?.Info(`Execute: ${command}`); if (options.decoderEncoding === undefined && options.autoDetectDecoderEncoding) { options.decoderEncoding = GetSystemEncoding({ logger }); logger?.Info(`GetSystemEncoding: ${options.decoderEncoding}`); } if (options.suppressingInteractiveInput) { if (IsWin32) { command = `${command} < NUL`; } else { command = `${command} < /dev/null`; } } const stderrTee = new PassThrough(); const stderrChunks = []; stderrTee.on("data", data => { stderrChunks.push(data); }); if (options.stderr) { options.stderr = Tee(options.stderr, stderrTee); } else { options.stderr = stderrTee; } const done = (code, signal) => { if (code !== 0 && !options.resolveNonZeroExitCodes) { let message = `Child process closed with code ${code} and signal ${signal}`; if (stderrChunks.length > 0) { const error = Buffer.concat(stderrChunks).toString(); message = error + EOL + message; } reject(new Error(message)); } else { resolve(code); } }; if (options.spawn) { if (options.spawnArgs === undefined) { // command = command.trim(); // if (command.startsWith(Strings_DoubleQuotes)) { // const index = command.indexOf(Strings_DoubleQuotes, 1); // if (index > 0) { // command = command.substring(0, index); // options.spawnArgs = command.substring(index + 1).trim().split(Strings_WhiteSpace); // } // } else if (command.startsWith("'")) { // const index = command.indexOf("'", 1); // if (index > 0) { // command = command.substring(0, index); // options.spawnArgs = command.substring(index + 1).trim().split(Strings_WhiteSpace); // } // } else { // [command, ...options.spawnArgs] = command.trim().split(Strings_WhiteSpace); // } command = command.trim(); // Regular expression to match quoted or unquoted parts const regex = /(?:[^\s'"]+|"[^"]*"|'[^']*')+/g; // Extract the matched parts const parts = command.match(regex) || []; // The first part is the command command = parts.shift() ?? command; // The remaining parts are the arguments options.spawnArgs = parts.map(arg => arg.replace(/^(['"])(.*)\1$/, "$2")); } const p = spawn(command, options.spawnArgs, options.spawnOptions); p.on("close", (code, signal) => done(code, signal)); p.on("error", data => { logger?.Error(data); }); if (options.decoderEncoding) { if (options.stdout) { p.stdout.pipe(decodeStream(options.decoderEncoding)).pipe(options.stdout); } if (options.stderr) { p.stderr.pipe(decodeStream(options.decoderEncoding)).pipe(options.stderr); } } else { if (options.stdout) { p.stdout.pipe(options.stdout); } if (options.stderr) { p.stderr.pipe(options.stderr); } } } else { const p = exec(command, { encoding: "buffer", ...options.execOptions, }); p.on("close", (code, signal) => done(code, signal)); p.on("error", data => { logger?.Error(data); }); if (options.stdout && p.stdout) { if (options.decoderEncoding) { p.stdout.pipe(decodeStream(options.decoderEncoding)).pipe(options.stdout); } else { p.stdout.pipe(options.stdout); } } if (options.stderr && p.stderr) { if (options.decoderEncoding) { p.stderr.pipe(decodeStream(options.decoderEncoding)).pipe(options.stderr); } else { p.stderr.pipe(options.stderr); } } } } catch (ex) { reject(ex); } }); }