diff --git a/lib/workers/branch/lock-files/__snapshots__/npm.spec.ts.snap b/lib/manager/npm/post-update/__snapshots__/npm.spec.ts.snap similarity index 77% rename from lib/workers/branch/lock-files/__snapshots__/npm.spec.ts.snap rename to lib/manager/npm/post-update/__snapshots__/npm.spec.ts.snap index 56cb6db006853a80b1a00268826f8878499ac6ea..319120c337a96b5fee2b12e7731faf0c327f3e91 100644 --- a/lib/workers/branch/lock-files/__snapshots__/npm.spec.ts.snap +++ b/lib/manager/npm/post-update/__snapshots__/npm.spec.ts.snap @@ -6,6 +6,23 @@ exports[`generateLockFile finds npm globally 1`] = `Array []`; exports[`generateLockFile generates lock files 1`] = ` Array [ + Object { + "cmd": "npm install --ignore-scripts --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": "npm dedupe", "options": Object { diff --git a/lib/workers/branch/lock-files/npm.spec.ts b/lib/manager/npm/post-update/npm.spec.ts similarity index 95% rename from lib/workers/branch/lock-files/npm.spec.ts rename to lib/manager/npm/post-update/npm.spec.ts index bd3c92bbc8db3fc8b0015d602a838b60ce2d348e..f27e6db4e7f00a3d72afa8d9860d12b42f4ae9bc 100644 --- a/lib/workers/branch/lock-files/npm.spec.ts +++ b/lib/manager/npm/post-update/npm.spec.ts @@ -3,9 +3,9 @@ import path from 'path'; import _fs from 'fs-extra'; import { envMock, mockExecAll } from '../../../../test/execUtil'; import { mocked } from '../../../../test/util'; -import * as npmHelper from '../../../manager/npm/post-update/npm'; import { BinarySource } from '../../../util/exec/common'; import * as _env from '../../../util/exec/env'; +import * as npmHelper from './npm'; jest.mock('fs-extra'); jest.mock('child_process'); @@ -25,12 +25,17 @@ describe('generateLockFile', () => { const execSnapshots = mockExecAll(exec); fs.readFile = jest.fn(() => 'package-lock-contents') as never; const skipInstalls = true; + const dockerMapDotfiles = true; const postUpdateOptions = ['npmDedupe']; + const updates = [ + { depName: 'some-dep', toVersion: '1.0.1', isLockfileUpdate: false }, + ]; const res = await npmHelper.generateLockFile( 'some-dir', {}, 'package-lock.json', - { skipInstalls, postUpdateOptions } + { dockerMapDotfiles, skipInstalls, postUpdateOptions }, + updates ); expect(fs.readFile).toHaveBeenCalledTimes(1); expect(res.error).toBeUndefined(); diff --git a/lib/manager/npm/post-update/npm.ts b/lib/manager/npm/post-update/npm.ts index 590306e862fd7a3da13980bf96c736beea5afa0b..abf0d19f4444ba3ca0d1f25a51c33ac8453294de 100644 --- a/lib/manager/npm/post-update/npm.ts +++ b/lib/manager/npm/post-update/npm.ts @@ -2,8 +2,7 @@ import { move, pathExists, readFile } from 'fs-extra'; import { join } from 'upath'; import { SYSTEM_INSUFFICIENT_DISK_SPACE } from '../../../constants/error-messages'; import { logger } from '../../../logger'; -import { exec } from '../../../util/exec'; -import { BinarySource } from '../../../util/exec/common'; +import { ExecOptions, exec } from '../../../util/exec'; import { PostUpdateConfig, Upgrade } from '../../common'; export interface GenerateLockFileResult { @@ -11,6 +10,7 @@ export interface GenerateLockFileResult { lockFile?: string; stderr?: string; } + export async function generateLockFile( cwd: string, env: NodeJS.ProcessEnv, @@ -20,84 +20,67 @@ export async function generateLockFile( ): Promise<GenerateLockFileResult> { logger.debug(`Spawning npm install to create ${cwd}/${filename}`); const { skipInstalls, postUpdateOptions } = config; - let lockFile: string = null; - let stdout = ''; - let stderr = ''; - let cmd = 'npm'; - let args = ''; + + let lockFile = null; try { - // istanbul ignore if - if (config.binarySource === BinarySource.Docker) { - logger.debug('Running npm via docker'); - cmd = `docker run --rm `; - // istanbul ignore if - if (config.dockerUser) { - cmd += `--user=${config.dockerUser} `; - } - const volumes = [cwd]; - if (config.cacheDir) { - volumes.push(config.cacheDir); - } - cmd += volumes.map((v) => `-v "${v}":"${v}" `).join(''); - if (config.dockerMapDotfiles) { - const homeDir = - process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE; - const homeNpmrc = join(homeDir, '.npmrc'); - cmd += `-v ${homeNpmrc}:/home/ubuntu/.npmrc `; - } - const envVars = ['NPM_CONFIG_CACHE', 'npm_config_store']; - cmd += envVars.map((e) => `-e ${e} `).join(''); - cmd += `-w "${cwd}" `; - cmd += `renovate/npm npm`; - } - logger.debug(`Using npm: ${cmd}`); - args = `install`; + const preCommands = ['npm i -g npm']; + const commands = []; + let cmdOptions = ''; if ( (postUpdateOptions && postUpdateOptions.includes('npmDedupe')) || skipInstalls === false ) { - logger.debug('Performing full npm install'); - args += ' --ignore-scripts --no-audit'; + logger.debug('Performing node_modules install'); + cmdOptions += '--ignore-scripts --no-audit'; } else { - args += ' --package-lock-only --no-audit'; + logger.debug('Updating lock file only'); + cmdOptions += '--package-lock-only --no-audit'; + } + const execOptions: ExecOptions = { + cwd, + extraEnv: { + NPM_CONFIG_CACHE: env.NPM_CONFIG_CACHE, + npm_config_store: env.npm_config_store, + }, + docker: { + image: 'renovate/node', + 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']]; } - logger.debug(`Using npm: ${cmd} ${args}`); - // istanbul ignore if + if (!upgrades.every((upgrade) => upgrade.isLockfileUpdate)) { - // TODO: Switch to native util.promisify once using only node 8 - ({ stdout, stderr } = await exec(`${cmd} ${args}`, { - cwd, - env, - })); + // This command updates the lock file based on package.json + commands.push(`npm install ${cmdOptions}`.trim()); } + + // rangeStrategy = update-lockfile const lockUpdates = upgrades.filter((upgrade) => upgrade.isLockfileUpdate); if (lockUpdates.length) { logger.debug('Performing lockfileUpdate (npm)'); const updateCmd = - `${cmd} ${args}` + + `npm install ${cmdOptions}` + lockUpdates .map((update) => ` ${update.depName}@${update.toVersion}`) .join(''); - const updateRes = await exec(updateCmd, { - cwd, - env, - }); - stdout += updateRes.stdout ? updateRes.stdout : ''; - stderr += updateRes.stderr ? updateRes.stderr : ''; + commands.push(updateCmd); } - if (postUpdateOptions && postUpdateOptions.includes('npmDedupe')) { + + // postUpdateOptions + if (config.postUpdateOptions?.includes('npmDedupe')) { logger.debug('Performing npm dedupe'); - const dedupeRes = await exec(`${cmd} dedupe`, { - cwd, - env, - }); - stdout += dedupeRes.stdout ? dedupeRes.stdout : ''; - stderr += dedupeRes.stderr ? dedupeRes.stderr : ''; - } - // istanbul ignore if - if (stderr && stderr.includes('ENOSPC: no space left on device')) { - throw new Error(SYSTEM_INSUFFICIENT_DISK_SPACE); + commands.push('npm dedupe'); } + + // Run the commands + await exec(commands, execOptions); + + // massage to shrinkwrap if necessary if ( filename === 'npm-shrinkwrap.json' && (await pathExists(join(cwd, 'package-lock.json'))) @@ -107,19 +90,20 @@ export async function generateLockFile( join(cwd, 'npm-shrinkwrap.json') ); } + + // Read the result lockFile = await readFile(join(cwd, filename), 'utf8'); } catch (err) /* istanbul ignore next */ { logger.debug( { - cmd, - args, err, - stdout, - stderr, type: 'npm', }, 'lock file error' ); + if (err.stderr?.includes('ENOSPC: no space left on device')) { + throw new Error(SYSTEM_INSUFFICIENT_DISK_SPACE); + } return { error: true, stderr: err.stderr }; } return { lockFile };