From 3b699e9f7eeb14c184f4571d5b7728df7acfcd01 Mon Sep 17 00:00:00 2001 From: Michael Kriese Date: Thu, 4 Aug 2022 21:18:43 +0200 Subject: [PATCH] feat(manager/npm): prepare for buildpack support (#16979) * feat(manager/npm): prepare for buildpack support * chore: fixes --- .../__snapshots__/npm.spec.ts.snap | 12 --- .../npm/post-update/node-version.spec.ts | 26 +++++- .../manager/npm/post-update/node-version.ts | 19 +++- .../manager/npm/post-update/npm.spec.ts | 91 +++++++++++++++++-- .../manager/npm/post-update/pnpm.spec.ts | 59 ++++++++++++ lib/modules/manager/types.ts | 2 + lib/util/exec/buildpack.spec.ts | 4 +- lib/util/exec/buildpack.ts | 5 + 8 files changed, 194 insertions(+), 24 deletions(-) diff --git a/lib/modules/manager/npm/post-update/__snapshots__/npm.spec.ts.snap b/lib/modules/manager/npm/post-update/__snapshots__/npm.spec.ts.snap index df3d739b113d73..5e3efffe468929 100644 --- a/lib/modules/manager/npm/post-update/__snapshots__/npm.spec.ts.snap +++ b/lib/modules/manager/npm/post-update/__snapshots__/npm.spec.ts.snap @@ -1,9 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`modules/manager/npm/post-update/npm catches errors 1`] = `Array []`; - -exports[`modules/manager/npm/post-update/npm finds npm globally 1`] = `Array []`; - exports[`modules/manager/npm/post-update/npm generates lock files 1`] = ` Array [ Object { @@ -45,8 +41,6 @@ Array [ ] `; -exports[`modules/manager/npm/post-update/npm performs full install 1`] = `Array []`; - exports[`modules/manager/npm/post-update/npm performs lock file maintenance 1`] = ` Array [ Object { @@ -202,9 +196,3 @@ Array [ }, ] `; - -exports[`modules/manager/npm/post-update/npm performs npm-shrinkwrap.json updates (no package-lock.json) 1`] = `Array []`; - -exports[`modules/manager/npm/post-update/npm performs npm-shrinkwrap.json updates 1`] = `Array []`; - -exports[`modules/manager/npm/post-update/npm uses docker npm 1`] = `Array []`; diff --git a/lib/modules/manager/npm/post-update/node-version.spec.ts b/lib/modules/manager/npm/post-update/node-version.spec.ts index fc222af12a0484..b1953b75b2a960 100644 --- a/lib/modules/manager/npm/post-update/node-version.spec.ts +++ b/lib/modules/manager/npm/post-update/node-version.spec.ts @@ -1,5 +1,9 @@ import { fs } from '../../../../../test/util'; -import { getNodeConstraint, getNodeUpdate } from './node-version'; +import { + getNodeConstraint, + getNodeToolConstraint, + getNodeUpdate, +} from './node-version'; jest.mock('../../../../util/fs'); @@ -56,4 +60,24 @@ describe('modules/manager/npm/post-update/node-version', () => { expect(getNodeUpdate([])).toBeUndefined(); }); }); + + describe('getNodeToolContraint()', () => { + it('returns getNodeUpdate', async () => { + expect( + await getNodeToolConstraint(config, [ + { depName: 'node', newValue: '16.15.0' }, + ]) + ).toEqual({ + toolName: 'node', + constraint: '16.15.0', + }); + }); + + it('returns getNodeConstraint', async () => { + expect(await getNodeToolConstraint(config, [])).toEqual({ + toolName: 'node', + constraint: '^12.16.0', + }); + }); + }); }); diff --git a/lib/modules/manager/npm/post-update/node-version.ts b/lib/modules/manager/npm/post-update/node-version.ts index b856a416512e6e..6bf62de865f3f3 100644 --- a/lib/modules/manager/npm/post-update/node-version.ts +++ b/lib/modules/manager/npm/post-update/node-version.ts @@ -1,5 +1,6 @@ import semver from 'semver'; import { logger } from '../../../../logger'; +import type { ToolConstraint } from '../../../../util/exec/types'; import { getSiblingFileName, readLocalFile } from '../../../../util/fs'; import { newlineRegex, regEx } from '../../../../util/regex'; import type { PostUpdateConfig, Upgrade } from '../../types'; @@ -35,9 +36,10 @@ export async function getNodeConstraint( config: Partial ): Promise { const { packageFile } = config; + // TODO: fix types (#7154) const constraint = - (await getNodeFile(getSiblingFileName(packageFile, '.nvmrc'))) ?? - (await getNodeFile(getSiblingFileName(packageFile, '.node-version'))) ?? + (await getNodeFile(getSiblingFileName(packageFile!, '.nvmrc'))) ?? + (await getNodeFile(getSiblingFileName(packageFile!, '.node-version'))) ?? getPackageJsonConstraint(config); if (!constraint) { logger.debug('No node constraint found - using latest'); @@ -48,3 +50,16 @@ export async function getNodeConstraint( export function getNodeUpdate(upgrades: Upgrade[]): string | undefined { return upgrades.find((u) => u.depName === 'node')?.newValue; } + +export async function getNodeToolConstraint( + config: Partial, + upgrades: Upgrade[] +): Promise { + const constraint = + getNodeUpdate(upgrades) ?? (await getNodeConstraint(config)); + + return { + toolName: 'node', + constraint, + }; +} diff --git a/lib/modules/manager/npm/post-update/npm.spec.ts b/lib/modules/manager/npm/post-update/npm.spec.ts index 9c1d91e04a4821..6bdb012a7b115f 100644 --- a/lib/modules/manager/npm/post-update/npm.spec.ts +++ b/lib/modules/manager/npm/post-update/npm.spec.ts @@ -1,20 +1,27 @@ import upath from 'upath'; import { envMock, mockExecAll } from '../../../../../test/exec-util'; import { Fixtures } from '../../../../../test/fixtures'; -import { env, fs } from '../../../../../test/util'; +import { env, fs, mockedFunction } from '../../../../../test/util'; import { GlobalConfig } from '../../../../config/global'; +import { getNodeToolConstraint } from './node-version'; import * as npmHelper from './npm'; jest.mock('../../../../util/exec/env'); jest.mock('../../../../util/fs'); jest.mock('./node-version'); +process.env.BUILDPACK = 'true'; + describe('modules/manager/npm/post-update/npm', () => { beforeEach(() => { jest.resetAllMocks(); jest.resetModules(); env.getChildProcessEnv.mockReturnValue(envMock.basic); GlobalConfig.set({ localDir: '' }); + mockedFunction(getNodeToolConstraint).mockResolvedValueOnce({ + toolName: 'node', + constraint: '16.16.0', + }); }); it('generates lock files', async () => { @@ -109,7 +116,8 @@ describe('modules/manager/npm/post-update/npm', () => { ); expect(res.error).toBeUndefined(); expect(res.lockFile).toBe('package-lock-contents'); - expect(execSnapshots).toMatchSnapshot(); + // TODO: is that right? + expect(execSnapshots).toEqual([]); }); it('performs npm-shrinkwrap.json updates (no package-lock.json)', async () => { @@ -131,7 +139,8 @@ describe('modules/manager/npm/post-update/npm', () => { ); expect(res.error).toBeUndefined(); expect(res.lockFile).toBe('package-lock-contents'); - expect(execSnapshots).toMatchSnapshot(); + // TODO: is that right? + expect(execSnapshots).toEqual([]); }); it('performs full install', async () => { @@ -148,7 +157,8 @@ describe('modules/manager/npm/post-update/npm', () => { expect(fs.readLocalFile).toHaveBeenCalledTimes(1); expect(res.error).toBeUndefined(); expect(res.lockFile).toBe('package-lock-contents'); - expect(execSnapshots).toMatchSnapshot(); + // TODO: is that right? + expect(execSnapshots).toEqual([]); }); it('runs twice if remediating', async () => { @@ -181,7 +191,7 @@ describe('modules/manager/npm/post-update/npm', () => { expect(fs.readLocalFile).toHaveBeenCalledTimes(1); expect(res.error).toBeTrue(); expect(res.lockFile).toBeUndefined(); - expect(execSnapshots).toMatchSnapshot(); + expect(execSnapshots).toEqual([]); }); it('finds npm globally', async () => { @@ -194,7 +204,8 @@ describe('modules/manager/npm/post-update/npm', () => { ); expect(fs.readLocalFile).toHaveBeenCalledTimes(1); expect(res.lockFile).toBe('package-lock-contents'); - expect(execSnapshots).toMatchSnapshot(); + // TODO: is that right? + expect(execSnapshots).toEqual([]); }); it('uses docker npm', async () => { @@ -208,7 +219,8 @@ describe('modules/manager/npm/post-update/npm', () => { ); expect(fs.readLocalFile).toHaveBeenCalledTimes(1); expect(res.lockFile).toBe('package-lock-contents'); - expect(execSnapshots).toMatchSnapshot(); + // TODO: is that right? + expect(execSnapshots).toEqual([]); }); it('performs lock file maintenance', async () => { @@ -226,4 +238,69 @@ describe('modules/manager/npm/post-update/npm', () => { expect(res.lockFile).toBe('package-lock-contents'); expect(execSnapshots).toMatchSnapshot(); }); + + it('works for docker mode', async () => { + GlobalConfig.set({ + localDir: '', + cacheDir: '/tmp', + binarySource: 'docker', + allowScripts: true, + }); + const execSnapshots = mockExecAll(); + fs.readLocalFile.mockResolvedValue('package-lock-contents'); + const res = await npmHelper.generateLockFile( + 'some-dir', + {}, + 'package-lock.json', + { constraints: { npm: '6.0.0' } }, + [{ isLockFileMaintenance: true }] + ); + expect(fs.readLocalFile).toHaveBeenCalledTimes(1); + expect(res.lockFile).toBe('package-lock-contents'); + expect(execSnapshots).toMatchObject([ + { cmd: 'docker pull renovate/node' }, + { cmd: 'docker ps --filter name=renovate_node -aq' }, + { + cmd: + 'docker run --rm --name=renovate_node --label=renovate_child ' + + '-v "/tmp":"/tmp" ' + + '-e BUILDPACK_CACHE_DIR ' + + '-w "some-dir" ' + + 'renovate/node ' + + 'bash -l -c "' + + 'install-tool npm 6.0.0 ' + + '&& ' + + 'hash -d npm 2>/dev/null || true ' + + '&& ' + + 'npm install --package-lock-only --no-audit' + + '"', + }, + ]); + }); + + it('works for install mode', async () => { + GlobalConfig.set({ + localDir: '', + cacheDir: '/tmp', + binarySource: 'install', + }); + const execSnapshots = mockExecAll(); + fs.readLocalFile.mockResolvedValue('package-lock-contents'); + const res = await npmHelper.generateLockFile( + 'some-dir', + {}, + 'package-lock.json', + { constraints: { npm: '6.0.0' } }, + [{ isLockFileMaintenance: true }] + ); + expect(fs.readLocalFile).toHaveBeenCalledTimes(1); + expect(res.lockFile).toBe('package-lock-contents'); + expect(execSnapshots).toMatchObject([ + { cmd: 'install-tool npm 6.0.0' }, + { cmd: 'hash -d npm 2>/dev/null || true' }, + { + cmd: 'npm install --package-lock-only --no-audit --ignore-scripts', + }, + ]); + }); }); diff --git a/lib/modules/manager/npm/post-update/pnpm.spec.ts b/lib/modules/manager/npm/post-update/pnpm.spec.ts index 2924256993d8b7..c0ee55d699d708 100644 --- a/lib/modules/manager/npm/post-update/pnpm.spec.ts +++ b/lib/modules/manager/npm/post-update/pnpm.spec.ts @@ -10,6 +10,7 @@ jest.mock('../../../../util/fs'); jest.mock('./node-version'); delete process.env.NPM_CONFIG_CACHE; +process.env.BUILDPACK = 'true'; describe('modules/manager/npm/post-update/pnpm', () => { let config: PostUpdateConfig; @@ -178,4 +179,62 @@ describe('modules/manager/npm/post-update/pnpm', () => { expect(fs.readLocalFile).toHaveBeenCalledTimes(3); expect(res.lockFile).toBe('lockfileVersion: 5.3\n'); }); + + it('works for docker mode', async () => { + GlobalConfig.set({ + localDir: '', + cacheDir: '/tmp', + binarySource: 'docker', + allowScripts: true, + }); + const execSnapshots = mockExecAll(); + fs.readLocalFile.mockResolvedValue('package-lock-contents'); + const res = await pnpmHelper.generateLockFile( + 'some-dir', + {}, + { ...config, constraints: { pnpm: '6.0.0' } } + ); + expect(fs.readLocalFile).toHaveBeenCalledTimes(1); + expect(res.lockFile).toBe('package-lock-contents'); + expect(execSnapshots).toMatchObject([ + { cmd: 'docker pull renovate/node' }, + { cmd: 'docker ps --filter name=renovate_node -aq' }, + { + cmd: + 'docker run --rm --name=renovate_node --label=renovate_child ' + + '-v "/tmp":"/tmp" ' + + '-e BUILDPACK_CACHE_DIR ' + + '-w "some-dir" ' + + 'renovate/node ' + + 'bash -l -c "' + + 'install-tool pnpm 6.0.0 ' + + '&& ' + + 'pnpm install --recursive --lockfile-only' + + '"', + }, + ]); + }); + + it('works for install mode', async () => { + GlobalConfig.set({ + localDir: '', + cacheDir: '/tmp', + binarySource: 'install', + }); + const execSnapshots = mockExecAll(); + fs.readLocalFile.mockResolvedValue('package-lock-contents'); + const res = await pnpmHelper.generateLockFile( + 'some-dir', + {}, + { ...config, constraints: { pnpm: '6.0.0' } } + ); + expect(fs.readLocalFile).toHaveBeenCalledTimes(1); + expect(res.lockFile).toBe('package-lock-contents'); + expect(execSnapshots).toMatchObject([ + { cmd: 'install-tool pnpm 6.0.0' }, + { + cmd: 'pnpm install --recursive --lockfile-only --ignore-scripts --ignore-pnpmfile', + }, + ]); + }); }); diff --git a/lib/modules/manager/types.ts b/lib/modules/manager/types.ts index 38b81ff98041c0..45797482dda734 100644 --- a/lib/modules/manager/types.ts +++ b/lib/modules/manager/types.ts @@ -288,6 +288,8 @@ export interface PostUpdateConfig> skipInstalls?: boolean; ignoreScripts?: boolean; + packageFile?: string; + upgrades: Upgrade[]; npmLock?: string; yarnLock?: string; diff --git a/lib/util/exec/buildpack.spec.ts b/lib/util/exec/buildpack.spec.ts index 7f489a182bd042..ca03191ac6bb7c 100644 --- a/lib/util/exec/buildpack.spec.ts +++ b/lib/util/exec/buildpack.spec.ts @@ -33,12 +33,12 @@ describe('util/exec/buildpack', () => { process.env.BUILDPACK = 'true'; const toolConstraints: ToolConstraint[] = [ { toolName: 'node' }, - { toolName: 'npm' }, + { toolName: 'invalid' }, ]; expect(isDynamicInstall(toolConstraints)).toBeFalse(); }); - it('returns false if supported tools', () => { + it('returns true if supported tools', () => { GlobalConfig.set({ binarySource: 'install' }); process.env.BUILDPACK = 'true'; const toolConstraints: ToolConstraint[] = [{ toolName: 'npm' }]; diff --git a/lib/util/exec/buildpack.ts b/lib/util/exec/buildpack.ts index 97b7e365e58d83..443fa167b526b2 100644 --- a/lib/util/exec/buildpack.ts +++ b/lib/util/exec/buildpack.ts @@ -63,6 +63,11 @@ const allToolConfig: Record = { depName: 'jsonnet-bundler/jsonnet-bundler', versioning: semverVersioningId, }, + node: { + datasource: 'node', + depName: 'node', + versioning: npmVersioningId, + }, npm: { datasource: 'npm', depName: 'npm',