Skip to content

Commit

Permalink
refactor(npm): update locked dep file structure (#13103)
Browse files Browse the repository at this point in the history
  • Loading branch information
rarkins committed Dec 14, 2021
1 parent 3fabb3b commit 7373292
Show file tree
Hide file tree
Showing 24 changed files with 268 additions and 256 deletions.
7 changes: 5 additions & 2 deletions lib/manager/npm/post-update/index.ts
Expand Up @@ -252,9 +252,12 @@ export async function writeUpdatedPackageFiles(
return;
}
const { localDir } = GlobalConfig.get();
const supportedLockFiles = ['package-lock.json'];
for (const packageFile of config.updatedPackageFiles) {
if (packageFile.name.endsWith('package-lock.json')) {
logger.debug(`Writing package-lock file: ${packageFile.name}`);
if (
supportedLockFiles.some((fileName) => packageFile.name.endsWith(fileName))
) {
logger.debug(`Writing lock file: ${packageFile.name}`);
await outputFile(
upath.join(localDir, packageFile.name),
packageFile.contents
Expand Down

This file was deleted.

@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`manager/npm/update/locked-dependency/parent-version getLockedDependencies() finds indirect dependency 1`] = `
exports[`manager/npm/update/locked-dependency/common/parent-version getLockedDependencies() finds indirect dependency 1`] = `
Array [
Object {
"headers": Object {
Expand All @@ -25,7 +25,7 @@ Array [
]
`;

exports[`manager/npm/update/locked-dependency/parent-version getLockedDependencies() finds removed dependencies 1`] = `
exports[`manager/npm/update/locked-dependency/common/parent-version getLockedDependencies() finds removed dependencies 1`] = `
Array [
Object {
"headers": Object {
Expand All @@ -40,7 +40,7 @@ Array [
]
`;

exports[`manager/npm/update/locked-dependency/parent-version getLockedDependencies() finds when a greater version is needed 1`] = `
exports[`manager/npm/update/locked-dependency/common/parent-version getLockedDependencies() finds when a greater version is needed 1`] = `
Array [
Object {
"headers": Object {
Expand All @@ -55,7 +55,7 @@ Array [
]
`;

exports[`manager/npm/update/locked-dependency/parent-version getLockedDependencies() finds when a range matches greater versions 1`] = `
exports[`manager/npm/update/locked-dependency/common/parent-version getLockedDependencies() finds when a range matches greater versions 1`] = `
Array [
Object {
"headers": Object {
Expand All @@ -70,7 +70,7 @@ Array [
]
`;

exports[`manager/npm/update/locked-dependency/parent-version getLockedDependencies() returns null if no matching 1`] = `
exports[`manager/npm/update/locked-dependency/common/parent-version getLockedDependencies() returns null if no matching 1`] = `
Array [
Object {
"headers": Object {
Expand Down
@@ -1,10 +1,10 @@
import * as httpMock from '../../../../../test/http-mock';
import { loadJsonFixture } from '../../../../../test/util';
import * as httpMock from '../../../../../../test/http-mock';
import { loadJsonFixture } from '../../../../../../test/util';
import { findFirstParentVersion } from './parent-version';

const expressJson = loadJsonFixture('express.json');

describe('manager/npm/update/locked-dependency/parent-version', () => {
describe('manager/npm/update/locked-dependency/common/parent-version', () => {
describe('getLockedDependencies()', () => {
it('finds indirect dependency', async () => {
httpMock
Expand Down
@@ -1,6 +1,9 @@
import { GetPkgReleasesConfig, getPkgReleases } from '../../../../datasource';
import { logger } from '../../../../logger';
import { api as semver } from '../../../../versioning/npm';
import {
GetPkgReleasesConfig,
getPkgReleases,
} from '../../../../../datasource';
import { logger } from '../../../../../logger';
import { api as semver } from '../../../../../versioning/npm';

/**
* Finds the first stable version of parentName after parentStartingVersion which either:
Expand Down
18 changes: 10 additions & 8 deletions lib/manager/npm/update/locked-dependency/index.spec.ts
Expand Up @@ -4,14 +4,16 @@ import { clone } from '../../../../util/clone';
import type { UpdateLockedConfig } from '../../../types';
import { updateLockedDependency } from '.';

const packageFileContent = loadFixture('package.json');
const lockFileContent = loadFixture('package-lock.json');
const acceptsJson = JSON.parse(loadFixture('accepts.json'));
const expressJson = JSON.parse(loadFixture('express.json'));
const mimeJson = JSON.parse(loadFixture('mime.json'));
const serveStaticJson = JSON.parse(loadFixture('serve-static.json'));
const sendJson = JSON.parse(loadFixture('send.json'));
const typeIsJson = JSON.parse(loadFixture('type-is.json'));
const packageFileContent = loadFixture('package.json', './package-lock');
const lockFileContent = loadFixture('package-lock.json', './package-lock');
const acceptsJson = JSON.parse(loadFixture('accepts.json', './package-lock'));
const expressJson = JSON.parse(loadFixture('express.json', './common'));
const mimeJson = JSON.parse(loadFixture('mime.json', './package-lock'));
const serveStaticJson = JSON.parse(
loadFixture('serve-static.json', './package-lock')
);
const sendJson = JSON.parse(loadFixture('send.json', './package-lock'));
const typeIsJson = JSON.parse(loadFixture('type-is.json', './package-lock'));

describe('manager/npm/update/locked-dependency/index', () => {
describe('updateLockedDependency()', () => {
Expand Down
204 changes: 10 additions & 194 deletions lib/manager/npm/update/locked-dependency/index.ts
@@ -1,203 +1,19 @@
import detectIndent from 'detect-indent';
import type { PackageJson } from 'type-fest';
import { logger } from '../../../../logger';
import { api as semver } from '../../../../versioning/npm';
import * as semver from '../../../../versioning/semver';
import type { UpdateLockedConfig } from '../../../types';
import { updateDependency } from '../dependency';
import { findDepConstraints } from './dep-constraints';
import { getLockedDependencies } from './get-locked';
import { findFirstParentVersion } from './parent-version';
import type { PackageLockOrEntry } from './types';
import * as packageLock from './package-lock';

export function validateInputs(config: UpdateLockedConfig): boolean {
export function updateLockedDependency(
config: UpdateLockedConfig
): Promise<Record<string, string>> {
const { currentVersion, newVersion, lockFile } = config;
if (!lockFile.endsWith('package-lock.json')) {
logger.debug({ lockFile }, 'Unsupported lock file');
return false;
}
if (!(semver.isVersion(currentVersion) && semver.isVersion(newVersion))) {
logger.warn({ config }, 'Update versions are not valid');
return false;
}
return true;
}

export async function updateLockedDependency(
config: UpdateLockedConfig,
isParentUpdate = false
): Promise<Record<string, string>> {
const {
depName,
currentVersion,
newVersion,
packageFile,
packageFileContent,
lockFile,
lockFileContent,
} = config;
logger.debug(
`npm.updateLockedDependency: ${depName}@${currentVersion} -> ${newVersion} [${lockFile}]`
);
try {
if (!validateInputs(config)) {
return null;
}
let packageJson: PackageJson;
let packageLockJson: PackageLockOrEntry;
const detectedIndent = detectIndent(lockFileContent).indent || ' ';
let newPackageJsonContent: string;
try {
packageJson = JSON.parse(packageFileContent);
packageLockJson = JSON.parse(lockFileContent);
} catch (err) {
logger.warn({ err }, 'Failed to parse files');
return null;
}
if (packageLockJson.lockfileVersion === 2) {
logger.debug('Only lockfileVersion 1 is supported');
return null;
}
const lockedDeps = getLockedDependencies(
packageLockJson,
depName,
currentVersion
);
if (!lockedDeps.length) {
logger.debug(
`${depName}@${currentVersion} not found in ${lockFile} - no work to do`
);
// Don't return null if we're a parent update or else the whole update will fail
// istanbul ignore if: too hard to replicate
if (isParentUpdate) {
const res = {};
res[packageFile] = packageFileContent;
res[lockFile] = lockFileContent;
return res;
}
return null;
}
logger.debug(
`Found matching dependencies with length ${lockedDeps.length}`
);
const constraints = findDepConstraints(
packageJson,
packageLockJson,
depName,
currentVersion,
newVersion
);
logger.trace({ deps: lockedDeps, constraints }, 'Matching details');
if (!constraints.length) {
logger.info(
{ depName, currentVersion, newVersion },
'Could not find constraints for the locked dependency - cannot remediate'
);
return null;
}
const parentUpdates: UpdateLockedConfig[] = [];
for (const {
parentDepName,
parentVersion,
constraint,
depType,
} of constraints) {
if (semver.matches(newVersion, constraint)) {
// Parent dependency is compatible with the new version we want
logger.debug(
`${depName} can be updated to ${newVersion} in-range with matching constraint "${constraint}" in ${
parentDepName ? `${parentDepName}@${parentVersion}` : packageFile
}`
);
} else if (parentDepName && parentVersion) {
// Parent dependency needs updating too
const parentNewVersion = await findFirstParentVersion(
parentDepName,
parentVersion,
depName,
newVersion
);
if (parentNewVersion) {
if (parentNewVersion === parentVersion) {
logger.debug(
`Update of ${depName} to ${newVersion} already achieved in parent ${parentDepName}@${parentNewVersion}`
);
} else {
// Update the parent dependency so that we can update this dependency
logger.debug(
`Update of ${depName} to ${newVersion} can be achieved due to parent ${parentDepName}`
);
const parentUpdate: UpdateLockedConfig = {
depName: parentDepName,
currentVersion: parentVersion,
newVersion: parentNewVersion,
};
parentUpdates.push(parentUpdate);
}
} else {
// For some reason it's not possible to update the parent to a version compatible with our desired dep version
logger.debug(
`Update of ${depName} to ${newVersion} cannot be achieved due to parent ${parentDepName}`
);
return null;
}
} else if (depType) {
// The constaint comes from the package.json file, so we need to update it
const newValue = semver.getNewValue({
currentValue: constraint,
rangeStrategy: 'replace',
currentVersion,
newVersion,
});
newPackageJsonContent = updateDependency({
fileContent: packageFileContent,
upgrade: { depName, depType, newValue },
});
}
}
for (const dependency of lockedDeps) {
// Remove resolved and integrity fields for npm to fill in
dependency.version = newVersion;
delete dependency.resolved;
delete dependency.integrity;
}
let newLockFileContent = JSON.stringify(
packageLockJson,
null,
detectedIndent
);
// iterate through the parent updates first
for (const parentUpdate of parentUpdates) {
const parentUpdateConfig = {
...config,
lockFileContent: newLockFileContent,
packageFileContent: newPackageJsonContent || packageFileContent,
...parentUpdate,
};
const parentUpdateResult = await updateLockedDependency(
parentUpdateConfig,
true
);
// istanbul ignore if: hard to test due to recursion
if (!parentUpdateResult) {
logger.debug(
`Update of ${depName} to ${newVersion} impossible due to failed update of parent ${parentUpdate.depName} to ${parentUpdate.newVersion}`
);
return null;
}
newPackageJsonContent =
parentUpdateResult[packageFile] || newPackageJsonContent;
newLockFileContent = parentUpdateResult[lockFile] || newLockFileContent;
}
const files = {};
if (newLockFileContent) {
files[lockFile] = newLockFileContent;
}
if (newPackageJsonContent) {
files[packageFile] = newPackageJsonContent;
}
return files;
} catch (err) /* istanbul ignore next */ {
logger.error({ err }, 'updateLockedDependency() error');
return null;
}
if (lockFile.endsWith('package-lock.json')) {
return packageLock.updateLockedDependency(config);
}
logger.debug({ lockFile }, 'Unsupported lock file');
return null;
}

Large diffs are not rendered by default.

@@ -0,0 +1,29 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`manager/npm/update/locked-dependency/package-lock/dep-constraints findDepConstraints() finds direct dependency 1`] = `
Array [
Object {
"constraint": "4.0.0",
"depType": "dependencies",
},
]
`;

exports[`manager/npm/update/locked-dependency/package-lock/dep-constraints findDepConstraints() finds direct devDependency 1`] = `
Array [
Object {
"constraint": "4.0.0",
"depType": "devDependencies",
},
]
`;

exports[`manager/npm/update/locked-dependency/package-lock/dep-constraints findDepConstraints() finds indirect dependency 1`] = `
Array [
Object {
"constraint": "0.2.0",
"parentDepName": "express",
"parentVersion": "4.0.0",
},
]
`;

0 comments on commit 7373292

Please sign in to comment.