UNPKG

@sap/cds-dk

Version:

Command line client and development toolkit for the SAP Cloud Application Programming Model

125 lines (99 loc) 4.02 kB
const cp = require('child_process'); const cds = require('../cds'); const LOG = cds.log('cli'), DEBUG = cds.debug('cli'); const { IS_WIN } = require('../init/constants'); class CommandUtil { /** * Execute a command and show output in console * spawn has problems with promisify so use native approach w/ Promise * @param cmd command * @param args command args * @param options spawn options * @param collectResult if true, return complete console output as promise resolve value * this might result in a huge string! * * @returns Promise */ async spawnCommand(cmd, args, options = {}, collectResult = false, showOutput = true) { args = args.map(arg => arg.replace(/[&;|]/g, '')); // sanitize args return new Promise((resolve, reject) => { DEBUG?.(`[spawn] ${cmd} ${args.join(' ')}`); options.env = options.env || process.env; const child = this._getChildProcess(cmd, args, options); // use inplace methods to ensure logger is called in context const stdLogBuffer = new LogBuffer(showOutput || LOG._debug ? (chunk) => console.log(chunk) : undefined, collectResult); child.stdout.on('data', (chunk) => { stdLogBuffer.handleChunk(chunk); }); // use inplace methods to ensure logger is called in context // collect error log in case of Win to handle command not found (ENOENT), const errLogBuffer = new LogBuffer(showOutput || LOG._debug ? (chunk) => console.error(chunk) : undefined, (IS_WIN || collectResult)); child.stderr.on('data', (chunk) => { errLogBuffer.handleChunk(chunk); }); child.on('close', (code) => { // dump buffer stdLogBuffer.flush(); errLogBuffer.flush(); if (code === 0) { resolve(stdLogBuffer.getResult()); } else { const errorLog = errLogBuffer.getResult(); const error = new Error(`${cmd} ${args.join(' ')} failed with code ${code}, ${errorLog}`) // add code ENOENT on Win if command not found if (IS_WIN && errorLog.includes(cmd) && /not recognized/i.exec(errorLog)) { error.code = 'ENOENT'; error.path = cmd; } error.errLog = errorLog; error.stdLog = stdLogBuffer.getResult(); reject(error); } }); child.on('error', (err) => { reject(err); }); }); } _getChildProcess(cmd, args, options) { if (!IS_WIN) { return cp.spawn(cmd, args, options); } // always use cmd shell on Win since Win does not report ENOENT // we need to parse output which might differ from PS to cmd shell return cp.spawn('cmd', ['/s', '/c', cmd, ...args], { shell: true, // for windows only ...options }); } } class LogBuffer { constructor(logMethod, collectResult) { this.logMethod = logMethod; this.collectResult = collectResult; this.result = ''; this.buffer = ''; } handleChunk(chunk) { const chunkStr = chunk.toString(); if (this.collectResult) { this.result = this.result + chunkStr; } const seq = chunkStr.split(/\r?\n/); // more robust than os.EOL this.buffer = this.buffer + seq.shift(); for (const line of seq) { this.flush(); this.buffer = line; } } flush() { if (this.logMethod && this.buffer) { this.logMethod(this.buffer); this.buffer = ''; } } getResult() { return this.result.trim(); } } module.exports = new CommandUtil();