From eb892ecca22bc1436c487cbddab7f8c39488656f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Antonio=20Gonz=C3=A1lez?= Date: Tue, 4 Oct 2022 17:43:29 +0100 Subject: [PATCH] Detect yarn version to dynamically not add deprecated flags --- lib/packExternalModules.js | 41 ++++++++++++----------- lib/packagers/index.js | 1 + lib/packagers/npm.js | 11 +++++++ lib/packagers/yarn.js | 33 +++++++++++++++---- package-lock.json | 14 ++++---- package.json | 2 +- tests/packExternalModules.test.js | 11 +++++++ tests/packagers/npm.test.js | 30 +++++++++++++++++ tests/packagers/yarn.test.js | 54 +++++++++++++++++++++++++++---- 9 files changed, 159 insertions(+), 38 deletions(-) diff --git a/lib/packExternalModules.js b/lib/packExternalModules.js index 3bc000676..56d2b1075 100644 --- a/lib/packExternalModules.js +++ b/lib/packExternalModules.js @@ -397,17 +397,20 @@ module.exports = { } else { this.serverless.cli.log('Packing external modules: ' + compositeModules.join(', ')); } - return packager - .install(compositeModulePath, this.configuration.packagerOptions) - .then(() => { - if (this.log) { - this.log.verbose(`Package took [${_.now() - start} ms]`); - } else { - this.options.verbose && this.serverless.cli.log(`Package took [${_.now() - start} ms]`); - } - return null; - }) - .return(stats.stats); + + return packager.getPackagerVersion(compositeModulePath).then(version => { + return packager + .install(compositeModulePath, this.configuration.packagerOptions, version) + .then(() => { + if (this.log) { + this.log.verbose(`Package took [${_.now() - start} ms]`); + } else { + this.options.verbose && this.serverless.cli.log(`Package took [${_.now() - start} ms]`); + } + return null; + }) + .return(stats.stats); + }); }) .mapSeries(compileStats => { const modulePath = compileStats.outputPath; @@ -485,13 +488,15 @@ module.exports = { .then(() => { // Prune extraneous packages - removes not needed ones const startPrune = _.now(); - return packager.prune(modulePath, this.configuration.packagerOptions).tap(() => { - if (this.log) { - this.log.verbose(`Prune: ${modulePath} [${_.now() - startPrune} ms]`); - } else { - this.options.verbose && - this.serverless.cli.log(`Prune: ${modulePath} [${_.now() - startPrune} ms]`); - } + return packager.getPackagerVersion(modulePath).then(version => { + return packager.prune(modulePath, this.configuration.packagerOptions, version).tap(() => { + if (this.log) { + this.log.verbose(`Prune: ${modulePath} [${_.now() - startPrune} ms]`); + } else { + this.options.verbose && + this.serverless.cli.log(`Prune: ${modulePath} [${_.now() - startPrune} ms]`); + } + }); }); }) .then(() => { diff --git a/lib/packagers/index.js b/lib/packagers/index.js index 4c2a1ef5e..4bea331fb 100644 --- a/lib/packagers/index.js +++ b/lib/packagers/index.js @@ -9,6 +9,7 @@ * static get lockfileName(): string; * static get copyPackageSectionNames(): Array; * static get mustCopyModules(): boolean; + * static getPackagerVersion(cwd: string): BbPromise * static getProdDependencies(cwd: string, depth: number = 1): BbPromise; * static rebaseLockfile(pathToPackageRoot: string, lockfile: Object): void; * static install(cwd: string): BbPromise; diff --git a/lib/packagers/npm.js b/lib/packagers/npm.js index 56b806547..d96a0d807 100644 --- a/lib/packagers/npm.js +++ b/lib/packagers/npm.js @@ -25,6 +25,17 @@ class NPM { return true; } + static getPackagerVersion(cwd) { + const command = /^win/.test(process.platform) ? 'npm.cmd' : 'npm'; + const args = ['-v']; + + return Utils.spawnProcess(command, args, { cwd }) + .catch(err => { + return BbPromise.resolve({ stdout: err.stdout }); + }) + .then(processOutput => processOutput.stdout); + } + static getProdDependencies(cwd, depth, packagerOptions) { // Try to use NPM lockfile v2 when possible const options = packagerOptions || {}; diff --git a/lib/packagers/yarn.js b/lib/packagers/yarn.js index d03f700ec..fbd83a823 100644 --- a/lib/packagers/yarn.js +++ b/lib/packagers/yarn.js @@ -29,6 +29,23 @@ class Yarn { return false; } + static isBerryVersion(version) { + const versionNumber = version.charAt(0); + const mainVersion = parseInt(versionNumber); + return mainVersion > 1; + } + + static getPackagerVersion(cwd) { + const command = /^win/.test(process.platform) ? 'yarn.cmd' : 'yarn'; + const args = ['-v']; + + return Utils.spawnProcess(command, args, { cwd }) + .catch(err => { + return BbPromise.resolve({ stdout: err.stdout }); + }) + .then(processOutput => processOutput.stdout); + } + static getProdDependencies(cwd, depth) { const command = /^win/.test(process.platform) ? 'yarn.cmd' : 'yarn'; const args = ['list', `--depth=${depth || 1}`, '--json', '--production']; @@ -118,20 +135,24 @@ class Yarn { return _.reduce(replacements, (__, replacement) => _.replace(__, replacement.oldRef, replacement.newRef), lockfile); } - static install(cwd, packagerOptions) { + static install(cwd, packagerOptions, version) { if (packagerOptions.noInstall) { return BbPromise.resolve(); } + const isBerry = Yarn.isBerryVersion(version); const command = /^win/.test(process.platform) ? 'yarn.cmd' : 'yarn'; const args = ['install']; - // Convert supported packagerOptions - if (!packagerOptions.noNonInteractive) { + if (!packagerOptions.noNonInteractive && !isBerry) { args.push('--non-interactive'); } if (!packagerOptions.noFrozenLockfile) { - args.push('--frozen-lockfile'); + if (isBerry) { + args.push('--immutable'); + } else { + args.push('--frozen-lockfile'); + } } if (packagerOptions.ignoreScripts) { args.push('--ignore-scripts'); @@ -144,8 +165,8 @@ class Yarn { } // "Yarn install" prunes automatically - static prune(cwd, packagerOptions) { - return Yarn.install(cwd, packagerOptions); + static prune(cwd, packagerOptions, version) { + return Yarn.install(cwd, packagerOptions, version); } static runScripts(cwd, scriptNames) { diff --git a/package-lock.json b/package-lock.json index 65b239dde..41388f02d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "eslint-plugin-import": "^2.26.0", "eslint-plugin-lodash": "^7.4.0", "eslint-plugin-prettier": "^4.2.1", - "eslint-plugin-promise": "^6.0.1", + "eslint-plugin-promise": "^6.1.0", "husky": "^4.3.8", "jest": "^27.5.1", "lint-staged": "^10.5.4", @@ -5192,9 +5192,9 @@ } }, "node_modules/eslint-plugin-promise": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.0.1.tgz", - "integrity": "sha512-uM4Tgo5u3UWQiroOyDEsYcVMOo7re3zmno0IZmB5auxoaQNIceAbXEkSt8RNrKtaYehARHG06pYK6K1JhtP0Zw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.0.tgz", + "integrity": "sha512-NYCfDZF/KHt27p06nFAttgWuFyIDSUMnNaJBIY1FY9GpBFhdT2vMG64HlFguSgcJeyM5by6Yr5csSOuJm60eXQ==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -18450,9 +18450,9 @@ } }, "eslint-plugin-promise": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.0.1.tgz", - "integrity": "sha512-uM4Tgo5u3UWQiroOyDEsYcVMOo7re3zmno0IZmB5auxoaQNIceAbXEkSt8RNrKtaYehARHG06pYK6K1JhtP0Zw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.0.tgz", + "integrity": "sha512-NYCfDZF/KHt27p06nFAttgWuFyIDSUMnNaJBIY1FY9GpBFhdT2vMG64HlFguSgcJeyM5by6Yr5csSOuJm60eXQ==", "dev": true, "requires": {} }, diff --git a/package.json b/package.json index c86f8fd05..61c7dd065 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "eslint-plugin-import": "^2.26.0", "eslint-plugin-lodash": "^7.4.0", "eslint-plugin-prettier": "^4.2.1", - "eslint-plugin-promise": "^6.0.1", + "eslint-plugin-promise": "^6.1.0", "husky": "^4.3.8", "jest": "^27.5.1", "lint-staged": "^10.5.4", diff --git a/tests/packExternalModules.test.js b/tests/packExternalModules.test.js index c6394c7ef..e308399c1 100644 --- a/tests/packExternalModules.test.js +++ b/tests/packExternalModules.test.js @@ -21,6 +21,7 @@ jest.mock('../lib/packagers/index', () => { copyPackageSectionNames: ['section1', 'section2'], mustCopyModules: true, rebaseLockfile: jest.fn(), + getPackagerVersion: jest.fn(), getProdDependencies: jest.fn(), install: jest.fn(), prune: jest.fn(), @@ -203,6 +204,7 @@ describe('packExternalModules', () => { fsExtraMock.pathExists.mockImplementation((p, cb) => cb(null, false)); fsExtraMock.copy.mockImplementation((from, to, cb) => cb()); packagerFactoryMock.get('npm').getProdDependencies.mockReturnValue(BbPromise.resolve({})); + packagerFactoryMock.get('npm').getPackagerVersion.mockReturnValue(BbPromise.resolve()); packagerFactoryMock.get('npm').install.mockReturnValue(BbPromise.resolve()); packagerFactoryMock.get('npm').prune.mockReturnValue(BbPromise.resolve()); packagerFactoryMock.get('npm').runScripts.mockReturnValue(BbPromise.resolve()); @@ -219,6 +221,7 @@ describe('packExternalModules', () => { expect(fsExtraMock.copy).toHaveBeenCalledTimes(1), // npm ls and npm prune should have been called expect(packagerFactoryMock.get('npm').getProdDependencies).toHaveBeenCalledTimes(1), + expect(packagerFactoryMock.get('npm').getPackagerVersion).toHaveBeenCalledTimes(2), expect(packagerFactoryMock.get('npm').install).toHaveBeenCalledTimes(1), expect(packagerFactoryMock.get('npm').prune).toHaveBeenCalledTimes(1), expect(packagerFactoryMock.get('npm').runScripts).toHaveBeenCalledTimes(1) @@ -256,6 +259,7 @@ describe('packExternalModules', () => { fsExtraMock.pathExists.mockImplementation((p, cb) => cb(null, false)); fsExtraMock.copy.mockImplementation((from, to, cb) => cb()); packagerFactoryMock.get('npm').getProdDependencies.mockReturnValue(BbPromise.resolve({})); + packagerFactoryMock.get('npm').getPackagerVersion.mockReturnValue(BbPromise.resolve()); packagerFactoryMock.get('npm').install.mockReturnValue(BbPromise.resolve()); packagerFactoryMock.get('npm').prune.mockReturnValue(BbPromise.resolve()); packagerFactoryMock.get('npm').runScripts.mockReturnValue(BbPromise.resolve()); @@ -272,6 +276,7 @@ describe('packExternalModules', () => { expect(fsExtraMock.copy).toHaveBeenCalledTimes(1), // npm ls and npm prune should have been called expect(packagerFactoryMock.get('npm').getProdDependencies).toHaveBeenCalledTimes(1), + expect(packagerFactoryMock.get('npm').getPackagerVersion).toHaveBeenCalledTimes(2), expect(packagerFactoryMock.get('npm').install).toHaveBeenCalledTimes(1), expect(packagerFactoryMock.get('npm').prune).toHaveBeenCalledTimes(1), expect(packagerFactoryMock.get('npm').runScripts).toHaveBeenCalledTimes(1) @@ -341,6 +346,7 @@ describe('packExternalModules', () => { fsExtraMock.copy.mockImplementation((from, to, cb) => cb()); packagerFactoryMock.get('npm').getProdDependencies.mockReturnValue(BbPromise.resolve({})); packagerFactoryMock.get('npm').rebaseLockfile.mockImplementation((pathToPackageRoot, lockfile) => lockfile); + packagerFactoryMock.get('npm').getPackagerVersion.mockReturnValue(BbPromise.resolve()); packagerFactoryMock.get('npm').install.mockReturnValue(BbPromise.resolve()); packagerFactoryMock.get('npm').prune.mockReturnValue(BbPromise.resolve()); packagerFactoryMock.get('npm').runScripts.mockReturnValue(BbPromise.resolve()); @@ -371,6 +377,7 @@ describe('packExternalModules', () => { ), // npm ls and npm prune should have been called expect(packagerFactoryMock.get('npm').getProdDependencies).toHaveBeenCalledTimes(1), + expect(packagerFactoryMock.get('npm').getPackagerVersion).toHaveBeenCalledTimes(2), expect(packagerFactoryMock.get('npm').install).toHaveBeenCalledTimes(1), expect(packagerFactoryMock.get('npm').prune).toHaveBeenCalledTimes(1), expect(packagerFactoryMock.get('npm').runScripts).toHaveBeenCalledTimes(1) @@ -412,6 +419,7 @@ describe('packExternalModules', () => { fsExtraMock.pathExists.mockImplementation((p, cb) => cb(null, false)); fsExtraMock.copy.mockImplementation((from, to, cb) => cb()); packagerFactoryMock.get('npm').getProdDependencies.mockReturnValue(BbPromise.resolve({})); + packagerFactoryMock.get('npm').getPackagerVersion.mockReturnValue(BbPromise.resolve()); packagerFactoryMock.get('npm').install.mockReturnValue(BbPromise.resolve()); packagerFactoryMock.get('npm').prune.mockReturnValue(BbPromise.resolve()); packagerFactoryMock.get('npm').runScripts.mockReturnValue(BbPromise.resolve()); @@ -428,6 +436,7 @@ describe('packExternalModules', () => { expect(fsExtraMock.copy).toHaveBeenCalledTimes(0), // npm ls and npm prune should have been called expect(packagerFactoryMock.get('npm').getProdDependencies).toHaveBeenCalledTimes(1), + expect(packagerFactoryMock.get('npm').getPackagerVersion).toHaveBeenCalledTimes(1), expect(packagerFactoryMock.get('npm').install).toHaveBeenCalledTimes(1), expect(packagerFactoryMock.get('npm').prune).toHaveBeenCalledTimes(0), expect(packagerFactoryMock.get('npm').runScripts).toHaveBeenCalledTimes(0) @@ -440,6 +449,7 @@ describe('packExternalModules', () => { fsExtraMock.pathExists.mockImplementation((p, cb) => cb(null, false)); fsExtraMock.copy.mockImplementation((from, to, cb) => cb()); packagerFactoryMock.get('npm').getProdDependencies.mockReturnValue(BbPromise.resolve({})); + packagerFactoryMock.get('npm').getPackagerVersion.mockReturnValue(BbPromise.resolve()); packagerFactoryMock .get('npm') .install.mockImplementation(() => BbPromise.reject(new Error('npm install failed'))); @@ -452,6 +462,7 @@ describe('packExternalModules', () => { BbPromise.all([ // npm ls and npm install should have been called expect(packagerFactoryMock.get('npm').getProdDependencies).toHaveBeenCalledTimes(1), + expect(packagerFactoryMock.get('npm').getPackagerVersion).toHaveBeenCalledTimes(1), expect(packagerFactoryMock.get('npm').install).toHaveBeenCalledTimes(1), expect(packagerFactoryMock.get('npm').prune).toHaveBeenCalledTimes(0), expect(packagerFactoryMock.get('npm').runScripts).toHaveBeenCalledTimes(0) diff --git a/tests/packagers/npm.test.js b/tests/packagers/npm.test.js index 0de52cbde..9c2f185c6 100644 --- a/tests/packagers/npm.test.js +++ b/tests/packagers/npm.test.js @@ -38,6 +38,36 @@ describe('npm', () => { expect(npmModule.mustCopyModules).toBe(true); }); + describe('getPackagerVersion', () => { + it('should use npm version 6.14.17', () => { + const npmVersion = '6.14.17'; + Utils.spawnProcess.mockReturnValue(BbPromise.resolve({ stdout: npmVersion, stderr: '' })); + return expect(npmModule.getPackagerVersion('myPath', 1)) + .resolves.toEqual(npmVersion) + .then(() => { + expect(Utils.spawnProcess).toHaveBeenCalledTimes(1); + expect(Utils.spawnProcess).toHaveBeenNthCalledWith(1, expect.stringMatching(/^npm/), ['-v'], { + cwd: 'myPath' + }); + return null; + }); + }); + + it('should use npm version 8.19.2', () => { + const npmVersion = '8.19.2'; + Utils.spawnProcess.mockReturnValue(BbPromise.resolve({ stdout: npmVersion, stderr: '' })); + return expect(npmModule.getPackagerVersion('myPath', 1)) + .resolves.toEqual(npmVersion) + .then(() => { + expect(Utils.spawnProcess).toHaveBeenCalledTimes(1); + expect(Utils.spawnProcess).toHaveBeenNthCalledWith(1, expect.stringMatching(/^npm/), ['-v'], { + cwd: 'myPath' + }); + return null; + }); + }); + }); + describe('install', () => { it('should use npm install', () => { Utils.spawnProcess.mockReturnValue(BbPromise.resolve({ stdout: 'installed successfully', stderr: '' })); diff --git a/tests/packagers/yarn.test.js b/tests/packagers/yarn.test.js index 9db6cf789..2f483defb 100644 --- a/tests/packagers/yarn.test.js +++ b/tests/packagers/yarn.test.js @@ -28,6 +28,48 @@ describe('yarn', () => { expect(yarnModule.mustCopyModules).toBe(false); }); + describe('isBerryVersion', () => { + it('Yarn version 1.22.19', () => { + const yarnVersion = '1.22.19'; + expect(yarnModule.isBerryVersion(yarnVersion)).toBe(false); + }); + + it('Yarn version 3.2.3', () => { + const yarnVersion = '3.2.3'; + expect(yarnModule.isBerryVersion(yarnVersion)).toBe(true); + }); + }); + + describe('getPackagerVersion', () => { + it('should use yarn version 1.22.19', () => { + const yarnVersion = '1.22.19'; + Utils.spawnProcess.mockReturnValue(BbPromise.resolve({ stdout: yarnVersion, stderr: '' })); + return expect(yarnModule.getPackagerVersion('myPath', 1)) + .resolves.toEqual(yarnVersion) + .then(() => { + expect(Utils.spawnProcess).toHaveBeenCalledTimes(1); + expect(Utils.spawnProcess).toHaveBeenNthCalledWith(1, expect.stringMatching(/^yarn/), ['-v'], { + cwd: 'myPath' + }); + return null; + }); + }); + + it('should use yarn version 3.2.3', () => { + const yarnVersion = '3.2.3'; + Utils.spawnProcess.mockReturnValue(BbPromise.resolve({ stdout: yarnVersion, stderr: '' })); + return expect(yarnModule.getPackagerVersion('myPath', 1)) + .resolves.toEqual(yarnVersion) + .then(() => { + expect(Utils.spawnProcess).toHaveBeenCalledTimes(1); + expect(Utils.spawnProcess).toHaveBeenNthCalledWith(1, expect.stringMatching(/^yarn/), ['-v'], { + cwd: 'myPath' + }); + return null; + }); + }); + }); + describe('getProdDependencies', () => { it('should use yarn list', () => { Utils.spawnProcess.mockReturnValue(BbPromise.resolve({ stdout: '{}', stderr: '' })); @@ -198,7 +240,7 @@ describe('yarn', () => { describe('install', () => { it('should use yarn install', () => { Utils.spawnProcess.mockReturnValue(BbPromise.resolve({ stdout: 'installed successfully', stderr: '' })); - return expect(yarnModule.install('myPath', {})) + return expect(yarnModule.install('myPath', {}, '1.22.19')) .resolves.toBeUndefined() .then(() => { expect(Utils.spawnProcess).toHaveBeenCalledTimes(1); @@ -215,7 +257,7 @@ describe('yarn', () => { it('should use noNonInteractive option', () => { Utils.spawnProcess.mockReturnValue(BbPromise.resolve({ stdout: 'installed successfully', stderr: '' })); - return expect(yarnModule.install('myPath', { noNonInteractive: true })) + return expect(yarnModule.install('myPath', { noNonInteractive: true }, '1.22.19')) .resolves.toBeUndefined() .then(() => { expect(Utils.spawnProcess).toHaveBeenCalledTimes(1); @@ -232,7 +274,7 @@ describe('yarn', () => { it('should use ignoreScripts option', () => { Utils.spawnProcess.mockReturnValue(BbPromise.resolve({ stdout: 'installed successfully', stderr: '' })); - return expect(yarnModule.install('myPath', { ignoreScripts: true })) + return expect(yarnModule.install('myPath', { ignoreScripts: true }, '1.22.19')) .resolves.toBeUndefined() .then(() => { expect(Utils.spawnProcess).toHaveBeenCalledTimes(1); @@ -249,7 +291,7 @@ describe('yarn', () => { it('should use noFrozenLockfile option', () => { Utils.spawnProcess.mockReturnValue(BbPromise.resolve({ stdout: 'installed successfully', stderr: '' })); - return expect(yarnModule.install('myPath', { noFrozenLockfile: true })) + return expect(yarnModule.install('myPath', { noFrozenLockfile: true }, '1.22.19')) .resolves.toBeUndefined() .then(() => { expect(Utils.spawnProcess).toHaveBeenCalledTimes(1); @@ -266,7 +308,7 @@ describe('yarn', () => { it('should use networkConcurrency option', () => { Utils.spawnProcess.mockReturnValue(BbPromise.resolve({ stdout: 'installed successfully', stderr: '' })); - return expect(yarnModule.install('myPath', { networkConcurrency: 1 })) + return expect(yarnModule.install('myPath', { networkConcurrency: 1 }, '1.22.19')) .resolves.toBeUndefined() .then(() => { expect(Utils.spawnProcess).toHaveBeenCalledTimes(1); @@ -291,7 +333,7 @@ describe('yarn', () => { describe('prune', () => { it('should call install', () => { Utils.spawnProcess.mockReturnValue(BbPromise.resolve({ stdout: 'success', stderr: '' })); - return expect(yarnModule.prune('myPath', {})) + return expect(yarnModule.prune('myPath', {}, '1.22.19')) .resolves.toBeUndefined() .then(() => { expect(Utils.spawnProcess).toHaveBeenCalledTimes(1);