diff --git a/docs/usage/self-hosted-configuration.md b/docs/usage/self-hosted-configuration.md index 2525f100708a0d..f3567222b3f933 100644 --- a/docs/usage/self-hosted-configuration.md +++ b/docs/usage/self-hosted-configuration.md @@ -28,7 +28,7 @@ module.exports = { In the `renovate.json` file, define the commands and files to be included in the final commit. -The command to install dependencies (`npm ci --ignore-scripts`) is necessary because, by default, the installation of dependencies is skipped (see the `skipInstalls` admin option). +The command to install dependencies (`npm ci --ignore-scripts`) is necessary because, by default, the installation of dependencies is skipped (see the `artifactUpdateApproach` admin option). ```json { @@ -69,6 +69,12 @@ e.g. } ``` +## artifactUpdateApproach + +By default, Renovate will use the most efficient approach to updating package files and lock files, which in most cases skips the need to perform a full module install by the bot. +If this is set to 'deep', then a full install of modules will be done. +This is currently applicable to `npm` and `yarn` only, and automatically set to `deep` when a full install is detected as necessary. + ## autodiscover When you enable `autodiscover`, by default, Renovate will run on _every_ repository that the bot account can access. @@ -377,12 +383,6 @@ It could then be used in a repository config or preset like so: Secret names must start with a upper or lower case character and can contain only characters, digits, or underscores. -## skipInstalls - -By default, Renovate will use the most efficient approach to updating package files and lock files, which in most cases skips the need to perform a full module install by the bot. -If this is set to false, then a full install of modules will be done. -This is currently applicable to `npm` and `lerna`/`npm` only, and only used in cases where bugs in `npm` result in incorrect lock files being updated. - ## token ## username diff --git a/lib/config/__snapshots__/migration.spec.ts.snap b/lib/config/__snapshots__/migration.spec.ts.snap index a9d93e6307f2b3..27d5183112d3f3 100644 --- a/lib/config/__snapshots__/migration.spec.ts.snap +++ b/lib/config/__snapshots__/migration.spec.ts.snap @@ -80,6 +80,7 @@ Object { "additionalBranchPrefix": "{{parentDir}}-", "allowCustomCrateRegistries": true, "allowScripts": true, + "artifactUpdateApproach": "shallow", "autodiscover": true, "automerge": false, "automergeType": "branch", @@ -173,6 +174,7 @@ Object { "versioning": "maven", }, Object { + "artifactUpdateApproach": "deep", "matchDepTypes": Array [ "peerDependencies", ], diff --git a/lib/config/definitions.ts b/lib/config/definitions.ts index 7ba77d22b16033..6db106c11f72c6 100644 --- a/lib/config/definitions.ts +++ b/lib/config/definitions.ts @@ -564,11 +564,12 @@ const options: RenovateOptions[] = [ type: 'boolean', }, { - name: 'skipInstalls', + name: 'artifactUpdateApproach', description: - 'Skip installing modules/dependencies if lock file updating is possible alone.', - type: 'boolean', - default: null, + 'Whether to employ a deep or shallow approach to artifact updating.', + type: 'string', + allowedValues: ['auto', 'deep', 'shallow'], + default: 'auto', admin: true, }, { diff --git a/lib/config/migration.spec.ts b/lib/config/migration.spec.ts index 6f19f5c6cacffe..477b523b2ead92 100644 --- a/lib/config/migration.spec.ts +++ b/lib/config/migration.spec.ts @@ -52,6 +52,7 @@ describe(getName(__filename), () => { binarySource: 'auto', automergeMinor: true, automergePatch: true, + skipInstalls: true, masterIssue: 'true', masterIssueTitle: 'foo', gomodTidy: true, @@ -95,6 +96,7 @@ describe(getName(__filename), () => { ], peerDependencies: { versionStrategy: 'widen', + skipInstalls: false, }, packageRules: [ { diff --git a/lib/config/migration.ts b/lib/config/migration.ts index 32ddd4566b6eaa..982da884faa34c 100644 --- a/lib/config/migration.ts +++ b/lib/config/migration.ts @@ -172,6 +172,13 @@ export function migrateConfig( migratedConfig[key] = val.replace(/{{depNameShort}}/g, '{{depName}}'); } else if (key === 'gitFs') { delete migratedConfig.gitFs; + } else if (key === 'skipInstalls') { + delete migratedConfig.skipInstalls; + if (val) { + migratedConfig.artifactUpdateApproach = 'shallow'; + } else { + migratedConfig.artifactUpdateApproach = 'deep'; + } } else if (key === 'rebaseStalePrs') { delete migratedConfig.rebaseStalePrs; if (!migratedConfig.rebaseWhen) { diff --git a/lib/manager/npm/extract/__snapshots__/index.spec.ts.snap b/lib/manager/npm/extract/__snapshots__/index.spec.ts.snap index e0b886ce2fb95c..3200c3f4e36426 100644 --- a/lib/manager/npm/extract/__snapshots__/index.spec.ts.snap +++ b/lib/manager/npm/extract/__snapshots__/index.spec.ts.snap @@ -15,6 +15,7 @@ Object { "lernaClient": undefined, "lernaPackages": undefined, "managerData": Object { + "hasFileRefs": false, "lernaJsonFile": undefined, }, "npmLock": undefined, @@ -23,7 +24,6 @@ Object { "packageJsonName": undefined, "packageJsonType": "app", "pnpmShrinkwrap": undefined, - "skipInstalls": true, "yarnLock": undefined, "yarnWorkspacesPackages": undefined, "yarnrc": undefined, @@ -139,6 +139,7 @@ Object { "lernaClient": undefined, "lernaPackages": undefined, "managerData": Object { + "hasFileRefs": true, "lernaJsonFile": undefined, }, "npmLock": undefined, @@ -147,7 +148,6 @@ Object { "packageJsonName": undefined, "packageJsonType": "library", "pnpmShrinkwrap": undefined, - "skipInstalls": false, "yarnLock": undefined, "yarnWorkspacesPackages": undefined, "yarnrc": undefined, @@ -303,6 +303,7 @@ Object { "lernaClient": undefined, "lernaPackages": undefined, "managerData": Object { + "hasFileRefs": false, "lernaJsonFile": undefined, }, "npmLock": undefined, @@ -311,7 +312,6 @@ Object { "packageJsonName": undefined, "packageJsonType": "app", "pnpmShrinkwrap": undefined, - "skipInstalls": true, "yarnLock": undefined, "yarnWorkspacesPackages": undefined, "yarnrc": undefined, @@ -353,6 +353,7 @@ Object { "lernaClient": undefined, "lernaPackages": undefined, "managerData": Object { + "hasFileRefs": false, "lernaJsonFile": undefined, }, "npmLock": undefined, @@ -361,7 +362,6 @@ Object { "packageJsonName": undefined, "packageJsonType": "app", "pnpmShrinkwrap": undefined, - "skipInstalls": false, "yarnLock": undefined, "yarnWorkspacesPackages": undefined, "yarnrc": undefined, @@ -414,6 +414,7 @@ Object { "lernaClient": undefined, "lernaPackages": undefined, "managerData": Object { + "hasFileRefs": false, "lernaJsonFile": undefined, }, "npmLock": undefined, @@ -422,7 +423,6 @@ Object { "packageJsonName": undefined, "packageJsonType": "library", "pnpmShrinkwrap": undefined, - "skipInstalls": true, "yarnLock": undefined, "yarnWorkspacesPackages": undefined, "yarnrc": undefined, @@ -469,6 +469,7 @@ Object { "lernaClient": undefined, "lernaPackages": undefined, "managerData": Object { + "hasFileRefs": false, "lernaJsonFile": undefined, }, "npmLock": undefined, @@ -477,7 +478,6 @@ Object { "packageJsonName": undefined, "packageJsonType": "library", "pnpmShrinkwrap": undefined, - "skipInstalls": true, "yarnLock": undefined, "yarnWorkspacesPackages": undefined, "yarnrc": undefined, @@ -606,6 +606,7 @@ Object { "lernaClient": "npm", "lernaPackages": undefined, "managerData": Object { + "hasFileRefs": false, "lernaJsonFile": "lerna.json", }, "npmLock": undefined, @@ -614,7 +615,6 @@ Object { "packageJsonName": "renovate", "packageJsonType": "app", "pnpmShrinkwrap": undefined, - "skipInstalls": true, "yarnLock": undefined, "yarnWorkspacesPackages": undefined, "yarnrc": undefined, @@ -743,6 +743,7 @@ Object { "lernaClient": "yarn", "lernaPackages": undefined, "managerData": Object { + "hasFileRefs": false, "lernaJsonFile": "lerna.json", }, "npmLock": undefined, @@ -751,7 +752,6 @@ Object { "packageJsonName": "renovate", "packageJsonType": "app", "pnpmShrinkwrap": undefined, - "skipInstalls": true, "yarnLock": undefined, "yarnWorkspacesPackages": undefined, "yarnrc": undefined, @@ -880,6 +880,7 @@ Object { "lernaClient": undefined, "lernaPackages": undefined, "managerData": Object { + "hasFileRefs": false, "lernaJsonFile": undefined, }, "npmLock": undefined, @@ -888,7 +889,6 @@ Object { "packageJsonName": "renovate", "packageJsonType": "app", "pnpmShrinkwrap": undefined, - "skipInstalls": true, "yarnLock": "yarn.lock", "yarnWorkspacesPackages": undefined, "yarnrc": undefined, @@ -903,6 +903,7 @@ Object { "lernaClient": "npm", "lernaPackages": undefined, "managerData": Object { + "hasFileRefs": false, "lernaJsonFile": "lerna.json", }, "npmLock": undefined, @@ -911,7 +912,6 @@ Object { "packageJsonName": "@a/b", "packageJsonType": "app", "pnpmShrinkwrap": undefined, - "skipInstalls": true, "yarnLock": undefined, "yarnWorkspacesPackages": Array [ "packages/*", @@ -1042,6 +1042,7 @@ Object { "lernaClient": "npm", "lernaPackages": undefined, "managerData": Object { + "hasFileRefs": false, "lernaJsonFile": "lerna.json", }, "npmLock": undefined, @@ -1050,7 +1051,6 @@ Object { "packageJsonName": "renovate", "packageJsonType": "app", "pnpmShrinkwrap": undefined, - "skipInstalls": true, "yarnLock": undefined, "yarnWorkspacesPackages": undefined, "yarnrc": undefined, @@ -1065,6 +1065,7 @@ Object { "lernaClient": "npm", "lernaPackages": undefined, "managerData": Object { + "hasFileRefs": false, "lernaJsonFile": "lerna.json", }, "npmLock": undefined, @@ -1073,7 +1074,6 @@ Object { "packageJsonName": "@a/b", "packageJsonType": "app", "pnpmShrinkwrap": undefined, - "skipInstalls": true, "yarnLock": undefined, "yarnWorkspacesPackages": Array [ "packages/*", @@ -1090,6 +1090,7 @@ Object { "lernaClient": undefined, "lernaPackages": undefined, "managerData": Object { + "hasFileRefs": false, "lernaJsonFile": undefined, }, "npmLock": undefined, @@ -1098,7 +1099,6 @@ Object { "packageJsonName": "@a/b", "packageJsonType": "app", "pnpmShrinkwrap": undefined, - "skipInstalls": true, "yarnLock": undefined, "yarnWorkspacesPackages": Array [ "packages/*", @@ -1229,6 +1229,7 @@ Object { "lernaClient": undefined, "lernaPackages": undefined, "managerData": Object { + "hasFileRefs": false, "lernaJsonFile": undefined, }, "npmLock": undefined, @@ -1237,7 +1238,6 @@ Object { "packageJsonName": "renovate", "packageJsonType": "app", "pnpmShrinkwrap": undefined, - "skipInstalls": true, "yarnLock": undefined, "yarnWorkspacesPackages": undefined, "yarnrc": undefined, diff --git a/lib/manager/npm/extract/index.ts b/lib/manager/npm/extract/index.ts index aacf7d55444bef..727484819f6183 100644 --- a/lib/manager/npm/extract/index.ts +++ b/lib/manager/npm/extract/index.ts @@ -126,7 +126,7 @@ export async function extractPackageFile( let lernaJsonFile: string; let lernaPackages: string[]; let lernaClient: 'yarn' | 'npm'; - let hasFancyRefs = false; + let hasFileRefs = false; let lernaJson: { packages: string[]; npmClient: string; @@ -224,7 +224,6 @@ export async function extractPackageFile( if (dep.currentValue.startsWith('npm:')) { dep.npmPackageAlias = true; - hasFancyRefs = true; const valSplit = dep.currentValue.replace('npm:', '').split('@'); if (valSplit.length === 2) { dep.lookupName = valSplit[0]; @@ -238,7 +237,11 @@ export async function extractPackageFile( } if (dep.currentValue.startsWith('file:')) { dep.skipReason = SkipReason.File; - hasFancyRefs = true; + // https://github.com/npm/cli/issues/1432 + // Explanation: + // - npm install --package-lock-only is buggy for transitive deps in file: references + // - So we set artifactUpdateApproach to false if file: refs are found *and* the user hasn't explicitly set the value already + hasFileRefs = true; return dep; } if (isValid(dep.currentValue)) { @@ -344,19 +347,6 @@ export async function extractPackageFile( return null; } } - let skipInstalls = config.skipInstalls; - if (skipInstalls === null) { - if (hasFancyRefs) { - // https://github.com/npm/cli/issues/1432 - // Explanation: - // - npm install --package-lock-only is buggy for transitive deps in file: and npm: references - // - So we set skipInstalls to false if file: or npm: refs are found *and* the user hasn't explicitly set the value already - logger.debug('Automatically setting skipInstalls to false'); - skipInstalls = false; - } else { - skipInstalls = true; - } - } return { deps, @@ -368,11 +358,11 @@ export async function extractPackageFile( yarnrc, ...lockFiles, managerData: { + hasFileRefs, lernaJsonFile, }, lernaClient, lernaPackages, - skipInstalls, yarnWorkspacesPackages, constraints, }; diff --git a/lib/manager/npm/post-update/lerna.spec.ts b/lib/manager/npm/post-update/lerna.spec.ts index b5a596d671bbc5..7ba6d474d67c37 100644 --- a/lib/manager/npm/post-update/lerna.spec.ts +++ b/lib/manager/npm/post-update/lerna.spec.ts @@ -47,26 +47,26 @@ describe(getName(__filename), () => { }); it('generates package-lock.json files', async () => { const execSnapshots = mockExecAll(exec); - const skipInstalls = true; + const artifactUpdateApproach = 'shallow'; const res = await lernaHelper.generateLockFiles( lernaPkgFile('npm'), 'some-dir', {}, {}, - skipInstalls + artifactUpdateApproach ); expect(res.error).toBe(false); expect(execSnapshots).toMatchSnapshot(); }); it('performs full npm install', async () => { const execSnapshots = mockExecAll(exec); - const skipInstalls = false; + const artifactUpdateApproach = 'deep'; const res = await lernaHelper.generateLockFiles( lernaPkgFile('npm'), 'some-dir', {}, {}, - skipInstalls + artifactUpdateApproach ); 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 2ce9e38d4a8362..89946ac87a974a 100644 --- a/lib/manager/npm/post-update/lerna.ts +++ b/lib/manager/npm/post-update/lerna.ts @@ -33,7 +33,7 @@ export async function generateLockFiles( cwd: string, config: PostUpdateConfig, env: NodeJS.ProcessEnv, - skipInstalls?: boolean + artifactUpdateApproach?: string ): Promise { const lernaClient = lernaPackageFile.lernaClient; if (!lernaClient) { @@ -52,7 +52,7 @@ export async function generateLockFiles( installYarn += `@${quote(yarnCompatibility)}`; } preCommands.push(installYarn); - if (skipInstalls !== false) { + if (artifactUpdateApproach !== 'deep') { preCommands.push(getOptimizeCommand()); } cmdOptions = '--ignore-scripts --ignore-engines --ignore-platform'; @@ -64,7 +64,7 @@ export async function generateLockFiles( } preCommands.push(installNpm, 'hash -d npm'); cmdOptions = '--ignore-scripts --no-audit'; - if (skipInstalls !== false) { + if (artifactUpdateApproach !== 'deep') { cmdOptions += ' --package-lock-only'; } } else { diff --git a/lib/manager/npm/post-update/npm.spec.ts b/lib/manager/npm/post-update/npm.spec.ts index f235cb63cc2af1..d17d200d5d37c3 100644 --- a/lib/manager/npm/post-update/npm.spec.ts +++ b/lib/manager/npm/post-update/npm.spec.ts @@ -26,7 +26,7 @@ describe('generateLockFile', () => { it('generates lock files', async () => { const execSnapshots = mockExecAll(exec); fs.readFile = jest.fn(() => 'package-lock-contents') as never; - const skipInstalls = true; + const artifactUpdateApproach = 'deep'; const dockerMapDotfiles = true; const postUpdateOptions = ['npmDedupe']; const updates = [ @@ -36,7 +36,7 @@ describe('generateLockFile', () => { 'some-dir', {}, 'package-lock.json', - { dockerMapDotfiles, skipInstalls, postUpdateOptions }, + { dockerMapDotfiles, artifactUpdateApproach, postUpdateOptions }, updates ); expect(fs.readFile).toHaveBeenCalledTimes(1); @@ -47,7 +47,7 @@ describe('generateLockFile', () => { it('performs lock file updates', async () => { const execSnapshots = mockExecAll(exec); fs.readFile = jest.fn(() => 'package-lock-contents') as never; - const skipInstalls = true; + const artifactUpdateApproach = 'shallow'; const updates = [ { depName: 'some-dep', newVersion: '1.0.1', isLockfileUpdate: true }, ]; @@ -55,7 +55,7 @@ describe('generateLockFile', () => { 'some-dir', {}, 'package-lock.json', - { skipInstalls }, + { artifactUpdateApproach }, updates ); expect(fs.readFile).toHaveBeenCalledTimes(1); @@ -68,12 +68,12 @@ describe('generateLockFile', () => { fs.pathExists.mockResolvedValueOnce(true); fs.move = jest.fn(); fs.readFile = jest.fn(() => 'package-lock-contents') as never; - const skipInstalls = true; + const artifactUpdateApproach = 'shallow'; const res = await npmHelper.generateLockFile( 'some-dir', {}, 'npm-shrinkwrap.json', - { skipInstalls } + { artifactUpdateApproach } ); expect(fs.pathExists).toHaveBeenCalledWith( upath.join('some-dir', 'package-lock.json') @@ -97,12 +97,12 @@ describe('generateLockFile', () => { fs.pathExists.mockResolvedValueOnce(false); fs.move = jest.fn(); fs.readFile = jest.fn((_, _1) => 'package-lock-contents') as never; - const skipInstalls = true; + const artifactUpdateApproach = 'shallow'; const res = await npmHelper.generateLockFile( 'some-dir', {}, 'npm-shrinkwrap.json', - { skipInstalls } + { artifactUpdateApproach } ); expect(fs.pathExists).toHaveBeenCalledWith( upath.join('some-dir', 'package-lock.json') @@ -120,13 +120,13 @@ describe('generateLockFile', () => { it('performs full install', async () => { const execSnapshots = mockExecAll(exec); fs.readFile = jest.fn(() => 'package-lock-contents') as never; - const skipInstalls = false; + const artifactUpdateApproach = 'deep'; const binarySource = BinarySource.Global; const res = await npmHelper.generateLockFile( 'some-dir', {}, 'package-lock.json', - { skipInstalls, binarySource } + { artifactUpdateApproach, binarySource } ); 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 a11bce39231f08..305bcfb09917f8 100644 --- a/lib/manager/npm/post-update/npm.ts +++ b/lib/manager/npm/post-update/npm.ts @@ -26,7 +26,7 @@ export async function generateLockFile( upgrades: Upgrade[] = [] ): Promise { logger.debug(`Spawning npm install to create ${cwd}/${filename}`); - const { skipInstalls, postUpdateOptions } = config; + const { artifactUpdateApproach } = config; let lockFile = null; try { @@ -49,7 +49,7 @@ export async function generateLockFile( const preCommands = [installNpm, 'hash -d npm']; const commands = []; let cmdOptions = ''; - if (postUpdateOptions?.includes('npmDedupe') || skipInstalls === false) { + if (artifactUpdateApproach === 'deep') { logger.debug('Performing node_modules install'); cmdOptions += '--ignore-scripts --no-audit'; } else { diff --git a/lib/manager/npm/post-update/yarn.ts b/lib/manager/npm/post-update/yarn.ts index 5e7d4958273365..be153bbee74291 100644 --- a/lib/manager/npm/post-update/yarn.ts +++ b/lib/manager/npm/post-update/yarn.ts @@ -82,7 +82,7 @@ export async function generateLockFile( CI: 'true', }; - if (isYarn1 && config.skipInstalls !== false) { + if (isYarn1 && config.artifactUpdateApproach !== 'deep') { const { offlineMirror, yarnPath } = await checkYarnrc(cwd); if (!offlineMirror) { logger.debug('Updating yarn.lock only - skipping node_modules'); diff --git a/lib/manager/types.ts b/lib/manager/types.ts index 2c167ecc539103..a0e31935fe05eb 100644 --- a/lib/manager/types.ts +++ b/lib/manager/types.ts @@ -25,7 +25,6 @@ export interface ExtractConfig extends ManagerConfig { aliases?: Record; ignoreNpmrcFile?: boolean; yarnrc?: string; - skipInstalls?: boolean; versioning?: string; updateInternalDeps?: boolean; } @@ -100,7 +99,6 @@ export interface PackageFile> packageJsonType?: 'app' | 'library'; packageFileVersion?: string; parent?: string; - skipInstalls?: boolean; yarnrc?: string; yarnWorkspacesPackages?: string[] | string; matchStrings?: string[]; @@ -277,8 +275,7 @@ export interface PostUpdateConfig extends ManagerConfig, Record { cacheDir?: string; updatedPackageFiles?: File[]; postUpdateOptions?: string[]; - skipInstalls?: boolean; - + artifactUpdateApproach?: 'auto' | 'deep' | 'shallow'; platform?: string; upgrades?: Upgrade[]; npmLock?: string; diff --git a/lib/workers/repository/extract/__snapshots__/manager-files.spec.ts.snap b/lib/workers/repository/extract/__snapshots__/manager-files.spec.ts.snap index 7b05cb15e52b9f..917cd12286f155 100644 --- a/lib/workers/repository/extract/__snapshots__/manager-files.spec.ts.snap +++ b/lib/workers/repository/extract/__snapshots__/manager-files.spec.ts.snap @@ -18,6 +18,7 @@ Array [ "lernaClient": undefined, "lernaPackages": undefined, "managerData": Object { + "hasFileRefs": false, "lernaJsonFile": undefined, }, "npmLock": undefined, @@ -27,7 +28,6 @@ Array [ "packageJsonName": undefined, "packageJsonType": "app", "pnpmShrinkwrap": undefined, - "skipInstalls": undefined, "yarnLock": undefined, "yarnWorkspacesPackages": undefined, "yarnrc": undefined,