UNPKG

@sap/cds

Version:

SAP Cloud Application Programming Model - CDS for Node.js

181 lines (161 loc) 6.99 kB
const child_process = require('child_process') const spawn = /\btar\b/.test(process.env.DEBUG) ? (cmd, args, options) => { Error.captureStackTrace(spawn,spawn) process.stderr.write(cmd +' ', args.join(' ') +' '+ spawn.stack.slice(7) + '\n') return child_process.spawn(cmd, args, options) } : child_process.spawn const cds = require('../index'), { fs, path, mkdirp } = cds.utils const _resolve = (...x) => path.resolve (cds.root,...x) /** * Creates a tar archive, to an in-memory Buffer, or piped to write stream or file. * @example ```js * const buffer = await tar.c('src/dir') * await tar.c('src/dir') .to (fs.createWriteStream('t.tar')) * await tar.c('src/dir') .to ('t.tar') * await tar.c('src/dir','-f t.tar *') * ``` * @param {string} dir - the directory to archive, used as `cwd` for the tar process * @param {string} [args] - additional arguments passed to tar (default: `'*'`) * @param {string[]} [more] - more of such additional arguments like `args` * @example ```js * // Passing additional arguments to tar * tar.c('src/dir','-v *') * tar.c('src/dir','-v -f t.tar *') * tar.c('src/dir','-vf','t.tar','*') * tar.c('src/dir','-vf','t.tar','file1','file2') * ``` * @returns A `ChildProcess` as returned by [`child_process.spawn()`]( * https://nodejs.org/api/child_process.html#child_processspawncommand-args-options), * augmented by two methods: * - `.then()` collects the tar output into an in-memory `Buffer` * - `.to()` is a convenient shortcut to pipe the output into a write stream */ exports.create = (dir='.', ...args) => { if (typeof dir === 'string') dir = _resolve(dir) if (Array.isArray(dir)) [ dir, ...args ] = [ cds.root, dir, ...args ] args = args.filter(el => el) const c = exports._spawn_tar_c (dir, args) return {__proto__:c, // returning a thenable + fluent ChildProcess... /** * Turns the returned `ChildProcess` into a thenable, resolving to an * in-memory Buffer holding the tar output, hence enabling this usage: * @example const buffer = await tar.c('src/dir') */ then (resolve, reject) { let data=[], stderr='' c.stdout.on('data', d => data.push(d)) c.stderr.on('data', d => stderr += d) c.on('close', code => code ? reject(new Error(stderr)) : resolve(Buffer.concat(data))) c.on('error', reject) }, /** * Turns the returned `ChildProcess` into fluent API, allowing to pipe * the tar's `stdout` into a write stream. If the argument is a string, * it will be interpreted as a filename and a write stream opened on it. * In that case, more filenames can be specified which are path.joined. */ to (out, ...etc) { if (typeof out === 'string') { // fs.mkdirSync(path.dirname(out),{recursive:true}) out = fs.createWriteStream (_resolve(out,...etc)) } // Returning a thenable ChildProcess.stdout return {__proto__: c.stdout.pipe (out), then (resolve, reject) { out.on('close', code => code ? reject(code) : resolve()) c.on('error', reject) } } } } } // Extracted to allow os-specific implementations, e.g. for win32 exports._spawn_tar_c = (dir, args) => { if (Array.isArray(args[0])) { args.push (...args.shift().map (f => path.isAbsolute(f) ? path.relative(dir,f) : f)) } else { args.push ('.') } return spawn ('tar', ['c', '-C', dir, ...args], { env: { COPYFILE_DISABLE: 1 }}) } // Extracted to allow os-specific implementations, e.g. for win32 exports._path = p => p /** * Extracts a tar archive, from an in-memory Buffer, or piped from a read stream or file. * @example ```js * await tar.x(buffer) .to ('dest') * await tar.x(fs.createReadStream('t.tar')) .to ('dest') * await tar.x('t.tar') .to ('dest') * await tar.x('t.tar','-C dest') * ``` * @param {String|Buffer|ReadableStream} [archive] - the tar file or content to extract * @param {String[]} [args] - additional arguments passed to tar, .e.g. '-C dest' */ exports.extract = (archive, ...args) => ({ /** * Fluent API method to actually start the tar x command. * @param {...string} dest - path names to a target dir → get `path.resolved` from `cds.root`. * @returns A `ChildProcess` as returned by [`child_process.spawn()`]( * https://nodejs.org/api/child_process.html#child_processspawncommand-args-options), * augmented by a method `.then()` to allow `await`ing finish. */ to (...dest) { if (typeof dest === 'string') dest = _resolve(...dest) const input = typeof archive !== 'string' || archive == '-' ? '-' : _resolve(archive) const x = spawn('tar', ['xf', exports._path(input), '-C', exports._path(dest), ...args]) if (archive === '-') return x.stdin if (Buffer.isBuffer(archive)) archive = require('stream').Readable.from (archive) if (typeof archive !== 'string') (archive.stdout || archive) .pipe (x.stdin) let stdout='', stderr='' x.stdout.on ('data', d => stdout += d) x.stderr.on ('data', d => stderr += d) return {__proto__:x, then (resolve, reject) { x.on('close', err => { if (err) return reject (new Error(stderr)) if (process.platform === 'linux') stdout = stderr resolve (stdout ? stdout.split('\n').slice(0,-1).map(x => x.replace(/^x |\r/g,'')): undefined) }) x.on('error', reject) } } }, /** * Shortcut to extract to current working directory, i.e. `cds.root`, * or for this kind of usage: * @example await tar.x(...,'-C _out') * @returns `stdin` of the tar child process */ then (r,e) { return this.to('.') .then(r,e) }, }) exports.list = (archive, ...more) => { const input = typeof archive !== 'string' ? '-' : archive === '-' ? archive : _resolve(archive) const x = spawn('tar', ['tf', input, ...more ]) let stdout='', stderr='' x.stdout.on ('data', d => stdout += d) x.stderr.on ('data', d => stderr += d) return {__proto__:x, then (resolve, reject) { x.on('close', code => code ? reject(new Error(stderr)) : resolve(stdout.split('\n').slice(0,-1))) x.on('error', reject) } } } // Common tar command shortcuts const tar = exports exports.c = tar.create exports.cz = (d,...args) => tar.c (d, ...args, '-z') exports.cf = (t,d,...args) => tar.c (d, ...args, '-f',t) exports.czf = (t,d,...args) => tar.c (d, ...args, '-z', '-f',t) exports.czfd = (t,...args) => mkdirp(path.dirname(t)).then (()=> tar.czf (t,...args)) exports.x = tar.xf = tar.extract exports.xz = tar.xzf = (a,...args) => tar.x (a, ...args, '-z') exports.xv = tar.xvf = (a,...args) => tar.x (a, ...args, '-v') exports.xvz = tar.xvzf = (a,...args) => tar.x (a, ...args, '-v', '-z') exports.t = tar.tf = tar.list /** * Shortcut for that kind of usage: * @example fs.createReadStream('t.tar') .pipe (tar.x.to('dest/dir')) * @returns `stdin` of the tar child process */ exports.extract.to = function (..._) { return this('-').to(..._) }