diff --git a/lib/manager/npm/post-update/yarn.ts b/lib/manager/npm/post-update/yarn.ts index 6e3385f8f663054efcf29f3f71b07fafcc9990dd..a54b80b7e94021e87ff1d367738a4706e7b52e4e 100644 --- a/lib/manager/npm/post-update/yarn.ts +++ b/lib/manager/npm/post-update/yarn.ts @@ -1,6 +1,7 @@ import { readFile } from 'fs-extra'; import { join } from 'upath'; import { getInstalledPath } from 'get-installed-path'; +import { api as semver } from '../../../versioning/semver'; import { exec } from '../../../util/exec'; import { logger } from '../../../logger'; import { PostUpdateConfig, Upgrade } from '../../common'; @@ -86,9 +87,20 @@ export async function generateLockFile( if (binarySource === BinarySource.Global) { cmd = 'yarn'; } - logger.debug(`Using yarn: ${cmd}`); + + const { stdout: yarnVersion } = await exec(`${cmd} --version`); + + logger.debug(`Using yarn: ${cmd} ${yarnVersion}`); + + const yarnMajorVersion = semver.getMajor(yarnVersion); + let cmdExtras = ''; - cmdExtras += ' --ignore-scripts'; + const cmdEnv = { ...env }; + if (yarnMajorVersion < 2) { + cmdExtras += ' --ignore-scripts'; + } else { + cmdEnv.YARN_ENABLE_SCRIPTS = '0'; + } cmdExtras += ' --ignore-engines'; cmdExtras += ' --ignore-platform'; cmdExtras += process.env.YARN_MUTEX_FILE @@ -98,7 +110,7 @@ export async function generateLockFile( // TODO: Switch to native util.promisify once using only node 8 await exec(installCmd, { cwd, - env, + env: cmdEnv, }); const lockUpdates = upgrades .filter((upgrade) => upgrade.isLockfileUpdate) @@ -121,41 +133,50 @@ export async function generateLockFile( ? /* istanbul ignore next */ updateRes.stderr : ''; } - if ( - config.postUpdateOptions && - config.postUpdateOptions.includes('yarnDedupeFewer') - ) { - logger.debug('Performing yarn dedupe fewer'); - const dedupeCommand = - 'npx yarn-deduplicate@1.1.1 --strategy fewer && yarn'; - const dedupeRes = await exec(dedupeCommand, { - cwd, - env, - }); - stdout += dedupeRes.stdout - ? /* istanbul ignore next */ dedupeRes.stdout - : ''; - stderr += dedupeRes.stderr - ? /* istanbul ignore next */ dedupeRes.stderr - : ''; - } - if ( + + if (yarnMajorVersion < 2) { + if ( + config.postUpdateOptions && + config.postUpdateOptions.includes('yarnDedupeFewer') + ) { + logger.debug('Performing yarn dedupe fewer'); + const dedupeCommand = + 'npx yarn-deduplicate@1.1.1 --strategy fewer && yarn'; + const dedupeRes = await exec(dedupeCommand, { + cwd, + env, + }); + stdout += dedupeRes.stdout + ? /* istanbul ignore next */ dedupeRes.stdout + : ''; + stderr += dedupeRes.stderr + ? /* istanbul ignore next */ dedupeRes.stderr + : ''; + } + if ( + config.postUpdateOptions && + config.postUpdateOptions.includes('yarnDedupeHighest') + ) { + logger.debug('Performing yarn dedupe highest'); + const dedupeCommand = + 'npx yarn-deduplicate@1.1.1 --strategy highest && yarn'; + const dedupeRes = await exec(dedupeCommand, { + cwd, + env, + }); + stdout += dedupeRes.stdout + ? /* istanbul ignore next */ dedupeRes.stdout + : ''; + stderr += dedupeRes.stderr + ? /* istanbul ignore next */ dedupeRes.stderr + : ''; + } + } else if ( config.postUpdateOptions && - config.postUpdateOptions.includes('yarnDedupeHighest') + (config.postUpdateOptions.includes('yarnDedupeFewer') || + config.postUpdateOptions.includes('yarnDedupeHighest')) ) { - logger.debug('Performing yarn dedupe highest'); - const dedupeCommand = - 'npx yarn-deduplicate@1.1.1 --strategy highest && yarn'; - const dedupeRes = await exec(dedupeCommand, { - cwd, - env, - }); - stdout += dedupeRes.stdout - ? /* istanbul ignore next */ dedupeRes.stdout - : ''; - stderr += dedupeRes.stderr - ? /* istanbul ignore next */ dedupeRes.stderr - : ''; + logger.warn('yarn-deduplicate is not supported since yarn 2'); } lockFile = await readFile(join(cwd, 'yarn.lock'), 'utf8'); } catch (err) /* istanbul ignore next */ { diff --git a/lib/workers/branch/lock-files/__snapshots__/yarn.spec.ts.snap b/lib/workers/branch/lock-files/__snapshots__/yarn.spec.ts.snap index a95efc06f658b8d666e2622c95b379c44f02cf2a..8c49d11c16386c4470a16e498f8d4daae245efd5 100644 --- a/lib/workers/branch/lock-files/__snapshots__/yarn.spec.ts.snap +++ b/lib/workers/branch/lock-files/__snapshots__/yarn.spec.ts.snap @@ -1,7 +1,24 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`generateLockFile catches errors 1`] = ` +exports[`workers/branch/lock-files/yarn catches errors 1`] = ` Array [ + Object { + "cmd": "<yarn> --version", + "options": Object { + "cwd": null, + "encoding": "utf-8", + "env": Object { + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "timeout": 900000, + }, + }, Object { "cmd": "<yarn> install --ignore-scripts --ignore-engines --ignore-platform --mutex network:31879", "options": Object { @@ -22,8 +39,25 @@ Array [ ] `; -exports[`generateLockFile detects yarnIntegrity 1`] = ` +exports[`workers/branch/lock-files/yarn detects yarnIntegrity using yarn v1.0.0 1`] = ` Array [ + Object { + "cmd": "<yarn> --version", + "options": Object { + "cwd": null, + "encoding": "utf-8", + "env": Object { + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "timeout": 900000, + }, + }, Object { "cmd": "<yarn> install --ignore-scripts --ignore-engines --ignore-platform --mutex network:31879", "options": Object { @@ -61,8 +95,82 @@ Array [ ] `; -exports[`generateLockFile finds yarn embedded in renovate 1`] = ` +exports[`workers/branch/lock-files/yarn detects yarnIntegrity using yarn v2.0.0 1`] = ` Array [ + Object { + "cmd": "<yarn> --version", + "options": Object { + "cwd": null, + "encoding": "utf-8", + "env": Object { + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "timeout": 900000, + }, + }, + Object { + "cmd": "<yarn> install --ignore-engines --ignore-platform --mutex network:31879", + "options": Object { + "cwd": "some-dir", + "encoding": "utf-8", + "env": Object { + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + "YARN_ENABLE_SCRIPTS": "0", + }, + "timeout": 900000, + }, + }, + Object { + "cmd": "<yarn> upgrade some-dep --ignore-engines --ignore-platform --mutex network:31879", + "options": Object { + "cwd": "some-dir", + "encoding": "utf-8", + "env": Object { + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "timeout": 900000, + }, + }, +] +`; + +exports[`workers/branch/lock-files/yarn finds yarn embedded in renovate 1`] = ` +Array [ + Object { + "cmd": "<yarn> --version", + "options": Object { + "cwd": null, + "encoding": "utf-8", + "env": Object { + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "timeout": 900000, + }, + }, Object { "cmd": "<yarn> install --ignore-scripts --ignore-engines --ignore-platform --mutex network:31879", "options": Object { @@ -83,8 +191,25 @@ Array [ ] `; -exports[`generateLockFile finds yarn globally 1`] = ` +exports[`workers/branch/lock-files/yarn finds yarn v1.0.0 globally 1`] = ` Array [ + Object { + "cmd": "<yarn> --version", + "options": Object { + "cwd": null, + "encoding": "utf-8", + "env": Object { + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "timeout": 900000, + }, + }, Object { "cmd": "<yarn> install --ignore-scripts --ignore-engines --ignore-platform --mutex network:31879", "options": Object { @@ -105,8 +230,65 @@ Array [ ] `; -exports[`generateLockFile generates lock files 1`] = ` +exports[`workers/branch/lock-files/yarn finds yarn v2.0.0 globally 1`] = ` Array [ + Object { + "cmd": "<yarn> --version", + "options": Object { + "cwd": null, + "encoding": "utf-8", + "env": Object { + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "timeout": 900000, + }, + }, + Object { + "cmd": "<yarn> install --ignore-engines --ignore-platform --mutex network:31879", + "options": Object { + "cwd": "some-dir", + "encoding": "utf-8", + "env": Object { + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + "YARN_ENABLE_SCRIPTS": "0", + }, + "timeout": 900000, + }, + }, +] +`; + +exports[`workers/branch/lock-files/yarn generates lock files using yarn v1.0.0 1`] = ` +Array [ + Object { + "cmd": "<yarn> --version", + "options": Object { + "cwd": null, + "encoding": "utf-8", + "env": Object { + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "timeout": 900000, + }, + }, Object { "cmd": "<yarn> install --ignore-scripts --ignore-engines --ignore-platform --mutex network:31879", "options": Object { @@ -161,8 +343,65 @@ Array [ ] `; -exports[`generateLockFile performs lock file updates 1`] = ` +exports[`workers/branch/lock-files/yarn generates lock files using yarn v2.0.0 1`] = ` Array [ + Object { + "cmd": "<yarn> --version", + "options": Object { + "cwd": null, + "encoding": "utf-8", + "env": Object { + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "timeout": 900000, + }, + }, + Object { + "cmd": "<yarn> install --ignore-engines --ignore-platform --mutex network:31879", + "options": Object { + "cwd": "some-dir", + "encoding": "utf-8", + "env": Object { + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + "YARN_ENABLE_SCRIPTS": "0", + }, + "timeout": 900000, + }, + }, +] +`; + +exports[`workers/branch/lock-files/yarn performs lock file updates using yarn v1.0.0 1`] = ` +Array [ + Object { + "cmd": "<yarn> --version", + "options": Object { + "cwd": null, + "encoding": "utf-8", + "env": Object { + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "timeout": 900000, + }, + }, Object { "cmd": "<yarn> install --ignore-scripts --ignore-engines --ignore-platform --mutex file:/tmp/yarn.mutext", "options": Object { @@ -200,8 +439,82 @@ Array [ ] `; -exports[`generateLockFile uses fallback yarn 1`] = ` +exports[`workers/branch/lock-files/yarn performs lock file updates using yarn v2.0.0 1`] = ` Array [ + Object { + "cmd": "<yarn> --version", + "options": Object { + "cwd": null, + "encoding": "utf-8", + "env": Object { + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "timeout": 900000, + }, + }, + Object { + "cmd": "<yarn> install --ignore-engines --ignore-platform --mutex file:/tmp/yarn.mutext", + "options": Object { + "cwd": "some-dir", + "encoding": "utf-8", + "env": Object { + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + "YARN_ENABLE_SCRIPTS": "0", + }, + "timeout": 900000, + }, + }, + Object { + "cmd": "<yarn> upgrade some-dep --ignore-engines --ignore-platform --mutex file:/tmp/yarn.mutext", + "options": Object { + "cwd": "some-dir", + "encoding": "utf-8", + "env": Object { + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "timeout": 900000, + }, + }, +] +`; + +exports[`workers/branch/lock-files/yarn uses fallback yarn 1`] = ` +Array [ + Object { + "cmd": "yarn --version", + "options": Object { + "cwd": null, + "encoding": "utf-8", + "env": Object { + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "timeout": 900000, + }, + }, Object { "cmd": "yarn install --ignore-scripts --ignore-engines --ignore-platform --mutex network:31879", "options": Object { diff --git a/lib/workers/branch/lock-files/yarn.spec.ts b/lib/workers/branch/lock-files/yarn.spec.ts index aa2351fd1f213b10a55662309f1b3ee48c42a637..99bc470ee862a3e36aa41cac99630ba10019c94e 100644 --- a/lib/workers/branch/lock-files/yarn.spec.ts +++ b/lib/workers/branch/lock-files/yarn.spec.ts @@ -2,7 +2,7 @@ import { getInstalledPath } from 'get-installed-path'; import _fs from 'fs-extra'; import { exec as _exec } from 'child_process'; import * as _yarnHelper from '../../../manager/npm/post-update/yarn'; -import { mocked } from '../../../../test/util'; +import { getName, mocked } from '../../../../test/util'; import { ExecSnapshots, envMock, mockExecAll } from '../../../../test/execUtil'; import * as _env from '../../../util/exec/env'; import { BinarySource } from '../../../util/exec/common'; @@ -26,55 +26,73 @@ const fixSnapshots = (snapshots: ExecSnapshots): ExecSnapshots => cmd: snapshot.cmd.replace(/^.*\/yarn.*?\.js\s+/, '<yarn> '), })); -describe('generateLockFile', () => { +describe(getName(__filename), () => { beforeEach(() => { delete process.env.YARN_MUTEX_FILE; jest.resetAllMocks(); env.getChildProcessEnv.mockReturnValue(envMock.basic); }); - it('generates lock files', async () => { - const execSnapshots = mockExecAll(exec); - getInstalledPath.mockReturnValueOnce('node_modules/yarn'); - fs.readFile = jest.fn(() => 'package-lock-contents') as never; - const config = { - postUpdateOptions: ['yarnDedupeFewer', 'yarnDedupeHighest'], - }; - const res = await yarnHelper.generateLockFile('some-dir', {}, config); - expect(fs.readFile).toHaveBeenCalledTimes(1); - expect(res.lockFile).toEqual('package-lock-contents'); - expect(fixSnapshots(execSnapshots)).toMatchSnapshot(); - }); - it('performs lock file updates', async () => { - const execSnapshots = mockExecAll(exec); + it.each([['1.0.0'], ['2.0.0']])( + 'generates lock files using yarn v%s', + async (yarnVersion) => { + const execSnapshots = mockExecAll(exec, { + stdout: yarnVersion, + stderr: '', + }); + getInstalledPath.mockReturnValueOnce('node_modules/yarn'); + fs.readFile = jest.fn(() => 'package-lock-contents') as never; + const config = { + postUpdateOptions: ['yarnDedupeFewer', 'yarnDedupeHighest'], + }; + const res = await yarnHelper.generateLockFile('some-dir', {}, config); + expect(fs.readFile).toHaveBeenCalledTimes(1); + expect(res.lockFile).toEqual('package-lock-contents'); + expect(fixSnapshots(execSnapshots)).toMatchSnapshot(); + } + ); + it.each([['1.0.0'], ['2.0.0']])( + 'performs lock file updates using yarn v%s', + async (yarnVersion) => { + const execSnapshots = mockExecAll(exec, { + stdout: yarnVersion, + stderr: '', + }); - getInstalledPath.mockReturnValueOnce('node_modules/yarn'); + getInstalledPath.mockReturnValueOnce('node_modules/yarn'); - fs.readFile = jest.fn(() => 'package-lock-contents') as never; - process.env.YARN_MUTEX_FILE = '/tmp/yarn.mutext'; - const res = await yarnHelper.generateLockFile('some-dir', {}, {}, [ - { depName: 'some-dep', isLockfileUpdate: true }, - ]); - expect(res.lockFile).toEqual('package-lock-contents'); - expect(fixSnapshots(execSnapshots)).toMatchSnapshot(); - }); - it('detects yarnIntegrity', async () => { - const execSnapshots = mockExecAll(exec); + fs.readFile = jest.fn(() => 'package-lock-contents') as never; + process.env.YARN_MUTEX_FILE = '/tmp/yarn.mutext'; + const res = await yarnHelper.generateLockFile('some-dir', {}, {}, [ + { depName: 'some-dep', isLockfileUpdate: true }, + ]); + expect(res.lockFile).toEqual('package-lock-contents'); + expect(fixSnapshots(execSnapshots)).toMatchSnapshot(); + } + ); + it.each([['1.0.0'], ['2.0.0']])( + 'detects yarnIntegrity using yarn v%s', + async (yarnVersion) => { + const execSnapshots = mockExecAll(exec, { + stdout: yarnVersion, + stderr: '', + }); - getInstalledPath.mockReturnValueOnce('node_modules/yarn'); - fs.readFile = jest.fn(() => 'package-lock-contents') as never; - const config = { - upgrades: [{ yarnIntegrity: true }], - }; - const res = await yarnHelper.generateLockFile('some-dir', {}, config, [ - { depName: 'some-dep', isLockfileUpdate: true }, - ]); - expect(res.lockFile).toEqual('package-lock-contents'); - expect(fixSnapshots(execSnapshots)).toMatchSnapshot(); - }); + getInstalledPath.mockReturnValueOnce('node_modules/yarn'); + fs.readFile = jest.fn(() => 'package-lock-contents') as never; + const config = { + upgrades: [{ yarnIntegrity: true }], + }; + const res = await yarnHelper.generateLockFile('some-dir', {}, config, [ + { depName: 'some-dep', isLockfileUpdate: true }, + ]); + expect(res.lockFile).toEqual('package-lock-contents'); + expect(fixSnapshots(execSnapshots)).toMatchSnapshot(); + } + ); it('catches errors', async () => { getInstalledPath.mockReturnValueOnce('node_modules/yarn'); const execSnapshots = mockExecAll(exec, { - stdout: '', + stdout: '1.9.4', stderr: 'some-error', }); fs.readFile = jest.fn(() => { @@ -87,7 +105,10 @@ describe('generateLockFile', () => { expect(fixSnapshots(execSnapshots)).toMatchSnapshot(); }); it('finds yarn embedded in renovate', async () => { - const execSnapshots = mockExecAll(exec); + const execSnapshots = mockExecAll(exec, { + stdout: '1.9.4', + stderr: '', + }); getInstalledPath.mockImplementationOnce(() => { throw new Error('not found'); }); @@ -101,24 +122,33 @@ describe('generateLockFile', () => { expect(res.lockFile).toEqual('package-lock-contents'); expect(fixSnapshots(execSnapshots)).toMatchSnapshot(); }); - it('finds yarn globally', async () => { - const execSnapshots = mockExecAll(exec); - getInstalledPath.mockImplementationOnce(() => { - throw new Error('not found'); - }); - getInstalledPath.mockImplementationOnce(() => '/node_modules/renovate'); - getInstalledPath.mockImplementationOnce(() => { - throw new Error('not found'); - }); - getInstalledPath.mockImplementationOnce(() => '/node_modules/yarn'); - fs.readFile = jest.fn(() => 'package-lock-contents') as never; - const res = await yarnHelper.generateLockFile('some-dir'); - expect(fs.readFile).toHaveBeenCalledTimes(1); - expect(res.lockFile).toEqual('package-lock-contents'); - expect(fixSnapshots(execSnapshots)).toMatchSnapshot(); - }); + it.each([['1.0.0'], ['2.0.0']])( + 'finds yarn v%s globally', + async (yarnVersion) => { + const execSnapshots = mockExecAll(exec, { + stdout: yarnVersion, + stderr: '', + }); + getInstalledPath.mockImplementationOnce(() => { + throw new Error('not found'); + }); + getInstalledPath.mockImplementationOnce(() => '/node_modules/renovate'); + getInstalledPath.mockImplementationOnce(() => { + throw new Error('not found'); + }); + getInstalledPath.mockImplementationOnce(() => '/node_modules/yarn'); + fs.readFile = jest.fn(() => 'package-lock-contents') as never; + const res = await yarnHelper.generateLockFile('some-dir'); + expect(fs.readFile).toHaveBeenCalledTimes(1); + expect(res.lockFile).toEqual('package-lock-contents'); + expect(fixSnapshots(execSnapshots)).toMatchSnapshot(); + } + ); it('uses fallback yarn', async () => { - const execSnapshots = mockExecAll(exec); + const execSnapshots = mockExecAll(exec, { + stdout: '1.9.4', + stderr: '', + }); getInstalledPath.mockImplementationOnce(() => { throw new Error('not found'); });