UNPKG

penguins-eggs

Version:

A remaster system tool, compatible with Almalinux, Alpine, Arch, Debian, Devuan, Fedora, Manjaro, Opensuse, Ubuntu and derivatives

287 lines (286 loc) 9.42 kB
/** * ./src/lib/utils.ts * penguins-eggs v.25.7.x / ecmascript 2020 * author: Piero Proietti * email: piero.proietti@gmail.com * license: MIT */ // Importiamo con alias per poter wrappare import { spawn as nodeSpawn, spawnSync as nodeSpawnSync } from 'child_process'; import fs from 'fs'; import path from 'path'; /** * Pulizia AppImage * Variabili d'ambiente da rimuovere per evitare conflitti quando si eseguono * comandi di sistema dall'interno di una AppImage. */ const APPIMAGE_ENV_BLACKLIST = ['LD_LIBRARY_PATH', 'LD_PRELOAD', 'PYTHONPATH', 'PERLLIB', 'GSETTINGS_SCHEMA_DIR', 'QT_PLUGIN_PATH', 'XDG_DATA_DIRS', 'LIBRARY_PATH', 'PKG_CONFIG_PATH', 'GIO_MODULE_DIR', 'APPIMAGE', 'APPDIR']; /** * Ottiene un ambiente pulito dalle variabili AppImage * @returns Oggetto process.env sanificato */ function getCleanEnv() { const env = { ...process.env }; if (process.env.APPIMAGE) { for (const key of APPIMAGE_ENV_BLACKLIST) delete env[key]; } return env; } /** * spawnSync (WRAPPER INTELLIGENTE) * Supporta: * 1. (command, args, options) * 2. (command, options) -> args diventa [] * Pulisce automaticamente l'ambiente. */ export function spawnSync(command, arg2, arg3) { let args = []; let options = {}; // Rilevamento argomenti (Polimorfismo) if (Array.isArray(arg2)) { args = arg2; options = arg3 || {}; } else if (arg2 && typeof arg2 === 'object') { // TypeScript fix: cast esplicito per evitare errori di tipo union options = arg2; } const env = getCleanEnv(); const finalEnv = { ...env, ...options.env }; return nodeSpawnSync(command, args, { ...options, env: finalEnv }); } /** * spawn (WRAPPER INTELLIGENTE) * Supporta: * 1. (command, args, options) * 2. (command, options) -> args diventa [] * Pulisce automaticamente l'ambiente. */ export function spawn(command, arg2, arg3) { let args = []; let options = {}; // Rilevamento argomenti (Polimorfismo) if (Array.isArray(arg2)) { args = arg2; options = arg3 || {}; } else if (arg2 && typeof arg2 === 'object') { // TypeScript fix: cast esplicito options = arg2; } const env = getCleanEnv(); const finalEnv = { ...env, ...options.env }; return nodeSpawn(command, args, { ...options, env: finalEnv }); } /** * shx * Sostituto drop-in per shelljs che usa API native e ambiente pulito. */ export const shx = { chmod(mode, file) { if (!fs.existsSync(file)) return; let finalMode = mode; if (mode === '+x') finalMode = 0o755; if (typeof mode === 'string' && !isNaN(Number.parseInt(mode, 8))) { finalMode = Number.parseInt(mode, 8); } fs.chmodSync(file, finalMode); }, cp(arg1, arg2, arg3) { const src = arg3 ? arg2 : arg1; const dest = arg3 ? arg3 : arg2; // --- GESTIONE WILDCARD (*) --- if (src.endsWith('*')) { const srcDir = path.dirname(src); if (!fs.existsSync(srcDir)) return; if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true }); const items = fs.readdirSync(srcDir); for (const item of items) { const s = path.join(srcDir, item); const d = path.join(dest, item); fs.cpSync(s, d, { force: true, recursive: true }); } return; } // ---------------------------- let finalDest = dest; if (fs.existsSync(dest) && fs.statSync(dest).isDirectory()) { finalDest = path.join(dest, path.basename(src)); } if (fs.existsSync(src)) { fs.cpSync(src, finalDest, { force: true, recursive: true }); } }, exec(command, options = {}) { const env = getCleanEnv(); const spawnOpts = { encoding: 'utf-8', env, shell: '/bin/bash', stdio: options.silent ? 'pipe' : 'inherit' }; // Usiamo nodeSpawnSync perché calcoliamo l'env qui sopra const result = nodeSpawnSync(command, [], spawnOpts); return { code: result.status ?? 1, stderr: result.stderr ? result.stderr.toString() : '', stdout: result.stdout ? result.stdout.toString() : '' }; }, ln(flag, target, link) { if (fs.existsSync(link) || fs.lstatSync(link, { throwIfNoEntry: false })) { fs.rmSync(link, { force: true }); } fs.symlinkSync(target, link); }, ls(arg1, arg2) { let options = ''; let paths = []; // Rilevamento argomenti: ls('-R', path) vs ls(path) if (typeof arg1 === 'string' && arg1.startsWith('-')) { options = arg1; // Se c'è arg2 lo usa, altrimenti default a '.' paths = arg2 ? (Array.isArray(arg2) ? arg2 : [arg2]) : ['.']; } else { // Nessuna opzione, arg1 sono i path. Se null, default a '.' paths = arg1 ? (Array.isArray(arg1) ? arg1 : [arg1]) : ['.']; } const recursive = options.includes('R'); let results = []; for (const p of paths) { if (!fs.existsSync(p)) continue; const stat = fs.statSync(p); if (stat.isDirectory()) { if (recursive) { // Funzione ricorsiva interna const walk = (dir) => { const files = fs.readdirSync(dir); for (const f of files) { const fullPath = path.join(dir, f); results.push(fullPath); // Aggiunge il path completo if (fs.statSync(fullPath).isDirectory()) { walk(fullPath); } } }; walk(p); } else { // Comportamento standard: ritorna solo i nomi dei file nella cartella results = results.concat(fs.readdirSync(p)); } } else { // È un file singolo results.push(p); } } return results; }, mkdir(arg1, arg2) { const dir = arg2 ? arg2 : arg1; fs.mkdirSync(dir, { recursive: true }); }, mv(src, dest) { if (!fs.existsSync(src)) return; fs.renameSync(src, dest); }, rm(arg1, arg2) { const target = arg2 ? arg2 : arg1; fs.rmSync(target, { force: true, recursive: true }); }, sed(flag, regex, replacement, file) { if (!fs.existsSync(file)) return; const content = fs.readFileSync(file, 'utf8'); const searchRegex = typeof regex === 'string' ? new RegExp(regex, 'g') : regex; const newContent = content.replace(searchRegex, replacement); fs.writeFileSync(file, newContent, 'utf8'); }, test(flag, pathToCheck) { try { const stats = fs.statSync(pathToCheck); if (flag === '-f') return stats.isFile(); if (flag === '-d') return stats.isDirectory(); return true; // -e } catch { return false; } }, touch(file) { const time = new Date(); try { fs.utimesSync(file, time, time); } catch { fs.closeSync(fs.openSync(file, 'w')); } }, which(cmd) { const result = shx.exec(`command -v ${cmd}`, { silent: true }); return result.code === 0 ? result.stdout.trim() : null; } }; /** * execSync */ export function execSync(command, options = {}) { const { echo = false, ignore = false, stdio } = options; if (echo) console.log(command); const isSilent = ignore || stdio === 'ignore'; const result = shx.exec(command, { silent: isSilent }); if (result.code !== 0) { throw new Error(`Command failed: ${command}\nExit Code: ${result.code}\nStderr: ${result.stderr}`); } return result.stdout.trim(); } /** * exec (Async) */ export async function exec(command, { capture = false, echo = false, ignore = false } = {}) { return new Promise((resolve, reject) => { if (echo) console.log(command); const env = getCleanEnv(); // Usiamo nodeSpawn direttamente qui per coerenza const child = nodeSpawn(command, [], { env, shell: '/bin/bash', stdio: ignore ? 'ignore' : capture ? 'pipe' : 'inherit' }); let stdout = ''; let stderr = ''; if (capture && child.stdout) child.stdout.on('data', (d) => (stdout += d.toString())); if (capture && child.stderr) child.stderr.on('data', (d) => (stderr += d.toString())); child.on('error', (error) => { reject({ code: 1, error, stderr }); }); child.on('close', (code) => { resolve({ code: code || 0, data: stdout.trim(), error: code === 0 ? undefined : stderr.trim() }); }); }); }