From ec5aeea0e8933032204bd0e1317cdb25ea8784b5 Mon Sep 17 00:00:00 2001 From: Chang-Hung Liang Date: Tue, 30 Jan 2024 20:59:00 +0800 Subject: [PATCH 01/15] fix(cli): `zapier build` fails with npm workspaces --- packages/cli/src/utils/build.js | 32 +++++++++++---- packages/cli/src/utils/files.js | 73 +++++++++++++++++---------------- packages/cli/src/utils/misc.js | 49 ++++++++++++++-------- 3 files changed, 96 insertions(+), 58 deletions(-) diff --git a/packages/cli/src/utils/build.js b/packages/cli/src/utils/build.js index 08e7adacf..ebc92e180 100644 --- a/packages/cli/src/utils/build.js +++ b/packages/cli/src/utils/build.js @@ -45,7 +45,7 @@ const { const checkMissingAppInfo = require('./check-missing-app-info'); -const { runCommand, isWindows } = require('./misc'); +const { runCommand, isWindows, findCorePackageDir } = require('./misc'); const debug = require('debug')('zapier:build'); @@ -332,6 +332,7 @@ const _buildFunc = async ({ osTmpDir, 'zapier-' + crypto.randomBytes(4).toString('hex') ); + debug('Using temp directory: ', tmpDir); maybeNotifyAboutOutdated(); @@ -342,9 +343,28 @@ const _buildFunc = async ({ await ensureDir(constants.BUILD_DIR); startSpinner('Copying project to temp directory'); - await copyDir(wdir, tmpDir, { - filter: skipNpmInstall ? (dir) => !dir.includes('.zip') : undefined, - }); + + const copyFilter = skipNpmInstall + ? (src) => !src.includes('.zip') + : undefined; + + await copyDir(wdir, tmpDir, { filter: copyFilter }); + + if (skipNpmInstall) { + const corePackageDir = findCorePackageDir(); + const nodeModulesDir = path.dirname(corePackageDir); + const workspaceDir = path.dirname(nodeModulesDir); + if (wdir !== workspaceDir) { + // If we're in here, it means the user is using npm/yarn workspaces + await copyDir(nodeModulesDir, path.join(tmpDir, 'node_modules'), { + filter: copyFilter, + onDirExists: (dir) => { + // Don't overwrite existing sub-directories in node_modules + return false; + }, + }); + } + } let output = {}; if (!skipNpmInstall) { @@ -412,7 +432,7 @@ const _buildFunc = async ({ * (Remote - `validateApp`) Both the Schema, AppVersion, and Auths are validated */ - startSpinner('Validating project schema'); + startSpinner('Validating project schema and style'); const validateResponse = await _appCommandZapierWrapper(tmpDir, { command: 'validate', }); @@ -425,8 +445,6 @@ const _buildFunc = async ({ ); } - startSpinner('Validating project style'); - // No need to mention specifically we're validating style checks as that's // implied from `zapier validate`, though it happens as a separate process const styleChecksResponse = await validateApp(rawDefinition); diff --git a/packages/cli/src/utils/files.js b/packages/cli/src/utils/files.js index c29fd341c..d3016352f 100644 --- a/packages/cli/src/utils/files.js +++ b/packages/cli/src/utils/files.js @@ -84,10 +84,10 @@ const copyFile = (src, dest, mode) => { }; // Returns a promise that copies a directory. -const copyDir = (src, dst, options) => { - const defaultFilter = (dir) => { - const isntPackage = dir.indexOf('node_modules') === -1; - const isntBuild = dir.indexOf('.zip') === -1; +const copyDir = async (src, dst, options) => { + const defaultFilter = (srcPath) => { + const isntPackage = !srcPath.includes('node_modules'); + const isntBuild = !srcPath.endsWith('.zip'); return isntPackage && isntBuild; }; @@ -96,43 +96,46 @@ const copyDir = (src, dst, options) => { filter: defaultFilter, onCopy: () => {}, onSkip: () => {}, + onDirExists: () => true, }); - return ensureDir(dst) - .then(() => fse.readdir(src)) - .then((files) => { - const promises = files.map((file) => { - const srcItem = path.resolve(src, file); - const dstItem = path.resolve(dst, file); - const stat = fse.statSync(srcItem); - const dstExists = fileExistsSync(dstItem); - - if (!options.filter(srcItem)) { - return Promise.resolve(); - } + await ensureDir(dst); + const files = await fse.readdirSync(src); - if (dstExists && options.clobber) { - fse.removeSync(dstItem); - } else if (dstExists) { - if (!stat.isDirectory()) { - options.onSkip(dstItem); - return Promise.resolve(); - } - } + const promises = files.map(async (file) => { + const srcItem = path.resolve(src, file); + const dstItem = path.resolve(dst, file); + const stat = fse.statSync(srcItem); + const isFile = stat.isFile(); + const dstExists = fileExistsSync(dstItem); + + if (!options.filter(srcItem)) { + return null; + } - if (stat.isDirectory()) { - return ensureDir(dstItem).then(() => - copyDir(srcItem, dstItem, options) - ); - } else { - return copyFile(srcItem, dstItem, stat.mode).then(() => { - options.onCopy(dstItem); - }); + if (isFile) { + if (dstExists) { + if (!options.clobber) { + options.onSkip(dstItem); + return null; } - }); + fse.removeSync(dstItem); + } - return Promise.all(promises); - }); + await copyFile(srcItem, dstItem, stat.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. diff --git a/packages/cli/src/utils/misc.js b/packages/cli/src/utils/misc.js index 5f576aee6..951e98441 100644 --- a/packages/cli/src/utils/misc.js +++ b/packages/cli/src/utils/misc.js @@ -77,6 +77,22 @@ const runCommand = (command, args, options) => { const isValidNodeVersion = (version = process.version) => semver.satisfies(version, NODE_VERSION_CLI_REQUIRES); +const findCorePackageDir = () => { + let baseDir = process.cwd(); + // 500 is just an arbitrary number to prevent infinite loops + for (let i = 0; i < 500; i++) { + const dir = path.join(baseDir, 'node_modules', PLATFORM_PACKAGE); + if (fse.existsSync(dir)) { + return dir; + } + if (baseDir === '/' || baseDir.match(/^[a-z]:\\$/i)) { + break; + } + baseDir = path.dirname(baseDir); + } + throw new Error(`Could not find ${PLATFORM_PACKAGE}.`); +}; + const isValidAppInstall = () => { let packageJson, dependedCoreVersion; try { @@ -91,7 +107,7 @@ const isValidAppInstall = () => { valid: false, reason: `Your app doesn't depend on ${PLATFORM_PACKAGE}. Run \`${colors.cyan( `npm install -E ${PLATFORM_PACKAGE}` - )}\` to resolve`, + )}\` to resolve.`, }; } else if (!semver.valid(dependedCoreVersion)) { // semver.valid only matches exact versions @@ -104,30 +120,30 @@ const isValidAppInstall = () => { return { valid: false, reason: String(err) }; } + let corePackageDir; try { - const installedPackageJson = require(path.join( - process.cwd(), - 'node_modules', - PLATFORM_PACKAGE, - 'package.json' - )); - - const installedCoreVersion = installedPackageJson.version; - // not an error for now, but something to mention to them - if (dependedCoreVersion !== installedCoreVersion) { - console.warn( - `\nYour code depends on v${dependedCoreVersion} of ${PLATFORM_PACKAGE}, but your local copy is v${installedCoreVersion}. You should probably reinstall your dependencies.\n` - ); - } + corePackageDir = findCorePackageDir(); } catch (err) { return { valid: false, reason: `Looks like you're missing a local installation of ${PLATFORM_PACKAGE}. Run \`${colors.cyan( 'npm install' - )}\` to resolve`, + )}\` to resolve.`, }; } + const installedPackageJson = require(path.join( + corePackageDir, + 'package.json' + )); + const installedCoreVersion = installedPackageJson.version; + + if (installedCoreVersion !== dependedCoreVersion) { + console.warn( + `\nYour code depends on v${dependedCoreVersion} of ${PLATFORM_PACKAGE}, but your local copy is v${installedCoreVersion}. You should probably reinstall your dependencies.\n` + ); + } + return { valid: true }; }; @@ -214,6 +230,7 @@ const printVersionInfo = (context) => { module.exports = { camelCase, entryPoint, + findCorePackageDir, isValidAppInstall, isValidNodeVersion, isWindows, From 05ac7b759a80909a8cc661451a224f52f9dbbc1e Mon Sep 17 00:00:00 2001 From: Chang-Hung Liang Date: Wed, 31 Jan 2024 15:50:12 +0800 Subject: [PATCH 02/15] Add tests for `zapier build` in workspaces --- packages/cli/package.json | 1 + packages/cli/src/tests/utils/build.js | 189 +++++++++++++++++++++++++- packages/cli/src/tests/utils/files.js | 155 +++++++++++++++++---- packages/cli/src/utils/build.js | 61 ++++++--- packages/cli/src/utils/files.js | 31 ++++- 5 files changed, 393 insertions(+), 44 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 903c02d00..a90bba7a4 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -32,6 +32,7 @@ "lint": "eslint src snippets", "lint:fix": "eslint --fix src snippets", "test": "cross-env NODE_ENV=test mocha -t 50s --recursive src/tests --exit", + "test-debug": "cross-env NODE_ENV=test node inspect ../../node_modules/.bin/mocha -t 50s --recursive src/tests --exit", "smoke-test": "cross-env NODE_ENV=test mocha -t 2m --recursive src/smoke-tests --exit", "validate-templates": "./scripts/validate-app-templates.js", "set-template-versions": "./scripts/set-app-template-versions.js", diff --git a/packages/cli/src/tests/utils/build.js b/packages/cli/src/tests/utils/build.js index 13ed751fb..df18951e6 100644 --- a/packages/cli/src/tests/utils/build.js +++ b/packages/cli/src/tests/utils/build.js @@ -33,7 +33,9 @@ describe('build (runs slowly)', function () { runCommand('npm', ['i'], { cwd: tmpDir }); // TODO: This test depends on how "typescript" example is set up, which // isn't good. Should refactor not to rely on that. - runCommand('npm', ['run', 'build', '--scripts-prepend-node-path'], { cwd: tmpDir }); + runCommand('npm', ['run', 'build', '--scripts-prepend-node-path'], { + cwd: tmpDir, + }); entryPoint = path.resolve(tmpDir, 'index.js'); }); @@ -309,3 +311,188 @@ describe('build (runs slowly)', function () { should.equal(buildExists, true); }); }); + +describe('build in workspaces', function () { + let tmpDir, origCwd; + + before(async () => { + tmpDir = getNewTempDirPath(); + + // Set up a monorepo project structure with two integrations as npm + // workspaces: + // + // packages/ + // ├─ package.json + // ├─ app-1/ + // │ ├─ index.js + // │ └─ package.json + // └─ app-2/ + // ├─ index.js + // └─ package.json + + // Create root package.json + fs.outputFileSync( + path.join(tmpDir, 'package.json'), + JSON.stringify({ + name: 'my-monorepo', + workspaces: ['packages/*'], + private: true, + }) + ); + + const defaultIndexJs = `module.exports = { + version: require('./package.json').version, + platformVersion: require('zapier-platform-core').version, +};`; + + // First integration: app-1 + fs.outputFileSync( + path.join(tmpDir, 'packages', 'app-1', 'index.js'), + defaultIndexJs + ); + fs.outputFileSync( + path.join(tmpDir, 'packages', 'app-1', 'package.json'), + JSON.stringify({ + name: 'app-1', + version: '1.0.0', + main: 'index.js', + dependencies: { + uuid: '8.3.2', + 'zapier-platform-core': '15.5.1', + }, + private: true, + }) + ); + + // Second integration: app-2 + fs.outputFileSync( + path.join(tmpDir, 'packages', 'app-2', 'index.js'), + defaultIndexJs + ); + fs.outputFileSync( + path.join(tmpDir, 'packages', 'app-2', 'package.json'), + JSON.stringify({ + name: 'app-2', + version: '1.0.0', + main: 'index.js', + dependencies: { + uuid: '9.0.1', + 'zapier-platform-core': '15.5.1', + }, + private: true, + }) + ); + + runCommand('yarn', ['install'], { cwd: tmpDir }); + }); + + after(() => { + fs.removeSync(tmpDir); + }); + + beforeEach(() => { + origCwd = process.cwd(); + }); + + afterEach(() => { + process.chdir(origCwd); + }); + + it('should build in app-1', async () => { + const workspaceDir = path.join(tmpDir, 'packages', 'app-1'); + const zipPath = path.join(workspaceDir, 'build', 'build.zip'); + const unzipPath = path.join(tmpDir, 'build', 'build'); + + // Make sure the zapier-platform-core dependency is installed in the root + // project directory + fs.existsSync( + path.join(tmpDir, 'node_modules', 'zapier-platform-core') + ).should.be.true(); + fs.existsSync( + path.join(workspaceDir, 'node_modules', 'zapier-platform-core') + ).should.be.false(); + + fs.ensureDirSync(path.dirname(zipPath)); + + process.chdir(workspaceDir); + + await build.buildAndOrUpload( + { build: true, upload: false }, + { + skipNpmInstall: true, + skipValidation: true, + printProgress: false, + checkOutdated: false, + } + ); + await decompress(zipPath, unzipPath); + + const corePackageJson = JSON.parse( + fs.readFileSync( + path.join( + unzipPath, + 'node_modules', + 'zapier-platform-core', + 'package.json' + ) + ) + ); + corePackageJson.version.should.equal('15.5.1'); + + const uuidPackageJson = JSON.parse( + fs.readFileSync( + path.join(unzipPath, 'node_modules', 'uuid', 'package.json') + ) + ); + uuidPackageJson.version.should.equal('8.3.2'); + }); + + it('should build in app-2', async () => { + const workspaceDir = path.join(tmpDir, 'packages', 'app-2'); + const zipPath = path.join(workspaceDir, 'build', 'build.zip'); + const unzipPath = path.join(tmpDir, 'build', 'build'); + + // Make sure the zapier-platform-core dependency is installed in the root + // project directory + fs.existsSync( + path.join(tmpDir, 'node_modules', 'zapier-platform-core') + ).should.be.true(); + fs.existsSync( + path.join(workspaceDir, 'node_modules', 'zapier-platform-core') + ).should.be.false(); + + fs.ensureDirSync(path.dirname(zipPath)); + + process.chdir(workspaceDir); + + await build.buildAndOrUpload( + { build: true, upload: false }, + { + skipNpmInstall: true, + skipValidation: true, + printProgress: false, + checkOutdated: false, + } + ); + await decompress(zipPath, unzipPath); + + const corePackageJson = JSON.parse( + fs.readFileSync( + path.join( + unzipPath, + 'node_modules', + 'zapier-platform-core', + 'package.json' + ) + ) + ); + corePackageJson.version.should.equal('15.5.1'); + + const uuidPackageJson = JSON.parse( + fs.readFileSync( + path.join(unzipPath, 'node_modules', 'uuid', 'package.json') + ) + ); + uuidPackageJson.version.should.equal('9.0.1'); + }); +}); diff --git a/packages/cli/src/tests/utils/files.js b/packages/cli/src/tests/utils/files.js index 3a4973521..19c48179b 100644 --- a/packages/cli/src/tests/utils/files.js +++ b/packages/cli/src/tests/utils/files.js @@ -1,8 +1,7 @@ -require('should'); -const files = require('../../utils/files'); - -const path = require('path'); const os = require('os'); +const path = require('path'); +const should = require('should'); +const files = require('../../utils/files'); describe('files', () => { let tmpDir; @@ -38,27 +37,137 @@ describe('files', () => { .catch(done); }); - // TODO: this is broken in travis - works locally though - it.skip('should copy a directory', (done) => { - const srcDir = os.tmpdir(); - const srcFileName = path.resolve(srcDir, 'read-write-test.txt'); - const dstDir = path.resolve(srcDir, 'zapier-platform-cli-test-dest-dir'); - const dstFileName = path.resolve(dstDir, 'read-write-test.txt'); - const data = '123'; + describe('copyDir', () => { + let tmpDir, srcDir, dstDir; - files - .writeFile(srcFileName, data) - .then(files.copyDir(srcDir, dstDir)) - .then(() => - files.readFile(dstFileName).then((buf) => { - buf.toString().should.equal(data); - done(); - }) - ) - .catch((err) => { - console.log('error', err); - done(err); + beforeEach(async () => { + tmpDir = path.join(os.tmpdir(), 'zapier-platform-cli-copyDir-test'); + srcDir = path.join(tmpDir, 'src'); + dstDir = path.join(tmpDir, 'dst'); + + await files.removeDir(srcDir); + await files.ensureDir(srcDir); + await files.writeFile(path.join(srcDir, '01.txt'), 'chapter 1'); + await files.writeFile(path.join(srcDir, '02.txt'), 'chapter 2'); + await files.ensureDir(path.join(srcDir, '03')); + await files.writeFile(path.join(srcDir, '03', '03.txt'), 'chapter 3'); + await files.writeFile(path.join(srcDir, '03', 'cover.jpg'), 'image data'); + await files.writeFile(path.join(srcDir, '03', 'photo.jpg'), 'photo data'); + + await files.removeDir(dstDir); + await files.ensureDir(dstDir); + await files.writeFile(path.join(dstDir, '01.txt'), 'ch 1'); + await files.writeFile(path.join(dstDir, '02.txt'), 'ch 2'); + await files.ensureDir(path.join(dstDir, '03')); + await files.writeFile(path.join(dstDir, '03', '03.txt'), 'ch 3'); + await files.writeFile(path.join(dstDir, '03', 'cover.jpg'), 'old data'); + await files.writeFile(path.join(dstDir, '03', 'fig.png'), 'png data'); + }); + + afterEach(async () => { + await files.removeDir(tmpDir); + }); + + it('should copy a directory without clobber', async () => { + // clobber defaults to false + await files.copyDir(srcDir, dstDir); + should.equal( + await files.readFileStr(path.join(dstDir, '01.txt')), + 'ch 1' + ); + should.equal( + await files.readFileStr(path.join(dstDir, '03', '03.txt')), + 'ch 3' + ); + should.equal( + await files.readFileStr(path.join(dstDir, '03', 'fig.png')), + 'png data' + ); + should.equal( + await files.readFileStr(path.join(dstDir, '03', 'photo.jpg')), + 'photo data' + ); + }); + + it('should copy a directory with clobber', async () => { + await files.copyDir(srcDir, dstDir, { clobber: true }); + should.equal( + await files.readFileStr(path.join(dstDir, '02.txt')), + 'chapter 2' + ); + should.equal( + await files.readFileStr(path.join(dstDir, '03', '03.txt')), + 'chapter 3' + ); + should.equal( + await files.readFileStr(path.join(dstDir, '03', 'cover.jpg')), + 'image data' + ); + should.equal( + await files.readFileStr(path.join(dstDir, '03', 'fig.png')), + 'png data' + ); + should.equal( + await files.readFileStr(path.join(dstDir, '03', 'photo.jpg')), + 'photo data' + ); + }); + + it('should copy a directory with onDirExists returning false', async () => { + await files.copyDir(srcDir, dstDir, { + clobber: true, + onDirExists: () => false, }); + should.equal( + await files.readFileStr(path.join(dstDir, '02.txt')), + 'chapter 2' + ); + should.equal( + await files.readFileStr(path.join(dstDir, '03', '03.txt')), + 'ch 3' + ); + should.equal( + await files.readFileStr(path.join(dstDir, '03', 'cover.jpg')), + 'old data' + ); + should.equal( + await files.readFileStr(path.join(dstDir, '03', 'fig.png')), + 'png data' + ); + files + .fileExistsSync(path.join(dstDir, '03', 'photo.jpg')) + .should.be.false(); + }); + + it('should copy a directory with onDirExists deleting dir', async () => { + await files.copyDir(srcDir, dstDir, { + clobber: true, + onDirExists: (dir) => { + // Delete existing directory => completely overwrite the directory + files.removeDirSync(dir); + return true; + }, + }); + should.equal( + await files.readFileStr(path.join(dstDir, '01.txt')), + 'chapter 1' + ); + should.equal( + await files.readFileStr(path.join(dstDir, '03', '03.txt')), + 'chapter 3' + ); + should.equal( + await files.readFileStr(path.join(dstDir, '03', 'cover.jpg')), + 'image data' + ); + should.equal( + await files.readFileStr(path.join(dstDir, '03', 'photo.jpg')), + 'photo data' + ); + files + .fileExistsSync(path.join(dstDir, '03', 'fig.png')) + .should.be.false(); + }); }); describe('validateFileExists', () => { diff --git a/packages/cli/src/utils/build.js b/packages/cli/src/utils/build.js index ebc92e180..ecfbfe351 100644 --- a/packages/cli/src/utils/build.js +++ b/packages/cli/src/utils/build.js @@ -322,6 +322,8 @@ const _buildFunc = async ({ skipNpmInstall = false, disableDependencyDetection = false, skipValidation = false, + printProgress = true, + checkOutdated = true, } = {}) => { const zipPath = constants.BUILD_PATH; const sourceZipPath = constants.SOURCE_PATH; @@ -334,7 +336,9 @@ const _buildFunc = async ({ ); debug('Using temp directory: ', tmpDir); - maybeNotifyAboutOutdated(); + if (checkOutdated) { + maybeNotifyAboutOutdated(); + } await maybeRunBuildScript(); @@ -342,7 +346,9 @@ const _buildFunc = async ({ await ensureDir(tmpDir); await ensureDir(constants.BUILD_DIR); - startSpinner('Copying project to temp directory'); + if (printProgress) { + startSpinner('Copying project to temp directory'); + } const copyFilter = skipNpmInstall ? (src) => !src.includes('.zip') @@ -368,8 +374,10 @@ const _buildFunc = async ({ let output = {}; if (!skipNpmInstall) { - endSpinner(); - startSpinner('Installing project dependencies'); + if (printProgress) { + endSpinner(); + startSpinner('Installing project dependencies'); + } output = await runCommand('npm', ['install', '--production'], { cwd: tmpDir, }); @@ -386,9 +394,12 @@ const _buildFunc = async ({ 'Could not install dependencies properly. Error log:\n' + output.stderr ); } - endSpinner(); - startSpinner('Applying entry point file'); + if (printProgress) { + endSpinner(); + startSpinner('Applying entry point file'); + } + // TODO: should this routine for include exist elsewhere? const zapierWrapperBuf = await readFile( path.join( @@ -403,9 +414,7 @@ const _buildFunc = async ({ path.join(tmpDir, 'zapierwrapper.js'), zapierWrapperBuf.toString() ); - endSpinner(); - startSpinner('Building app definition.json'); const rawDefinition = ( await _appCommandZapierWrapper(tmpDir, { command: 'definition', @@ -423,7 +432,10 @@ const _buildFunc = async ({ `Unable to write ${tmpDir}/definition.json, please check file permissions!` ); } - endSpinner(); + + if (printProgress) { + endSpinner(); + } if (!skipValidation) { /** @@ -432,7 +444,9 @@ const _buildFunc = async ({ * (Remote - `validateApp`) Both the Schema, AppVersion, and Auths are validated */ - startSpinner('Validating project schema and style'); + if (printProgress) { + startSpinner('Validating project schema and style'); + } const validateResponse = await _appCommandZapierWrapper(tmpDir, { command: 'validate', }); @@ -459,7 +473,9 @@ const _buildFunc = async ({ 'We hit some style validation errors, try running `zapier validate` to see them!' ); } - endSpinner(); + if (printProgress) { + endSpinner(); + } if (_.get(styleChecksResponse, ['warnings', 'total_failures'])) { console.log(colors.yellow('WARNINGS:')); @@ -475,16 +491,21 @@ const _buildFunc = async ({ debug('\nWarning: Skipping Validation'); } - startSpinner('Zipping project and dependencies'); + if (printProgress) { + startSpinner('Zipping project and dependencies'); + } await makeZip(tmpDir, path.join(wdir, zipPath), disableDependencyDetection); await makeSourceZip( tmpDir, path.join(wdir, sourceZipPath), disableDependencyDetection ); - endSpinner(); - startSpinner('Testing build'); + if (printProgress) { + endSpinner(); + startSpinner('Testing build'); + } + if (!isWindows()) { // TODO err, what should we do on windows? @@ -497,11 +518,17 @@ const _buildFunc = async ({ { cwd: tmpDir } ); } - endSpinner(); - startSpinner('Cleaning up temp directory'); + if (printProgress) { + endSpinner(); + startSpinner('Cleaning up temp directory'); + } + await removeDir(tmpDir); - endSpinner(); + + if (printProgress) { + endSpinner(); + } return zipPath; }; diff --git a/packages/cli/src/utils/files.js b/packages/cli/src/utils/files.js index d3016352f..b749feb53 100644 --- a/packages/cli/src/utils/files.js +++ b/packages/cli/src/utils/files.js @@ -83,7 +83,25 @@ const copyFile = (src, dest, mode) => { }); }; -// Returns a promise that copies a directory. +/* + 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'); @@ -91,13 +109,18 @@ const copyDir = async (src, dst, options) => { return isntPackage && isntBuild; }; - options = _.defaults(options || {}, { + 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); @@ -140,6 +163,7 @@ const copyDir = async (src, dst, options) => { // 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. @@ -167,6 +191,7 @@ module.exports = { readFile, readFileStr, removeDir, + removeDirSync, validateFileExists, writeFile, copyFile, From 13973980e2cf7c28fa81a3b057b424f1bd459768 Mon Sep 17 00:00:00 2001 From: Chang-Hung Liang Date: Wed, 31 Jan 2024 16:01:32 +0800 Subject: [PATCH 03/15] Fix box drawing tree --- packages/cli/src/tests/utils/build.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/cli/src/tests/utils/build.js b/packages/cli/src/tests/utils/build.js index df18951e6..3d1cd0310 100644 --- a/packages/cli/src/tests/utils/build.js +++ b/packages/cli/src/tests/utils/build.js @@ -321,14 +321,15 @@ describe('build in workspaces', function () { // Set up a monorepo project structure with two integrations as npm // workspaces: // - // packages/ + // (project root) // ├─ package.json - // ├─ app-1/ - // │ ├─ index.js - // │ └─ package.json - // └─ app-2/ - // ├─ index.js - // └─ package.json + // └── packages/ + // ├─ app-1/ + // │ ├─ index.js + // │ └─ package.json + // └─ app-2/ + // ├─ index.js + // └─ package.json // Create root package.json fs.outputFileSync( From 6cfae02c81f8c12b708437e462e401060ff7a7f0 Mon Sep 17 00:00:00 2001 From: Chang-Hung Liang Date: Wed, 31 Jan 2024 19:40:30 +0800 Subject: [PATCH 04/15] Make `zapier validate` support workspaces too --- packages/cli/src/utils/local.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/utils/local.js b/packages/cli/src/utils/local.js index da8c6ffb6..7010fd7eb 100644 --- a/packages/cli/src/utils/local.js +++ b/packages/cli/src/utils/local.js @@ -16,7 +16,7 @@ const getLocalAppHandler = ({ reload = false, baseEvent = {} } = {}) => { let appRaw, zapier; try { appRaw = require(entryPath); - zapier = require(`${rootPath}/node_modules/${PLATFORM_PACKAGE}`); + zapier = require(PLATFORM_PACKAGE); } catch (err) { // this err.stack doesn't give a nice traceback at all :-( // maybe we could do require('syntax-error') in the future From 54051e49a0afd9531d06d0b90e4a7f763c3b2dfc Mon Sep 17 00:00:00 2001 From: marinahand Date: Wed, 31 Jan 2024 13:31:22 +0000 Subject: [PATCH 05/15] hydration_cache_documentation --- docs/index.html | 1 + packages/cli/README-source.md | 1 + packages/cli/README.md | 1 + packages/cli/docs/index.html | 1 + 4 files changed, 4 insertions(+) diff --git a/docs/index.html b/docs/index.html index 30eb2ad02..b13a7352c 100644 --- a/docs/index.html +++ b/docs/index.html @@ -4175,6 +4175,7 @@

Dehydration

The method z.dehydrate(func, inputData) has two required arguments:

  • func - the function to call to fetch the extra data. Can be any raw function, defined in the file doing the dehydration or imported from another part of your app. You must also register the function in the app's hydrators property. Note that since v10.1.0, the maximum payload size to pass to z.dehydrate / z.dehydrateFile is 6KB.
  • inputData - this is an object that contains things like a path or id - whatever you need to load data on the other side
  • +
  • A known limitation of hydration is a 5 minute cache if the hydration call is made with identical inputData within that timeframe. To workaround this cache for records triggering hydration in close succession, include a unique value in the inputData, for example a timestamp in addition to the record id.

Why do I need to register my functions? Because of how JavaScript works with its module system, we need an explicit handle on the function that can be accessed from the App definition without trying to "automagically" (and sometimes incorrectly) infer code locations.

Here is an example that pulls in extra data for a movie:

diff --git a/packages/cli/README-source.md b/packages/cli/README-source.md index 6c5ab867d..0774d3e24 100644 --- a/packages/cli/README-source.md +++ b/packages/cli/README-source.md @@ -1421,6 +1421,7 @@ The method `z.dehydrate(func, inputData)` has two required arguments: * `func` - the function to call to fetch the extra data. Can be any raw `function`, defined in the file doing the dehydration or imported from another part of your app. You must also register the function in the app's `hydrators` property. Note that since v10.1.0, the maximum payload size to pass to `z.dehydrate` / `z.dehydrateFile` is 6KB. * `inputData` - this is an object that contains things like a `path` or `id` - whatever you need to load data on the other side +* A known limitation of hydration is a 5 minute cache if the hydration call is made with identical `inputData` within that timeframe. To workaround this cache for records triggering hydration in close succession, include a unique value in the `inputData`, for example a `timestamp` in addition to the record `id`. > **Why do I need to register my functions?** Because of how JavaScript works with its module system, we need an explicit handle on the function that can be accessed from the App definition without trying to "automagically" (and sometimes incorrectly) infer code locations. diff --git a/packages/cli/README.md b/packages/cli/README.md index db3e89a4a..3fc10e970 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -2573,6 +2573,7 @@ The method `z.dehydrate(func, inputData)` has two required arguments: * `func` - the function to call to fetch the extra data. Can be any raw `function`, defined in the file doing the dehydration or imported from another part of your app. You must also register the function in the app's `hydrators` property. Note that since v10.1.0, the maximum payload size to pass to `z.dehydrate` / `z.dehydrateFile` is 6KB. * `inputData` - this is an object that contains things like a `path` or `id` - whatever you need to load data on the other side +* A known limitation of hydration is a 5 minute cache if the hydration call is made with identical `inputData` within that timeframe. To workaround this cache for records triggering hydration in close succession, include a unique value in the `inputData`, for example a `timestamp` in addition to the record `id`. > **Why do I need to register my functions?** Because of how JavaScript works with its module system, we need an explicit handle on the function that can be accessed from the App definition without trying to "automagically" (and sometimes incorrectly) infer code locations. diff --git a/packages/cli/docs/index.html b/packages/cli/docs/index.html index 30eb2ad02..b13a7352c 100644 --- a/packages/cli/docs/index.html +++ b/packages/cli/docs/index.html @@ -4175,6 +4175,7 @@

Dehydration

The method z.dehydrate(func, inputData) has two required arguments:

  • func - the function to call to fetch the extra data. Can be any raw function, defined in the file doing the dehydration or imported from another part of your app. You must also register the function in the app's hydrators property. Note that since v10.1.0, the maximum payload size to pass to z.dehydrate / z.dehydrateFile is 6KB.
  • inputData - this is an object that contains things like a path or id - whatever you need to load data on the other side
  • +
  • A known limitation of hydration is a 5 minute cache if the hydration call is made with identical inputData within that timeframe. To workaround this cache for records triggering hydration in close succession, include a unique value in the inputData, for example a timestamp in addition to the record id.

Why do I need to register my functions? Because of how JavaScript works with its module system, we need an explicit handle on the function that can be accessed from the App definition without trying to "automagically" (and sometimes incorrectly) infer code locations.

Here is an example that pulls in extra data for a movie:

From 4ff1ba9a13ddba4097da71a108606d7bfba30ea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Negr=C3=B3n?= Date: Mon, 29 Jan 2024 17:02:12 -0400 Subject: [PATCH 06/15] check gitignore before copying to tmpDir --- docs/cli.html | 2 +- docs/cli.md | 2 +- packages/cli/docs/cli.html | 2 +- packages/cli/docs/cli.md | 2 +- packages/cli/src/oclif/commands/build.js | 2 +- packages/cli/src/utils/build.js | 24 ++++++++++++++++++++++++ packages/cli/src/utils/files.js | 2 +- 7 files changed, 30 insertions(+), 6 deletions(-) diff --git a/docs/cli.html b/docs/cli.html index 5573e7415..18bec1537 100644 --- a/docs/cli.html +++ b/docs/cli.html @@ -345,7 +345,7 @@

build

Usage: zapier build

This command does the following:

  • Creates a temporary folder

  • -
  • Copies all code into the temporary folder

    +
  • Copies all code (excluding files in .gitignore) into the temporary folder

  • Adds an entry point: zapierwrapper.js

  • diff --git a/docs/cli.md b/docs/cli.md index 434fafd2f..5388fe535 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -34,7 +34,7 @@ This command does the following: * Creates a temporary folder -* Copies all code into the temporary folder +* Copies all code (excluding files in .gitignore) into the temporary folder * Adds an entry point: `zapierwrapper.js` diff --git a/packages/cli/docs/cli.html b/packages/cli/docs/cli.html index 5573e7415..18bec1537 100644 --- a/packages/cli/docs/cli.html +++ b/packages/cli/docs/cli.html @@ -345,7 +345,7 @@

    build

    Usage: zapier build

    This command does the following:

    • Creates a temporary folder

    • -
    • Copies all code into the temporary folder

      +
    • Copies all code (excluding files in .gitignore) into the temporary folder

    • Adds an entry point: zapierwrapper.js

    • diff --git a/packages/cli/docs/cli.md b/packages/cli/docs/cli.md index 434fafd2f..5388fe535 100644 --- a/packages/cli/docs/cli.md +++ b/packages/cli/docs/cli.md @@ -34,7 +34,7 @@ This command does the following: * Creates a temporary folder -* Copies all code into the temporary folder +* Copies all code (excluding files in .gitignore) into the temporary folder * Adds an entry point: `zapierwrapper.js` diff --git a/packages/cli/src/oclif/commands/build.js b/packages/cli/src/oclif/commands/build.js index 1fbbf0e0e..a43bc8c65 100644 --- a/packages/cli/src/oclif/commands/build.js +++ b/packages/cli/src/oclif/commands/build.js @@ -48,7 +48,7 @@ BuildCommand.description = `Build a pushable zip from the current directory. This command does the following: * Creates a temporary folder -* Copies all code into the temporary folder +* Copies all code (excluding files in .gitignore) into the temporary folder * Adds an entry point: \`zapierwrapper.js\` * Generates and validates app definition. * Detects dependencies via browserify (optional, on by default) diff --git a/packages/cli/src/utils/build.js b/packages/cli/src/utils/build.js index ecfbfe351..2b4bedffe 100644 --- a/packages/cli/src/utils/build.js +++ b/packages/cli/src/utils/build.js @@ -318,6 +318,29 @@ const maybeRunBuildScript = async (options = {}) => { } }; +const buildCopyDirFilter = ({ wdir, skipNpmInstall = false }) => { + // read and parse .gitignore + const gitIgnorePath = path.join(wdir, '.gitignore'); + const gitIgnoreFilter = ignore(); + + // create an ignore filter from .gitignore, if it exists + if (fs.existsSync(gitIgnorePath)) { + const gitIgnoredPaths = gitIgnore(gitIgnorePath); + const validGitIgnorePaths = gitIgnoredPaths.filter(ignore.isPathValid); + gitIgnoreFilter.add(validGitIgnorePaths); + } + + return (file) => { + // exclude any files defined in .gitignore + if (gitIgnoreFilter.ignores(path.relative(wdir, file))) { + return false; + } + + // exclude '.zip' files only if skipNpmInstall is true + return !(skipNpmInstall && file.includes('.zip')); + }; +}; + const _buildFunc = async ({ skipNpmInstall = false, disableDependencyDetection = false, @@ -563,4 +586,5 @@ module.exports = { listFiles, requiredFiles, maybeRunBuildScript, + buildCopyDirFilter, }; diff --git a/packages/cli/src/utils/files.js b/packages/cli/src/utils/files.js index b749feb53..997f89798 100644 --- a/packages/cli/src/utils/files.js +++ b/packages/cli/src/utils/files.js @@ -87,7 +87,7 @@ const copyFile = (src, dest, mode) => { 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 From dff25dd726d6ea0a9b54904ec6aa91f5556df07e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Negr=C3=B3n?= Date: Wed, 31 Jan 2024 14:45:33 -0400 Subject: [PATCH 07/15] Revert "check gitignore before copying to tmpDir" This reverts commit 4ff1ba9a13ddba4097da71a108606d7bfba30ea4. --- docs/cli.html | 2 +- docs/cli.md | 2 +- packages/cli/docs/cli.html | 2 +- packages/cli/docs/cli.md | 2 +- packages/cli/src/oclif/commands/build.js | 2 +- packages/cli/src/utils/build.js | 24 ------------------------ packages/cli/src/utils/files.js | 2 +- 7 files changed, 6 insertions(+), 30 deletions(-) diff --git a/docs/cli.html b/docs/cli.html index 18bec1537..5573e7415 100644 --- a/docs/cli.html +++ b/docs/cli.html @@ -345,7 +345,7 @@

      build

      Usage: zapier build

      This command does the following:

      • Creates a temporary folder

      • -
      • Copies all code (excluding files in .gitignore) into the temporary folder

        +
      • Copies all code into the temporary folder

      • Adds an entry point: zapierwrapper.js

      • diff --git a/docs/cli.md b/docs/cli.md index 5388fe535..434fafd2f 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -34,7 +34,7 @@ This command does the following: * Creates a temporary folder -* Copies all code (excluding files in .gitignore) into the temporary folder +* Copies all code into the temporary folder * Adds an entry point: `zapierwrapper.js` diff --git a/packages/cli/docs/cli.html b/packages/cli/docs/cli.html index 18bec1537..5573e7415 100644 --- a/packages/cli/docs/cli.html +++ b/packages/cli/docs/cli.html @@ -345,7 +345,7 @@

        build

        Usage: zapier build

        This command does the following:

        • Creates a temporary folder

        • -
        • Copies all code (excluding files in .gitignore) into the temporary folder

          +
        • Copies all code into the temporary folder

        • Adds an entry point: zapierwrapper.js

        • diff --git a/packages/cli/docs/cli.md b/packages/cli/docs/cli.md index 5388fe535..434fafd2f 100644 --- a/packages/cli/docs/cli.md +++ b/packages/cli/docs/cli.md @@ -34,7 +34,7 @@ This command does the following: * Creates a temporary folder -* Copies all code (excluding files in .gitignore) into the temporary folder +* Copies all code into the temporary folder * Adds an entry point: `zapierwrapper.js` diff --git a/packages/cli/src/oclif/commands/build.js b/packages/cli/src/oclif/commands/build.js index a43bc8c65..1fbbf0e0e 100644 --- a/packages/cli/src/oclif/commands/build.js +++ b/packages/cli/src/oclif/commands/build.js @@ -48,7 +48,7 @@ BuildCommand.description = `Build a pushable zip from the current directory. This command does the following: * Creates a temporary folder -* Copies all code (excluding files in .gitignore) into the temporary folder +* Copies all code into the temporary folder * Adds an entry point: \`zapierwrapper.js\` * Generates and validates app definition. * Detects dependencies via browserify (optional, on by default) diff --git a/packages/cli/src/utils/build.js b/packages/cli/src/utils/build.js index 2b4bedffe..ecfbfe351 100644 --- a/packages/cli/src/utils/build.js +++ b/packages/cli/src/utils/build.js @@ -318,29 +318,6 @@ const maybeRunBuildScript = async (options = {}) => { } }; -const buildCopyDirFilter = ({ wdir, skipNpmInstall = false }) => { - // read and parse .gitignore - const gitIgnorePath = path.join(wdir, '.gitignore'); - const gitIgnoreFilter = ignore(); - - // create an ignore filter from .gitignore, if it exists - if (fs.existsSync(gitIgnorePath)) { - const gitIgnoredPaths = gitIgnore(gitIgnorePath); - const validGitIgnorePaths = gitIgnoredPaths.filter(ignore.isPathValid); - gitIgnoreFilter.add(validGitIgnorePaths); - } - - return (file) => { - // exclude any files defined in .gitignore - if (gitIgnoreFilter.ignores(path.relative(wdir, file))) { - return false; - } - - // exclude '.zip' files only if skipNpmInstall is true - return !(skipNpmInstall && file.includes('.zip')); - }; -}; - const _buildFunc = async ({ skipNpmInstall = false, disableDependencyDetection = false, @@ -586,5 +563,4 @@ module.exports = { listFiles, requiredFiles, maybeRunBuildScript, - buildCopyDirFilter, }; diff --git a/packages/cli/src/utils/files.js b/packages/cli/src/utils/files.js index 997f89798..b749feb53 100644 --- a/packages/cli/src/utils/files.js +++ b/packages/cli/src/utils/files.js @@ -87,7 +87,7 @@ const copyFile = (src, dest, mode) => { 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 From c1a68fe962ef005a67e39af307257cfa986f258c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Negr=C3=B3n?= Date: Wed, 31 Jan 2024 16:29:24 -0400 Subject: [PATCH 08/15] rebase and use warning logic in copyDir --- packages/cli/src/utils/files.js | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/utils/files.js b/packages/cli/src/utils/files.js index b749feb53..e897390ff 100644 --- a/packages/cli/src/utils/files.js +++ b/packages/cli/src/utils/files.js @@ -4,6 +4,7 @@ const os = require('os'); const path = require('path'); const fse = require('fs-extra'); +const colors = require('colors/safe'); const fixHome = (dir) => { const home = process.env.HOME || process.env.USERPROFILE; @@ -87,7 +88,7 @@ const copyFile = (src, dest, mode) => { 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 @@ -127,16 +128,27 @@ const copyDir = async (src, dst, options) => { const promises = files.map(async (file) => { const srcItem = path.resolve(src, file); + const srcStat = fse.lstatSync(srcItem); + const srcIsFile = srcStat.isFile(); + const srcIsSymbolicLink = srcStat.isSymbolicLink(); + const dstItem = path.resolve(dst, file); - const stat = fse.statSync(srcItem); - const isFile = stat.isFile(); const dstExists = fileExistsSync(dstItem); - if (!options.filter(srcItem)) { return null; } - if (isFile) { + if (srcIsFile || srcIsSymbolicLink) { + if (srcIsSymbolicLink && !fileExistsSync(srcItem)) { + console.warn( + colors.yellow( + `\n! Warning: symlink "${srcItem}" points to a non-existent file. Skipping!\n` + ) + ); + options.onSkip(dstItem); + return null; + } + if (dstExists) { if (!options.clobber) { options.onSkip(dstItem); @@ -145,7 +157,7 @@ const copyDir = async (src, dst, options) => { fse.removeSync(dstItem); } - await copyFile(srcItem, dstItem, stat.mode); + await copyFile(srcItem, dstItem, srcStat.mode); options.onCopy(dstItem); } else { let shouldCopyRecursively = true; From 7968c495a12c3c1af1922a8418f51efeca3bc20e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Negr=C3=B3n?= Date: Wed, 31 Jan 2024 18:25:39 -0400 Subject: [PATCH 09/15] simplify logic to: if stat fails, check if symlink and notify if so --- packages/cli/src/utils/files.js | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/packages/cli/src/utils/files.js b/packages/cli/src/utils/files.js index e897390ff..afd9daa0d 100644 --- a/packages/cli/src/utils/files.js +++ b/packages/cli/src/utils/files.js @@ -128,27 +128,34 @@ const copyDir = async (src, dst, options) => { const promises = files.map(async (file) => { const srcItem = path.resolve(src, file); - const srcStat = fse.lstatSync(srcItem); - const srcIsFile = srcStat.isFile(); - const srcIsSymbolicLink = srcStat.isSymbolicLink(); - const dstItem = path.resolve(dst, file); - const dstExists = fileExistsSync(dstItem); - if (!options.filter(srcItem)) { - return null; - } - - if (srcIsFile || srcIsSymbolicLink) { - if (srcIsSymbolicLink && !fileExistsSync(srcItem)) { + 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` ) ); - options.onSkip(dstItem); 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); From 52fba09b195add7cd30fa7ed19efeb8775fbf31c Mon Sep 17 00:00:00 2001 From: Chang-Hung Liang Date: Thu, 1 Feb 2024 14:05:18 +0800 Subject: [PATCH 10/15] Add startSpinner back --- packages/cli/src/utils/build.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/cli/src/utils/build.js b/packages/cli/src/utils/build.js index ecfbfe351..ad55b6430 100644 --- a/packages/cli/src/utils/build.js +++ b/packages/cli/src/utils/build.js @@ -415,6 +415,11 @@ const _buildFunc = async ({ zapierWrapperBuf.toString() ); + if (printProgress) { + endSpinner(); + startSpinner('Building app definition.json'); + } + const rawDefinition = ( await _appCommandZapierWrapper(tmpDir, { command: 'definition', From a826667040f43bec5eeedc86ce6c368959a11e4f Mon Sep 17 00:00:00 2001 From: Chang-Hung Liang Date: Thu, 1 Feb 2024 14:08:04 +0800 Subject: [PATCH 11/15] Use spaces instead tabs --- packages/cli/src/utils/files.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/cli/src/utils/files.js b/packages/cli/src/utils/files.js index b749feb53..83cbc4dec 100644 --- a/packages/cli/src/utils/files.js +++ b/packages/cli/src/utils/files.js @@ -86,21 +86,21 @@ const copyFile = (src, dest, mode) => { /* 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. + 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) => { From dacbf6dae631ff88b2bf12c6c4d7d298114b7d13 Mon Sep 17 00:00:00 2001 From: Chang-Hung Liang Date: Thu, 1 Feb 2024 19:56:42 +0800 Subject: [PATCH 12/15] Exclude workspace symlinks from build.zip --- packages/cli/package.json | 1 + packages/cli/src/utils/build.js | 46 ++++++++++++++++++++++++++++++--- yarn.lock | 7 +++++ 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index a90bba7a4..64bd777a1 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -60,6 +60,7 @@ "lodash": "4.17.21", "marked": "4.2.12", "marked-terminal": "5.2.0", + "minimatch": "9.0.3", "node-fetch": "2.6.7", "ora": "5.4.0", "parse-gitignore": "0.5.1", diff --git a/packages/cli/src/utils/build.js b/packages/cli/src/utils/build.js index ad55b6430..fccb01f7a 100644 --- a/packages/cli/src/utils/build.js +++ b/packages/cli/src/utils/build.js @@ -14,6 +14,7 @@ const colors = require('colors/safe'); const ignore = require('ignore'); const gitIgnore = require('parse-gitignore'); const semver = require('semver'); +const { minimatch } = require('minimatch'); const { constants: { Z_BEST_COMPRESSION }, @@ -318,6 +319,24 @@ const maybeRunBuildScript = async (options = {}) => { } }; +const listWorkspaces = (workspaceRoot) => { + const packageJsonPath = path.join(workspaceRoot, 'package.json'); + if (!fs.existsSync(packageJsonPath)) { + return []; + } + + let packageJson; + try { + packageJson = require(packageJsonPath); + } catch (err) { + return []; + } + + return (packageJson.workspaces || []).map((relpath) => + path.join(workspaceRoot, relpath) + ); +}; + const _buildFunc = async ({ skipNpmInstall = false, disableDependencyDetection = false, @@ -351,7 +370,7 @@ const _buildFunc = async ({ } const copyFilter = skipNpmInstall - ? (src) => !src.includes('.zip') + ? (src) => !src.endsWith('.zip') : undefined; await copyDir(wdir, tmpDir, { filter: copyFilter }); @@ -359,11 +378,30 @@ const _buildFunc = async ({ if (skipNpmInstall) { const corePackageDir = findCorePackageDir(); const nodeModulesDir = path.dirname(corePackageDir); - const workspaceDir = path.dirname(nodeModulesDir); - if (wdir !== workspaceDir) { + const workspaceRoot = path.dirname(nodeModulesDir); + if (wdir !== workspaceRoot) { // If we're in here, it means the user is using npm/yarn workspaces + const workspaces = listWorkspaces(workspaceRoot); + await copyDir(nodeModulesDir, path.join(tmpDir, 'node_modules'), { - filter: copyFilter, + filter: (src) => { + if (src.endsWith('.zip')) { + return false; + } + const stat = fse.lstatSync(src); + if (stat.isSymbolicLink()) { + const realPath = path.resolve( + path.dirname(src), + fse.readlinkSync(src) + ); + for (const workspace of workspaces) { + if (minimatch(realPath, workspace)) { + return false; + } + } + } + return true; + }, onDirExists: (dir) => { // Don't overwrite existing sub-directories in node_modules return false; diff --git a/yarn.lock b/yarn.lock index 9cfb3e85d..5a081c2ac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8129,6 +8129,13 @@ minimatch@5.0.1: dependencies: brace-expansion "^2.0.1" +minimatch@9.0.3: + version "9.0.3" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" + integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== + dependencies: + brace-expansion "^2.0.1" + minimatch@^5.0.1: version "5.1.6" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" From d5f1c45de8033fa36659c7074b57330a7c8f3c99 Mon Sep 17 00:00:00 2001 From: Chang-Hung Liang Date: Thu, 1 Feb 2024 19:59:15 +0800 Subject: [PATCH 13/15] Add test code --- packages/cli/src/tests/utils/build.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/cli/src/tests/utils/build.js b/packages/cli/src/tests/utils/build.js index 3d1cd0310..7718a4a09 100644 --- a/packages/cli/src/tests/utils/build.js +++ b/packages/cli/src/tests/utils/build.js @@ -446,6 +446,15 @@ describe('build in workspaces', function () { ) ); uuidPackageJson.version.should.equal('8.3.2'); + + // Make sure node_modules/app-1 and node_modules/app-2 are not included + // in the build + fs.existsSync( + path.join(unzipPath, 'node_modules', 'app-1') + ).should.be.false(); + fs.existsSync( + path.join(unzipPath, 'node_modules', 'app-2') + ).should.be.false(); }); it('should build in app-2', async () => { @@ -495,5 +504,14 @@ describe('build in workspaces', function () { ) ); uuidPackageJson.version.should.equal('9.0.1'); + + // Make sure node_modules/app-1 and node_modules/app-2 are not included + // in the build + fs.existsSync( + path.join(unzipPath, 'node_modules', 'app-1') + ).should.be.false(); + fs.existsSync( + path.join(unzipPath, 'node_modules', 'app-2') + ).should.be.false(); }); }); From 18abd6301fbb81bb4f3d7f8a777894bbd963c9da Mon Sep 17 00:00:00 2001 From: Mikkel Bonde Wiuff Date: Thu, 1 Feb 2024 20:53:02 +0100 Subject: [PATCH 14/15] docs(examples): Avoid a warning from zapier cli when using the custom-auth sample (#724) * Avoid a warning from zapier cli when using the sample --- example-apps/custom-auth/authentication.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example-apps/custom-auth/authentication.js b/example-apps/custom-auth/authentication.js index 6944af1ce..8f271680f 100644 --- a/example-apps/custom-auth/authentication.js +++ b/example-apps/custom-auth/authentication.js @@ -47,7 +47,7 @@ module.exports = { // Define any input app's auth requires here. The user will be prompted to enter // this info when they connect their account. - fields: [{ key: 'apiKey', label: 'API Key', required: true }], + fields: [{ key: 'apiKey', label: 'API Key', required: true, helpText: 'Find the API Key in your [app settings page](https://yourapp.com/settings)' }], // The test method allows Zapier to verify that the credentials a user provides // are valid. We'll execute this method whenever a user connects their account for From b64e43f773a9b64e7e07a652dc83da0e413ce75f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Negr=C3=B3n?= Date: Fri, 2 Feb 2024 09:15:14 -0400 Subject: [PATCH 15/15] [PDE-4682] chore(cli): Add @clif/core dependency, upgrade @oclif/plugin-help (#739) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add @clif/core and upgrade @oclif/plugin-help * another spot that uses stdtermwidth * remove renderList options --------- Co-authored-by: Raúl Negrón --- packages/cli/package.json | 3 +- packages/cli/src/oclif/ZapierBaseCommand.js | 5 +- .../cli/src/oclif/hooks/renderMarkdownHelp.js | 2 +- yarn.lock | 98 +++++++++++++++---- 4 files changed, 86 insertions(+), 22 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 64bd777a1..b70d43c49 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -41,8 +41,9 @@ "dependencies": { "@oclif/command": "1.8.27", "@oclif/config": "1.18.10", + "@oclif/core": "1.26.0", "@oclif/plugin-autocomplete": "0.3.0", - "@oclif/plugin-help": "3.2.2", + "@oclif/plugin-help": "3.2.20", "@oclif/plugin-not-found": "1.2.4", "adm-zip": "0.5.10", "archiver": "5.3.1", diff --git a/packages/cli/src/oclif/ZapierBaseCommand.js b/packages/cli/src/oclif/ZapierBaseCommand.js index 6f6dbed48..fab3a954a 100644 --- a/packages/cli/src/oclif/ZapierBaseCommand.js +++ b/packages/cli/src/oclif/ZapierBaseCommand.js @@ -1,6 +1,5 @@ const { Command } = require('@oclif/command'); -const { stdtermwidth } = require('@oclif/plugin-help/lib/screen'); -const { renderList } = require('@oclif/plugin-help/lib/list'); +const { renderList } = require('@oclif/core/lib/cli-ux/list'); const colors = require('colors/safe'); const { startSpinner, endSpinner, formatStyles } = require('../utils/display'); @@ -150,7 +149,7 @@ class ZapierBaseCommand extends Command { * @param {string[][]} items */ logList(items) { - this.log(renderList(items, { spacer: '\n', maxWidth: stdtermwidth })); + this.log(renderList(items)); } /** diff --git a/packages/cli/src/oclif/hooks/renderMarkdownHelp.js b/packages/cli/src/oclif/hooks/renderMarkdownHelp.js index 73fb58b4f..f05d02494 100644 --- a/packages/cli/src/oclif/hooks/renderMarkdownHelp.js +++ b/packages/cli/src/oclif/hooks/renderMarkdownHelp.js @@ -1,7 +1,7 @@ const chalk = require('chalk'); const { marked } = require('marked'); const TerminalRenderer = require('marked-terminal'); -const { stdtermwidth } = require('@oclif/plugin-help/lib/screen'); +const { stdtermwidth } = require('@oclif/core/lib/screen'); marked.setOptions({ renderer: new TerminalRenderer({ diff --git a/yarn.lock b/yarn.lock index 86dbf0668..9861e0e85 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1714,6 +1714,40 @@ debug "^4.1.1" tslib "^1.9.3" +"@oclif/core@1.26.0": + version "1.26.0" + resolved "https://registry.yarnpkg.com/@oclif/core/-/core-1.26.0.tgz#35f2e913e2d533d5384b88e5cdb02dc8158ebfd9" + integrity sha512-VzvxKsFOLSlmAPloeLAQHGbOe3o2eP+/T7czf5WsBS69GrsIMYHSNnOXbYoKozbpkFUX9xe1Moc2j2g3s9OmWw== + dependencies: + "@oclif/linewrap" "^1.0.0" + "@oclif/screen" "^3.0.4" + ansi-escapes "^4.3.2" + ansi-styles "^4.3.0" + cardinal "^2.1.1" + chalk "^4.1.2" + clean-stack "^3.0.1" + cli-progress "^3.10.0" + debug "^4.3.4" + ejs "^3.1.6" + fs-extra "^9.1.0" + get-package-type "^0.1.0" + globby "^11.1.0" + hyperlinker "^1.0.0" + indent-string "^4.0.0" + is-wsl "^2.2.0" + js-yaml "^3.14.1" + natural-orderby "^2.0.3" + object-treeify "^1.1.33" + password-prompt "^1.1.2" + semver "^7.3.7" + string-width "^4.2.3" + strip-ansi "^6.0.1" + supports-color "^8.1.1" + supports-hyperlinks "^2.2.0" + tslib "^2.4.1" + widest-line "^3.1.0" + wrap-ansi "^7.0.0" + "@oclif/dev-cli@^1.26.10": version "1.26.10" resolved "https://registry.yarnpkg.com/@oclif/dev-cli/-/dev-cli-1.26.10.tgz#d8df3a79009b68552f5e7f249d1d19ca52278382" @@ -1856,21 +1890,22 @@ widest-line "^3.1.0" wrap-ansi "^6.2.0" -"@oclif/plugin-help@3.2.2": - version "3.2.2" - resolved "https://registry.yarnpkg.com/@oclif/plugin-help/-/plugin-help-3.2.2.tgz#063ee08cee556573a5198fbdfdaa32796deba0ed" - integrity sha512-SPZ8U8PBYK0n4srFjCLedk0jWU4QlxgEYLCXIBShJgOwPhTTQknkUlsEwaMIevvCU4iCQZhfMX+D8Pz5GZjFgA== +"@oclif/plugin-help@3.2.20": + version "3.2.20" + resolved "https://registry.yarnpkg.com/@oclif/plugin-help/-/plugin-help-3.2.20.tgz#441c706f863df29b05da29716537454ce22f82e4" + integrity sha512-3DhmxopYMHblO9peXulsVkb1ueEg0/5dAdQ3ddy+/S2Vi/lytrkA3WsJUTp+QNdJJc5w2y1+1BR8T8KS2nOmow== dependencies: - "@oclif/command" "^1.5.20" - "@oclif/config" "^1.15.1" - "@oclif/errors" "^1.2.2" - chalk "^4.1.0" + "@oclif/command" "^1.8.15" + "@oclif/config" "1.18.2" + "@oclif/errors" "1.3.5" + "@oclif/help" "^1.0.1" + chalk "^4.1.2" indent-string "^4.0.0" - lodash.template "^4.4.0" + lodash "^4.17.21" string-width "^4.2.0" strip-ansi "^6.0.0" widest-line "^3.1.0" - wrap-ansi "^4.0.0" + wrap-ansi "^6.2.0" "@oclif/plugin-help@^2": version "2.2.1" @@ -1917,6 +1952,11 @@ resolved "https://registry.yarnpkg.com/@oclif/screen/-/screen-1.0.4.tgz#b740f68609dfae8aa71c3a6cab15d816407ba493" integrity sha512-60CHpq+eqnTxLZQ4PGHYNwUX572hgpMHGPtTWMjdTMsAvlm69lZV/4ly6O3sAYkomo4NggGcomrDpBe34rxUqw== +"@oclif/screen@^3.0.4": + version "3.0.8" + resolved "https://registry.yarnpkg.com/@oclif/screen/-/screen-3.0.8.tgz#f746549c3ae52fdb7794dfc244dfba98ebca37f2" + integrity sha512-yx6KAqlt3TAHBduS2fMQtJDL2ufIHnDRArrJEOoTTuizxqmjLT+psGYOHpmMl3gvQpFJ11Hs76guUUktzAF9Bg== + "@oclif/test@^1.2.9": version "1.2.9" resolved "https://registry.yarnpkg.com/@oclif/test/-/test-1.2.9.tgz#bfd0bd3de6d2309f779b8f445a163db2fd48e72b" @@ -2466,6 +2506,13 @@ ansi-escapes@^4.2.1, ansi-escapes@^4.3.0: dependencies: type-fest "^0.11.0" +ansi-escapes@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + ansi-escapes@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-6.2.0.tgz#8a13ce75286f417f1963487d86ba9f90dccf9947" @@ -2525,7 +2572,7 @@ ansi-styles@^4.0.0: "@types/color-name" "^1.1.1" color-convert "^2.0.1" -ansi-styles@^4.1.0, ansi-styles@^4.2.0: +ansi-styles@^4.1.0, ansi-styles@^4.2.0, ansi-styles@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== @@ -3578,7 +3625,7 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== -clean-stack@^3.0.0: +clean-stack@^3.0.0, clean-stack@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-3.0.1.tgz#155bf0b2221bf5f4fba89528d24c5953f17fe3a8" integrity sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg== @@ -3597,6 +3644,13 @@ cli-cursor@3.1.0, cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" +cli-progress@^3.10.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/cli-progress/-/cli-progress-3.12.0.tgz#807ee14b66bcc086258e444ad0f19e7d42577942" + integrity sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A== + dependencies: + string-width "^4.2.3" + cli-progress@^3.4.0: version "3.11.2" resolved "https://registry.yarnpkg.com/cli-progress/-/cli-progress-3.11.2.tgz#f8c89bd157e74f3f2c43bcfb3505670b4d48fc77" @@ -5754,6 +5808,11 @@ get-own-enumerable-property-symbols@^3.0.0: resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.1.tgz#6f7764f88ea11e0b514bd9bd860a132259992ca4" integrity sha512-09/VS4iek66Dh2bctjRkowueRJbY1JDGR1L/zRxO1Qk8Uxs6PnqaNSqalpizPT+CDjre3hnEsuzvhgomz9qYrA== +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + get-pkg-repo@^4.0.0: version "4.2.1" resolved "https://registry.yarnpkg.com/get-pkg-repo/-/get-pkg-repo-4.2.1.tgz#75973e1c8050c73f48190c52047c4cee3acbf385" @@ -7193,7 +7252,7 @@ js-yaml@4.1.0, js-yaml@^4.1.0: dependencies: argparse "^2.0.1" -js-yaml@^3.10.0, js-yaml@^3.13.0: +js-yaml@^3.10.0, js-yaml@^3.13.0, js-yaml@^3.14.1: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== @@ -8448,7 +8507,7 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -natural-orderby@^2.0.1: +natural-orderby@^2.0.1, natural-orderby@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/natural-orderby/-/natural-orderby-2.0.3.tgz#8623bc518ba162f8ff1cdb8941d74deb0fdcc016" integrity sha512-p7KTHxU0CUrcOXe62Zfrb5Z13nLvPhSWR/so3kFulUQU0sgUll2Z0LwpsLN351eOOD+hRGu/F1g+6xDfPeD++Q== @@ -8973,7 +9032,7 @@ object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object-treeify@^1.1.4: +object-treeify@^1.1.33, object-treeify@^1.1.4: version "1.1.33" resolved "https://registry.yarnpkg.com/object-treeify/-/object-treeify-1.1.33.tgz#f06fece986830a3cba78ddd32d4c11d1f76cdf40" integrity sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A== @@ -11072,7 +11131,7 @@ subarg@^1.0.0: dependencies: minimist "^1.1.0" -supports-color@8.1.1, supports-color@^8.1.0: +supports-color@8.1.1, supports-color@^8.1.0, supports-color@^8.1.1: version "8.1.1" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== @@ -11123,7 +11182,7 @@ supports-hyperlinks@^2.1.0: has-flag "^4.0.0" supports-color "^7.0.0" -supports-hyperlinks@^2.3.0: +supports-hyperlinks@^2.2.0, supports-hyperlinks@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz#3943544347c1ff90b15effb03fc14ae45ec10624" integrity sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA== @@ -11498,6 +11557,11 @@ type-fest@^0.20.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + type-fest@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.4.1.tgz#8bdf77743385d8a4f13ba95f610f5ccd68c728f8"