@sap/cds
Version:
SAP Cloud Application Programming Model - CDS for Node.js
107 lines (92 loc) • 3.73 kB
JavaScript
// This module monkey patches ./tar.js to work on Windows, where tar does not work properly w/o these changes.
exports = module.exports = require('./tar')
exports._spawn_tar_c = (dir, args) => {
args.push('.')
if (Array.isArray(args[0])) return winSpawnTempDir(dir, args)
else return winSpawnDir(dir, args)
}
exports._path = path => {
if (!path) return path
if (typeof path === 'string') return path.replace('C:', '//localhost/c$').replace(/\\+/g, '/')
if (Array.isArray(path)) return path.map(el => exports._path(el))
}
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, exists, rimraf } = cds.utils
const { PassThrough } = require('stream')
// spawn tar on Windows, using the cli version
const winSpawnDir = (dir, args) => {
if (args.some(arg => arg === '-f')) return spawn ('tar', ['c', '-C', exports._path(dir), ...exports._path(args)])
else return spawn ('tar', ['cf', '-', '-C', exports._path(dir), ...exports._path(args)])
}
// copy a directory recursively on Windows, using fs.promises
async function winCopyDir(src, dest) {
if ((await fs.promises.stat(src)).isDirectory()) {
const entries = await fs.promises.readdir(src)
return Promise.all(entries.map(async each => winCopyDir(path.join(src, each), path.join(dest, each))))
} else {
await fs.promises.mkdir(path.dirname(dest), { recursive: true })
return fs.promises.copyFile(src, dest)
}
}
// copy resources containing files and folders to temp dir on Windows
// cli tar has a size limit on Windows
const winCreateTemp = async (root, resources) => {
// Asynchronously copies the entire content from src to dest.
const temp = await fs.promises.mkdtemp(`${fs.realpathSync(require('os').tmpdir())}${path.sep}tar-`)
for (let resource of resources) {
const destination = path.join(temp, path.relative(root, resource))
if ((await fs.promises.stat(resource)).isFile()) {
const dirName = path.dirname(destination)
if (!await exists(dirName)) {
await fs.promises.mkdir(dirName, { recursive: true })
}
await fs.promises.copyFile(resource, destination)
} else {
if (fs.promises.cp) {
await fs.promises.cp(resource, destination, { recursive: true })
} else {
// node < 16
await winCopyDir(resource, destination)
}
}
}
return temp
}
// spawn tar on Windows, using a temp dir, which is copied from the original dir
// cli tar has a size limit on Windows
const winSpawnTempDir = (dir, args) => {
// Synchronous trick: use a PassThrough as placeholder
const stdout = new PassThrough()
const stderr = new PassThrough()
const c = {
stdout,
stderr,
on: (...a) => { stdout.on(...a); stderr.on(...a); return c },
once: (...a) => { stdout.once(...a); stderr.once(...a); return c },
kill: () => {},
}
// async copy, then swap streams/events
winCreateTemp(dir, args.shift()).then(tempPath => {
const real = winSpawnDir(tempPath, args)
real.stdout.pipe(stdout)
real.stderr && real.stderr.pipe(stderr)
const cleanup = () => exists(tempPath) && rimraf(tempPath)
real.on('close', (...ev) => {
stdout.emit('close', ...ev)
stderr.emit('close', ...ev)
cleanup()
})
real.on('error', (...ev) => {
stdout.emit('error', ...ev)
stderr.emit('error', ...ev)
cleanup()
})
c.kill = (...ev) => real.kill(...ev)
})
return c
}