From 6516cc1d5089f533be26b5092b70e13c444f7585 Mon Sep 17 00:00:00 2001 From: Rhys Arkins <rhys@arkins.net> Date: Tue, 2 Jun 2020 14:41:03 +0200 Subject: [PATCH] feat(lerna): use docker exec (#6407) Co-authored-by: Michael Kriese <michael.kriese@visualon.de> --- .../__snapshots__/lerna.spec.ts.snap | 78 ++++++++++++++++ .../npm/post-update}/lerna.spec.ts | 41 ++++++++- lib/manager/npm/post-update/lerna.ts | 91 ++++++++++++------- lib/manager/npm/post-update/yarn.ts | 7 +- 4 files changed, 174 insertions(+), 43 deletions(-) rename lib/{workers/branch/lock-files => manager/npm/post-update}/__snapshots__/lerna.spec.ts.snap (67%) rename lib/{workers/branch/lock-files => manager/npm/post-update}/lerna.spec.ts (66%) diff --git a/lib/workers/branch/lock-files/__snapshots__/lerna.spec.ts.snap b/lib/manager/npm/post-update/__snapshots__/lerna.spec.ts.snap similarity index 67% rename from lib/workers/branch/lock-files/__snapshots__/lerna.spec.ts.snap rename to lib/manager/npm/post-update/__snapshots__/lerna.spec.ts.snap index 4c64806349..f93b73cf05 100644 --- a/lib/workers/branch/lock-files/__snapshots__/lerna.spec.ts.snap +++ b/lib/manager/npm/post-update/__snapshots__/lerna.spec.ts.snap @@ -1,5 +1,44 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`generateLockFiles() allows scripts for trust level high 1`] = ` +Array [ + Object { + "cmd": "npm install --package-lock-only --no-audit", + "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, + }, + }, + Object { + "cmd": "npx lerna@latest bootstrap --no-ci -- --package-lock-only --no-audit", + "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[`generateLockFiles() defaults to latest 1`] = ` Array [ Object { @@ -117,6 +156,45 @@ Array [ ] `; +exports[`generateLockFiles() maps dot files 1`] = ` +Array [ + Object { + "cmd": "npm install --package-lock-only --no-audit", + "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, + }, + }, + Object { + "cmd": "npx lerna@latest bootstrap --no-ci -- --package-lock-only --no-audit", + "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[`generateLockFiles() performs full npm install 1`] = ` Array [ Object { diff --git a/lib/workers/branch/lock-files/lerna.spec.ts b/lib/manager/npm/post-update/lerna.spec.ts similarity index 66% rename from lib/workers/branch/lock-files/lerna.spec.ts rename to lib/manager/npm/post-update/lerna.spec.ts index 4bd78fb957..9438d7ae4a 100644 --- a/lib/workers/branch/lock-files/lerna.spec.ts +++ b/lib/manager/npm/post-update/lerna.spec.ts @@ -1,12 +1,13 @@ import { exec as _exec } from 'child_process'; import { envMock, mockExecAll } from '../../../../test/execUtil'; import { mocked } from '../../../../test/util'; -import * as _lernaHelper from '../../../manager/npm/post-update/lerna'; import { platform as _platform } from '../../../platform'; import * as _env from '../../../util/exec/env'; +import * as _lernaHelper from './lerna'; jest.mock('child_process'); jest.mock('../../../util/exec/env'); +jest.mock('../../../manager/npm/post-update/node-version'); const exec: jest.Mock<typeof _exec> = _exec as any; const env = mocked(_env); @@ -20,7 +21,16 @@ describe('generateLockFiles()', () => { env.getChildProcessEnv.mockReturnValue(envMock.basic); }); it('returns if no lernaClient', async () => { - const res = await lernaHelper.generateLockFiles(undefined, 'some-dir', {}); + const res = await lernaHelper.generateLockFiles( + undefined, + 'some-dir', + {}, + {} + ); + expect(res.error).toBe(false); + }); + it('returns if invalid lernaClient', async () => { + const res = await lernaHelper.generateLockFiles('foo', 'some-dir', {}, {}); expect(res.error).toBe(false); }); it('generates package-lock.json files', async () => { @@ -60,14 +70,35 @@ describe('generateLockFiles()', () => { JSON.stringify({ devDependencies: { lerna: '2.0.0' } }) ); const execSnapshots = mockExecAll(exec); - const res = await lernaHelper.generateLockFiles('yarn', 'some-dir', {}); - expect(res.error).toBe(false); + const res = await lernaHelper.generateLockFiles('yarn', 'some-dir', {}, {}); expect(execSnapshots).toMatchSnapshot(); + expect(res.error).toBe(false); }); it('defaults to latest', async () => { platform.getFile.mockReturnValueOnce(undefined); const execSnapshots = mockExecAll(exec); - const res = await lernaHelper.generateLockFiles('npm', 'some-dir', {}); + const res = await lernaHelper.generateLockFiles('npm', 'some-dir', {}, {}); + expect(res.error).toBe(false); + expect(execSnapshots).toMatchSnapshot(); + }); + it('maps dot files', async () => { + platform.getFile.mockReturnValueOnce(undefined); + const execSnapshots = mockExecAll(exec); + const res = await lernaHelper.generateLockFiles( + 'npm', + 'some-dir', + { dockerMapDotfiles: true }, + {} + ); + expect(res.error).toBe(false); + expect(execSnapshots).toMatchSnapshot(); + }); + it('allows scripts for trust level high', async () => { + platform.getFile.mockReturnValueOnce(undefined); + const execSnapshots = mockExecAll(exec); + global.trustLevel = 'high'; + const res = await lernaHelper.generateLockFiles('npm', 'some-dir', {}, {}); + delete global.trustLevel; expect(res.error).toBe(false); expect(execSnapshots).toMatchSnapshot(); }); diff --git a/lib/manager/npm/post-update/lerna.ts b/lib/manager/npm/post-update/lerna.ts index ef5cd9bd4a..d449ac198f 100644 --- a/lib/manager/npm/post-update/lerna.ts +++ b/lib/manager/npm/post-update/lerna.ts @@ -1,8 +1,12 @@ +import semver from 'semver'; import { quote } from 'shlex'; +import { join } from 'upath'; import { logger } from '../../../logger'; import { platform } from '../../../platform'; -import { exec } from '../../../util/exec'; +import { ExecOptions, exec } from '../../../util/exec'; import { PostUpdateConfig } from '../../common'; +import { getNodeConstraint } from './node-version'; +import { optimizeCommand } from './yarn'; export interface GenerateLockFileResult { error?: boolean; @@ -13,7 +17,7 @@ export async function generateLockFiles( lernaClient: string, cwd: string, config: PostUpdateConfig, - env?: NodeJS.ProcessEnv, + env: NodeJS.ProcessEnv, skipInstalls?: boolean ): Promise<GenerateLockFileResult> { if (!lernaClient) { @@ -21,11 +25,51 @@ export async function generateLockFiles( return { error: false }; } logger.debug(`Spawning lerna with ${lernaClient} to create lock files`); - const cmd: string[] = []; - // const envVars = ['NPM_CONFIG_CACHE', 'npm_config_store']; + const preCommands = []; + const cmd = []; + let cmdOptions = ''; try { + if (lernaClient === 'yarn') { + preCommands.push('npm i -g yarn'); + if (skipInstalls !== false) { + preCommands.push(optimizeCommand); + } + cmdOptions = '--ignore-scripts --ignore-engines --ignore-platform'; + } else if (lernaClient === 'npm') { + if (skipInstalls === false) { + cmdOptions = '--ignore-scripts --no-audit'; + } else { + cmdOptions = '--package-lock-only --no-audit'; + } + } else { + logger.warn({ lernaClient }, 'Unknown lernaClient'); + return { error: false }; + } + if (global.trustLevel === 'high' && config.ignoreScripts !== false) { + cmdOptions = cmdOptions.replace('--ignore-scripts ', ''); + } + const tagConstraint = await getNodeConstraint(config.packageFile); + const execOptions: ExecOptions = { + cwd, + extraEnv: { + NPM_CONFIG_CACHE: env.NPM_CONFIG_CACHE, + npm_config_store: env.npm_config_store, + }, + docker: { + image: 'renovate/node', + tagScheme: 'npm', + tagConstraint, + preCommands, + }, + }; + if (config.dockerMapDotfiles) { + const homeDir = + process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE; + const homeNpmrc = join(homeDir, '.npmrc'); + execOptions.docker.volumes = [[homeNpmrc, '/home/ubuntu/.npmrc']]; + } + cmd.push(`${lernaClient} install ${cmdOptions}`); let lernaVersion: string; - // const volumes: VolumeOption[] = []; try { const pJson = JSON.parse(await platform.getFile('package.json')); lernaVersion = @@ -34,37 +78,14 @@ export async function generateLockFiles( } catch (err) { logger.warn('Could not detect lerna version in package.json'); } - lernaVersion = lernaVersion || 'latest'; - logger.debug('Using lerna version ' + lernaVersion); - let params: string; - if (lernaClient === 'npm') { - if (skipInstalls === false) { - params = '--ignore-scripts --no-audit'; - } else { - params = '--package-lock-only --no-audit'; - } - } else { - params = '--ignore-scripts --ignore-engines --ignore-platform'; + if (!lernaVersion || !semver.validRange(lernaVersion)) { + lernaVersion = 'latest'; } - - // // istanbul ignore if - // if (config.dockerMapDotfiles) { - // const homeDir = - // process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE; - // const homeNpmrc = join(homeDir, '.npmrc'); - // volumes.push([homeNpmrc, `/home/ubuntu/.npmrc`]); - // } - cmd.push(`${lernaClient} install ${params}`); - cmd.push(`npx lerna@${quote(lernaVersion)} bootstrap --no-ci -- ${params}`); - await exec(cmd, { - cwd, - env, - // docker: { - // image: `renovate/${lernaClient}`, - // volumes, - // envVars, - // }, - }); + logger.debug('Using lerna version ' + lernaVersion); + cmd.push( + `npx lerna@${quote(lernaVersion)} bootstrap --no-ci -- ${cmdOptions}` + ); + await exec(cmd, execOptions); } catch (err) /* istanbul ignore next */ { logger.debug( { diff --git a/lib/manager/npm/post-update/yarn.ts b/lib/manager/npm/post-update/yarn.ts index 8a5c71a0ea..44f19d48b1 100644 --- a/lib/manager/npm/post-update/yarn.ts +++ b/lib/manager/npm/post-update/yarn.ts @@ -31,6 +31,9 @@ export async function hasYarnOfflineMirror(cwd: string): Promise<boolean> { return false; } +export const optimizeCommand = + "sed -i 's/ steps,/ steps.slice(0,1),/' /home/ubuntu/.npm-global/lib/node_modules/yarn/lib/cli.js"; + export async function generateLockFile( cwd: string, env: NodeJS.ProcessEnv, @@ -47,9 +50,7 @@ export async function generateLockFile( ) { logger.debug('Updating yarn.lock only - skipping node_modules'); // The following change causes Yarn 1.x to exit gracefully after updating the lock file but without installing node_modules - preCommands.push( - "sed -i 's/ steps,/ steps.slice(0,1),/' /home/ubuntu/.npm-global/lib/node_modules/yarn/lib/cli.js" - ); + preCommands.push(optimizeCommand); } const commands = []; let cmdOptions = '--network-timeout 100000'; -- GitLab