UNPKG

zapier-platform-cli

Version:

The CLI for managing integrations in Zapier Developer Platform.

263 lines (229 loc) 6.9 kB
const crypto = require('node:crypto'); const fs = require('node:fs'); const os = require('node:os'); const path = require('node:path'); const _ = require('lodash'); const colors = require('colors/safe'); const fse = require('fs-extra'); const fixHome = (dir) => { const home = process.env.HOME || process.env.USERPROFILE; return dir.replace(/^~/, home); }; const fileExistsSync = (fileName) => { try { fse.accessSync(fileName); return true; } catch (e) { return false; } }; const validateFileExists = (fileName, errMsg) => { return fse.access(fileName).catch(() => { let msg = `: File ${fileName} not found.`; if (errMsg) { msg += ` ${errMsg}`; } throw new Error(msg); }); }; // Returns a promise that reads a file and returns a buffer. const readFile = (fileName, errMsg) => { return validateFileExists(fileName, errMsg).then(() => fse.readFile(fixHome(fileName)), ); }; const readFileStr = async (fileName, errMsg) => { const buf = await readFile(fileName, errMsg); return buf.toString(); }; // Returns a promise that writes a file. const writeFile = (fileName, data) => { if (!data) { throw new Error('No data provided'); } return fse.writeFile(fixHome(fileName), data); }; // deletes a file, eats the error const deleteFile = (path) => { try { fse.unlinkSync(path); return true; } catch (err) { return false; } }; // Returns a promise that ensures a directory exists. const ensureDir = (dir) => fse.ensureDir(dir); const copyFile = (src, dest, mode) => { return new Promise((resolve, reject) => { const readStream = fse.createReadStream(src); const writeStream = fse.createWriteStream(dest, { mode }); readStream.on('error', reject); writeStream.on('error', reject); writeStream.on('open', function () { readStream.pipe(writeStream); }); writeStream.once('finish', (err) => { if (err) { reject(err); } resolve(); }); }); }; /* Returns a promise that copies a directory recursively. Options: - clobber: Overwrite existing files? Default is false. - filter: A function that returns true if the file should be copied. By default, it ignores node_modules and .zip files. - onCopy: A function called when a file is copied. Takes the destination path as an argument. - onSkip: A function called when a file is skipped. Takes the destination path as an argument. - onDirExists: A function called when a directory exists. Takes the destination path as an argument. Returns true to carry on copying. Returns false to skip. */ const copyDir = async (src, dst, options) => { const defaultFilter = (srcPath) => { const isntPackage = !srcPath.includes('node_modules'); const isntBuild = !srcPath.endsWith('.zip'); return isntPackage && isntBuild; }; options = { clobber: false, filter: defaultFilter, onCopy: () => {}, onSkip: () => {}, onDirExists: () => true, ...options, }; if (!options.filter) { options.filter = defaultFilter; } await ensureDir(dst); const files = await fse.readdirSync(src); const promises = files.map(async (file) => { const srcItem = path.resolve(src, file); let srcStat; try { srcStat = fse.statSync(srcItem); } catch (err) { // If the file is a symlink and the target doesn't exist, skip it. if (fse.lstatSync(srcItem).isSymbolicLink()) { console.warn( colors.yellow( `\n! Warning: symlink "${srcItem}" points to a non-existent file. Skipping!\n`, ), ); return null; } // otherwise, rethrow the error throw err; } const srcIsFile = srcStat.isFile(); const dstItem = path.resolve(dst, file); const dstExists = fileExistsSync(dstItem); if (!options.filter(srcItem)) { return null; } if (srcIsFile) { if (dstExists) { if (!options.clobber) { options.onSkip(dstItem); return null; } fse.removeSync(dstItem); } await copyFile(srcItem, dstItem, srcStat.mode); options.onCopy(dstItem); } else { let shouldCopyRecursively = true; if (dstExists) { shouldCopyRecursively = options.onDirExists(dstItem); } if (shouldCopyRecursively) { await copyDir(srcItem, dstItem, options); } } }); return Promise.all(promises); }; // Delete a directory. const removeDir = (dir) => fse.remove(dir); const removeDirSync = (dir) => fse.removeSync(dir); // Returns true if directory is empty, else false. // Rejects if directory does not exist. const isEmptyDir = (dir) => fse.readdir(dir).then((items) => _.isEmpty(items)); const isExistingEmptyDir = async (dir) => fse.existsSync(dir) && !(await isEmptyDir(dir)); const makeTempDir = () => { let workdir; const tmpBaseDir = os.tmpdir(); while (!workdir || fse.existsSync(workdir)) { workdir = path.join(tmpBaseDir, crypto.randomBytes(20).toString('hex')); } fse.mkdirSync(workdir); return workdir; }; // Iterates files and symlinks in a directory recursively. // Yields fs.Dirent objects. function* walkDir(dir) { const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { if (entry.isDirectory()) { const subDir = path.join(dir, entry.name); yield* walkDir(subDir); } else if (entry.isFile() || entry.isSymbolicLink()) { if (!entry.parentPath) { // dirent.parentPath is only available since Node.js 18.20, 20.12, and // 21.4. Re-assigning it so the caller can use dirent.parentPath. entry.parentPath = dir; } yield entry; } } } // Iterates files and symlinks in a directory recursively, up to a specified // number of levels deep (maxLevels). The minimum value for maxLevels is 1. // Yields fs.Dirent objects. function* walkDirLimitedLevels(dir, maxLevels, currentLevel = 1) { const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { if (entry.isDirectory()) { if (currentLevel < maxLevels) { const subDir = path.join(dir, entry.name); yield* walkDirLimitedLevels(subDir, maxLevels, currentLevel + 1); } } else if (entry.isFile() || entry.isSymbolicLink()) { if (!entry.parentPath) { // dirent.parentPath is only available since Node.js 18.20, 20.12, and // 21.4. Re-assigning it so the caller can use dirent.parentPath. entry.parentPath = dir; } yield entry; } } } module.exports = { copyDir, copyFile, deleteFile, ensureDir, fileExistsSync, isEmptyDir, isExistingEmptyDir, makeTempDir, readFile, readFileStr, removeDir, removeDirSync, validateFileExists, walkDir, walkDirLimitedLevels, writeFile, };