jacobinwwey_obsidian-NotEMD/scripts/lib/cross-platform-command.js

175 lines
4.8 KiB
JavaScript

'use strict';
const { spawnSync } = require('child_process');
const fs = require('fs');
const path = require('path');
function spawnSyncWithCommandResolution(command, args = [], options = {}) {
if (process.platform === 'win32') {
const resolvedCommand = resolveWindowsCommand(command, options);
if (resolvedCommand) {
if (isWindowsBatchFile(resolvedCommand)) {
return spawnWindowsBatchFileSync(resolvedCommand, args, options);
}
return spawnSync(resolvedCommand, args, {
...options,
shell: false,
windowsHide: true
});
}
}
const directResult = spawnSync(command, args, {
...options,
shell: false,
windowsHide: true
});
if (!shouldRetryWithWindowsShell(directResult.error)) {
return directResult;
}
const resolvedCommand = resolveWindowsCommand(command, options);
if (resolvedCommand && isWindowsBatchFile(resolvedCommand)) {
return spawnWindowsBatchFileSync(resolvedCommand, args, options);
}
return directResult;
}
function spawnWindowsBatchFileSync(command, args, options) {
const commandLine = [
'call',
quoteCmdArgument(command),
...args.map(quoteCmdArgument)
].join(' ');
return spawnSync(process.env.ComSpec || 'cmd.exe', ['/d', '/s', '/c', commandLine], {
...options,
shell: false,
windowsVerbatimArguments: true,
windowsHide: true
});
}
function execFileSyncWithCommandResolution(command, args = [], options = {}) {
const result = spawnSyncWithCommandResolution(command, args, {
...options,
encoding: options.encoding ?? 'buffer'
});
if (result.error) {
throw result.error;
}
if (result.status !== 0) {
throw createCommandFailure(command, args, result);
}
return result.stdout;
}
function createCommandFailure(command, args, result) {
const error = new Error(`${command} ${args.join(' ')} failed with exit code ${result.status}`);
error.status = result.status;
error.signal = result.signal;
error.stdout = result.stdout;
error.stderr = result.stderr;
return error;
}
function shouldRetryWithWindowsShell(error) {
if (process.platform !== 'win32' || !error) {
return false;
}
const code = String(error.code ?? '').toUpperCase();
const message = String(error.message ?? '').toUpperCase();
return code === 'EINVAL'
|| code === 'ENOENT'
|| message.includes('SPAWN EINVAL')
|| message.includes('SPAWN ENOENT');
}
function resolveWindowsCommand(command, options = {}) {
if (typeof command !== 'string' || command.trim().length === 0) {
return null;
}
const env = options.env ?? process.env;
const cwd = options.cwd ?? process.cwd();
const pathEntries = String(env.Path ?? env.PATH ?? process.env.Path ?? process.env.PATH ?? '')
.split(path.delimiter)
.filter(Boolean);
const pathExts = String(env.PATHEXT ?? process.env.PATHEXT ?? '.COM;.EXE;.BAT;.CMD')
.split(';')
.filter(Boolean);
const hasDirectory = /[\\/]/.test(command);
const candidates = commandHasExtension(command)
? [command]
: pathExts.map((extension) => `${command}${extension}`);
if (hasDirectory) {
for (const candidate of candidates) {
const absoluteCandidate = path.resolve(cwd, candidate);
if (fs.existsSync(absoluteCandidate)) {
return absoluteCandidate;
}
}
return null;
}
for (const entry of [cwd, ...pathEntries]) {
for (const candidate of candidates) {
const absoluteCandidate = path.join(entry, candidate);
if (fs.existsSync(absoluteCandidate)) {
return absoluteCandidate;
}
}
}
return null;
}
function commandHasExtension(command) {
return path.extname(command).length > 0;
}
function isWindowsBatchFile(command) {
const extension = path.extname(command).toLowerCase();
return extension === '.cmd' || extension === '.bat';
}
function quoteCmdArgument(value) {
const source = String(value);
let quoted = '"';
let backslashCount = 0;
for (const char of source) {
if (char === '\\') {
backslashCount += 1;
continue;
}
if (char === '"') {
quoted += '\\'.repeat(backslashCount * 2 + 1);
quoted += '"';
backslashCount = 0;
continue;
}
quoted += '\\'.repeat(backslashCount);
quoted += char;
backslashCount = 0;
}
quoted += '\\'.repeat(backslashCount * 2);
quoted += '"';
return quoted;
}
module.exports = {
execFileSyncWithCommandResolution,
spawnSyncWithCommandResolution
};