@serverless-devs/s-zip
Version:
Serverless Devs Tool ZIP Component
215 lines (185 loc) • 5.86 kB
JavaScript
const fs = require('fs-extra')
const archiver = require('archiver')
const path = require('path')
const ignore = require('ignore')
const readline = require('readline')
const processCwd = process.cwd()
const _ = require('lodash')
const isWindows = process.platform === 'win32'
async function packTo({
codeUri,
exclude,
include,
outputFileName = 'dome.zip',
outputFilePath = './.s/.cache'
}) {
await fs.ensureDir(outputFilePath)
if (!(await fs.pathExists(codeUri))) {
throw new Error(`CodeUri: ${codeUri} is not exist`)
}
const fsStat = await fs.stat(codeUri)
if (fsStat.isFile() && !(_.isEmpty(exclude) && _.isEmpty(include))) {
throw new Error(`${codeUri} is file, and Include/Exclude was not provided`)
}
const output = fs.createWriteStream(`${outputFilePath}/${outputFileName}`)
console.log('Packing ...')
const zipArchiver = archiver('zip', {
zlib: { level: 9 }
})
.on('warning', (err) => {
console.warn(err)
})
.on('error', (err) => {
throw err
})
zipArchiver.pipe(output)
let count = await zipTo(codeUri, zipArchiver, exclude)
if (include) {
for (const item of include) {
const c = await zipTo(item, zipArchiver)
count += c
}
}
return await new Promise((resolve, reject) => {
output.on('close', () => {
const compressedSize = zipArchiver.pointer()
console.log('Package complete.')
resolve({ count, compressedSize })
})
try {
zipArchiver.finalize()
} catch (err) {
reject(err)
}
})
}
async function zipTo(codeUri, zipArchiver, exclude) {
const asbFilePath = path.resolve(codeUri)
const fsStat = await fs.stat(codeUri)
// if (fsStat.isFile()) {
// const prefix = codeUri.split('.').pop();
// if (zipPrefix.includes(prefix)) {
// console.log(`File is ${zipPrefix.join('/')}`);
// return false;
// }
// }
let count
if (fsStat.isFile()) {
const isBootstrap = isBootstrapPath(asbFilePath, asbFilePath, true)
zipArchiver.file(asbFilePath, {
name: path.basename(codeUri),
mode: isBootstrap ? fsStat.mode | 73 : fsStat.mode
})
return 1
}
let funignore = null
if (exclude) {
funignore = await generateFunIngore(processCwd, path.join(processCwd, codeUri), exclude)
}
count = await zipFolder(zipArchiver, codeUri, [], funignore, codeUri, '')
return count
}
async function zipFolder(zipArchiver, folder, folders, funignore, codeUri, prefix = '') {
folders.push(folder)
const absCodeUri = path.resolve(codeUri)
const dir = path.join(...folders)
const dirItems = await fs.readdir(dir)
const absDir = path.resolve(dir)
const relative = path.relative(absCodeUri, absDir)
if (!_.isEmpty(relative)) {
zipArchiver.append(null, {
name: relative,
type: 'directory',
prefix
})
}
return (
await Promise.all(
dirItems.map(async (f) => {
const fPath = path.join(dir, f)
let s
try {
s = await fs.lstat(fPath)
} catch (error) {
console.log(
`Before zip: could not found fPath ${fPath}, absolute fPath is ${path.resolve(
fPath
)}, exception is ${error}, skiping`
)
return 0
}
if (funignore && funignore(fPath)) {
console.log('file %s is ignored.', fPath)
return 0
}
const absFilePath = path.resolve(fPath)
const relative = path.relative(absCodeUri, absFilePath)
const isBootstrap = isBootstrapPath(absFilePath, absCodeUri, false)
if (s.size === 1067) {
const content = await readLines(fPath)
if (_.head(content) === 'XSym' && content.length === 5) {
const target = content[3]
zipArchiver.symlink(relative, target, {
mode: isBootstrap || isWindows ? s.mode | 73 : s.mode
})
return 1
}
}
if (s.isFile() || s.isSymbolicLink()) {
zipArchiver.file(fPath, {
name: relative,
prefix,
mode: isBootstrap || isWindows ? s.mode | 73 : s.mode,
stats: s // The archiver uses fs.stat by default, and pasing the result of lstat to ensure that the symbolic link is properly packaged
})
return 1
} else if (s.isDirectory()) {
return await zipFolder(zipArchiver, f, folders.slice(), funignore, codeUri, prefix)
}
console.error(
`Ignore file ${absFilePath}, because it isn't a file, symbolic link or directory`
)
return 0
})
)
).reduce((sum, curr) => sum + curr, 0)
}
function readLines(fileName) {
return new Promise((resolve, reject) => {
const lines = []
readline
.createInterface({ input: fs.createReadStream(fileName) })
.on('line', (line) => lines.push(line))
.on('close', () => resolve(lines))
.on('error', reject)
})
}
function isBootstrapPath(absFilePath, absCodeUri, isFile = true) {
let absBootstrapDir
if (isFile) {
absBootstrapDir = path.dirname(absCodeUri)
} else {
absBootstrapDir = absCodeUri
}
return path.join(absBootstrapDir, 'bootstrap') === absFilePath
}
async function generateFunIngore(baseDir, codeUri, exclude) {
const absCodeUri = path.resolve(baseDir, codeUri)
const absBaseDir = path.resolve(baseDir)
const relative = path.relative(absBaseDir, absCodeUri)
if (codeUri.startsWith('..') || relative.startsWith('..')) {
console.warn(`\t\tFunignore is not supported for your CodeUri: ${codeUri}`)
return null
}
const ig = ignore().add(exclude)
return function(f) {
const relativePath = path.relative(baseDir, f)
if (relativePath === '') {
return false
}
return ig.ignores(relativePath)
}
}
module.exports = {
packTo
}