diff --git a/lib/modules/manager/npm/post-update/__fixtures__/update-lockfile-massage-1/package-lock.json b/lib/modules/manager/npm/post-update/__fixtures__/update-lockfile-massage-1/package-lock.json new file mode 100644 index 0000000000000000000000000000000000000000..3426f0d759cb835e71e10589518455b80bbb35a9 --- /dev/null +++ b/lib/modules/manager/npm/post-update/__fixtures__/update-lockfile-massage-1/package-lock.json @@ -0,0 +1,83 @@ +{ + "name": "update-lockfile-massage-1", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "update-lockfile-massage-1", + "version": "1.0.0", + "dependencies": { + "postcss": "^8.4.8" + } + }, + "node_modules/nanoid": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", + "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/postcss": { + "version": "8.4.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.8.tgz", + "integrity": "sha512-2tXEqGxrjvAO6U+CJzDL2Fk2kPHTv1jQsYkSoMeOis2SsYaXRO2COxTdQp99cYvif9JTXaAk9lYGc3VhJt7JPQ==", + "dependencies": { + "nanoid": "^3.3.1", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "engines": { + "node": ">=0.10.0" + } + } + }, + "dependencies": { + "nanoid": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", + "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==" + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "postcss": { + "version": "8.4.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.8.tgz", + "integrity": "sha512-2tXEqGxrjvAO6U+CJzDL2Fk2kPHTv1jQsYkSoMeOis2SsYaXRO2COxTdQp99cYvif9JTXaAk9lYGc3VhJt7JPQ==", + "requires": { + "nanoid": "^3.3.1", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" + } + } +} diff --git a/lib/modules/manager/npm/post-update/__fixtures__/update-lockfile-massage-1/package.json b/lib/modules/manager/npm/post-update/__fixtures__/update-lockfile-massage-1/package.json new file mode 100644 index 0000000000000000000000000000000000000000..c4982dc8b8e5f22a79e0e94c4cbdc1b3ab0d60ee --- /dev/null +++ b/lib/modules/manager/npm/post-update/__fixtures__/update-lockfile-massage-1/package.json @@ -0,0 +1,7 @@ +{ + "name": "update-lockfile-massage-1", + "version": "1.0.0", + "dependencies": { + "postcss": "^8.0.0" + } +} 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 0dcc2b29a2ca21c25ceb154e5a6cff41c37db789..6c274a61ef9f65028e3ef16814c2f46be102d4ec 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 @@ -93,6 +93,115 @@ Array [ ] `; +exports[`modules/manager/npm/post-update/npm performs lock file updates retaining the package.json counterparts 1`] = ` +"{ + \\"name\\": \\"update-lockfile-massage-1\\", + \\"version\\": \\"1.0.0\\", + \\"lockfileVersion\\": 2, + \\"requires\\": true, + \\"packages\\": { + \\"\\": { + \\"name\\": \\"update-lockfile-massage-1\\", + \\"version\\": \\"1.0.0\\", + \\"dependencies\\": { + \\"postcss\\": \\"^8.0.0\\" + } + }, + \\"node_modules/nanoid\\": { + \\"version\\": \\"3.3.1\\", + \\"resolved\\": \\"https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz\\", + \\"integrity\\": \\"sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==\\", + \\"bin\\": { + \\"nanoid\\": \\"bin/nanoid.cjs\\" + }, + \\"engines\\": { + \\"node\\": \\"^10 || ^12 || ^13.7 || ^14 || >=15.0.1\\" + } + }, + \\"node_modules/picocolors\\": { + \\"version\\": \\"1.0.0\\", + \\"resolved\\": \\"https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz\\", + \\"integrity\\": \\"sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==\\" + }, + \\"node_modules/postcss\\": { + \\"version\\": \\"8.4.8\\", + \\"resolved\\": \\"https://registry.npmjs.org/postcss/-/postcss-8.4.8.tgz\\", + \\"integrity\\": \\"sha512-2tXEqGxrjvAO6U+CJzDL2Fk2kPHTv1jQsYkSoMeOis2SsYaXRO2COxTdQp99cYvif9JTXaAk9lYGc3VhJt7JPQ==\\", + \\"dependencies\\": { + \\"nanoid\\": \\"^3.3.1\\", + \\"picocolors\\": \\"^1.0.0\\", + \\"source-map-js\\": \\"^1.0.2\\" + }, + \\"engines\\": { + \\"node\\": \\"^10 || ^12 || >=14\\" + }, + \\"funding\\": { + \\"type\\": \\"opencollective\\", + \\"url\\": \\"https://opencollective.com/postcss/\\" + } + }, + \\"node_modules/source-map-js\\": { + \\"version\\": \\"1.0.2\\", + \\"resolved\\": \\"https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz\\", + \\"integrity\\": \\"sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==\\", + \\"engines\\": { + \\"node\\": \\">=0.10.0\\" + } + } + }, + \\"dependencies\\": { + \\"nanoid\\": { + \\"version\\": \\"3.3.1\\", + \\"resolved\\": \\"https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz\\", + \\"integrity\\": \\"sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==\\" + }, + \\"picocolors\\": { + \\"version\\": \\"1.0.0\\", + \\"resolved\\": \\"https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz\\", + \\"integrity\\": \\"sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==\\" + }, + \\"postcss\\": { + \\"version\\": \\"8.4.8\\", + \\"resolved\\": \\"https://registry.npmjs.org/postcss/-/postcss-8.4.8.tgz\\", + \\"integrity\\": \\"sha512-2tXEqGxrjvAO6U+CJzDL2Fk2kPHTv1jQsYkSoMeOis2SsYaXRO2COxTdQp99cYvif9JTXaAk9lYGc3VhJt7JPQ==\\", + \\"requires\\": { + \\"nanoid\\": \\"^3.3.1\\", + \\"picocolors\\": \\"^1.0.0\\", + \\"source-map-js\\": \\"^1.0.2\\" + } + }, + \\"source-map-js\\": { + \\"version\\": \\"1.0.2\\", + \\"resolved\\": \\"https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz\\", + \\"integrity\\": \\"sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==\\" + } + } +}" +`; + +exports[`modules/manager/npm/post-update/npm performs lock file updates retaining the package.json counterparts 2`] = ` +Array [ + Object { + "cmd": "npm install --package-lock-only --no-audit --ignore-scripts postcss@8.4.8", + "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", + }, + "maxBuffer": 10485760, + "timeout": 900000, + }, + }, +] +`; + 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 []`; diff --git a/lib/modules/manager/npm/post-update/npm.spec.ts b/lib/modules/manager/npm/post-update/npm.spec.ts index d7054aadfd234a3ba3de6f1b81f0b79d1ff5a2e4..8cb1a14cd6f8825a0187fb51c1838ba57c5049b5 100644 --- a/lib/modules/manager/npm/post-update/npm.spec.ts +++ b/lib/modules/manager/npm/post-update/npm.spec.ts @@ -2,7 +2,7 @@ import { exec as _exec } from 'child_process'; import upath from 'upath'; import { envMock, mockExecAll } from '../../../../../test/exec-util'; -import { mocked } from '../../../../../test/util'; +import { loadFixture, mocked } from '../../../../../test/util'; import * as _env from '../../../../util/exec/env'; import * as _fs from '../../../../util/fs/proxies'; import * as npmHelper from './npm'; @@ -61,6 +61,33 @@ describe('modules/manager/npm/post-update/npm', () => { expect(res.lockFile).toBe('package-lock-contents'); expect(execSnapshots).toMatchSnapshot(); }); + it('performs lock file updates retaining the package.json counterparts', async () => { + const execSnapshots = mockExecAll(exec); + fs.readFile = jest.fn(() => + loadFixture('update-lockfile-massage-1/package-lock.json') + ) as never; + const skipInstalls = true; + const updates = [ + { + depName: 'postcss', + depType: 'dependencies', + newVersion: '8.4.8', + newValue: '^8.0.0', + isLockfileUpdate: true, + }, + ]; + const res = await npmHelper.generateLockFile( + 'some-dir', + {}, + 'package-lock.json', + { skipInstalls }, + updates + ); + expect(fs.readFile).toHaveBeenCalledTimes(1); + expect(res.error).toBeUndefined(); + expect(res.lockFile).toMatchSnapshot(); + expect(execSnapshots).toMatchSnapshot(); + }); it('performs npm-shrinkwrap.json updates', async () => { const execSnapshots = mockExecAll(exec); fs.pathExists.mockResolvedValueOnce(true); diff --git a/lib/modules/manager/npm/post-update/npm.ts b/lib/modules/manager/npm/post-update/npm.ts index 840840e2c2fae292edad9eedc99335605742ec81..a274c329455d6a71122081d08515f21c0788fab1 100644 --- a/lib/modules/manager/npm/post-update/npm.ts +++ b/lib/modules/manager/npm/post-update/npm.ts @@ -1,3 +1,4 @@ +import detectIndent from 'detect-indent'; import upath from 'upath'; import { GlobalConfig } from '../../../../config/global'; import { @@ -123,6 +124,33 @@ export async function generateLockFile( // Read the result lockFile = await readFile(upath.join(cwd, filename), 'utf8'); + + // Massage lockfile counterparts of package.json that were modified + // because npm install was called with an explicit version for rangeStrategy=update-lockfile + if (lockUpdates.length) { + let detectedIndent: string; + let lockFileParsed: any; + try { + detectedIndent = detectIndent(lockFile).indent || ' '; + lockFileParsed = JSON.parse(lockFile); + } catch (err) { + logger.warn({ err }, 'Error parsing npm lock file'); + } + if (lockFileParsed?.lockfileVersion === 2) { + lockUpdates.forEach((lockUpdate) => { + if ( + lockFileParsed.packages?.['']?.[lockUpdate.depType]?.[ + lockUpdate.depName + ] + ) { + lockFileParsed.packages[''][lockUpdate.depType][ + lockUpdate.depName + ] = lockUpdate.newValue; + } + }); + lockFile = JSON.stringify(lockFileParsed, null, detectedIndent); + } + } } catch (err) /* istanbul ignore next */ { if (err.message === TEMPORARY_ERROR) { throw err;