refactor(manager): final strict null checks (#15185)

* refactor(manager): final strict null checks

* refactor: fix type issues

* test: fix mocking
This commit is contained in:
Michael Kriese 2022-04-20 08:40:20 +02:00 committed by GitHub
parent 1c0073d9a9
commit 868ebbef63
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 337 additions and 254 deletions

View file

@ -7,6 +7,8 @@ const lockFileContent = loadFixture('Gemfile.rubyci.lock');
describe('modules/manager/bundler/update-locked', () => {
it('detects already updated', () => {
const config: UpdateLockedConfig = {
packageFile: 'Gemfile',
lockFile: 'Gemfile.lock',
lockFileContent,
depName: 'activejob',
newVersion: '5.2.3',
@ -16,6 +18,8 @@ describe('modules/manager/bundler/update-locked', () => {
it('returns unsupported', () => {
const config: UpdateLockedConfig = {
packageFile: 'Gemfile',
lockFile: 'Gemfile.lock',
lockFileContent,
depName: 'activejob',
newVersion: '5.2.0',

View file

@ -2,11 +2,15 @@ import { loadFixture } from '../../../../test/util';
import type { UpdateLockedConfig } from '../types';
import { updateLockedDependency } from '.';
const lockFile = 'compose.lock';
const lockFileContent = loadFixture('composer5.lock');
describe('modules/manager/composer/update-locked', () => {
it('detects already updated', () => {
const config: UpdateLockedConfig = {
packageFile: 'composer.json',
lockFile,
lockFileContent,
depName: 'awesome/git',
newVersion: '1.2.0',
@ -16,6 +20,8 @@ describe('modules/manager/composer/update-locked', () => {
it('returns unsupported', () => {
const config: UpdateLockedConfig = {
packageFile: 'composer.json',
lockFile,
lockFileContent,
depName: 'awesome/git',
newVersion: '1.0.0',

View file

@ -17,7 +17,7 @@ const languageList = Object.values(ProgrammingLanguage);
export function get<T extends keyof ManagerApi>(
manager: string,
name: T
): ManagerApi[T] | null {
): ManagerApi[T] | undefined {
return managers.get(manager)?.[name];
}
export const getLanguageList = (): string[] => languageList;
@ -27,7 +27,7 @@ export const getManagers = (): Map<string, ManagerApi> => managers;
export async function detectAllGlobalConfig(): Promise<GlobalManagerConfig> {
let config: GlobalManagerConfig = {};
for (const managerName of managerList) {
const manager = managers.get(managerName);
const manager = managers.get(managerName)!;
if (manager.detectGlobalConfig) {
// This should use mergeChildConfig once more than one manager is supported, but introduces a cyclic dependency
config = { ...config, ...(await manager.detectGlobalConfig()) };
@ -44,7 +44,7 @@ export async function extractAllPackageFiles(
if (!managers.has(manager)) {
return null;
}
const m = managers.get(manager);
const m = managers.get(manager)!;
if (m.extractAllPackageFiles) {
const res = await m.extractAllPackageFiles(config, files);
// istanbul ignore if
@ -65,18 +65,18 @@ export function extractPackageFile(
if (!managers.has(manager)) {
return null;
}
const m = managers.get(manager);
const m = managers.get(manager)!;
return m.extractPackageFile
? m.extractPackageFile(content, fileName, config)
: null;
}
export function getRangeStrategy(config: RangeConfig): RangeStrategy {
export function getRangeStrategy(config: RangeConfig): RangeStrategy | null {
const { manager, rangeStrategy } = config;
if (!managers.has(manager)) {
if (!manager || !managers.has(manager)) {
return null;
}
const m = managers.get(manager);
const m = managers.get(manager)!;
if (m.getRangeStrategy) {
// Use manager's own function if it exists
const managerRangeStrategy = m.getRangeStrategy(config);

View file

@ -66,7 +66,7 @@ export async function extractPackageFile(
`npm file ${fileName} has name ${JSON.stringify(packageJsonName)}`
);
const packageFileVersion = packageJson.version;
let yarnWorkspacesPackages: string[];
let yarnWorkspacesPackages: string[] | undefined;
if (is.array(packageJson.workspaces)) {
yarnWorkspacesPackages = packageJson.workspaces;
} else {
@ -83,7 +83,10 @@ export async function extractPackageFile(
pnpmShrinkwrap: 'pnpm-lock.yaml',
};
for (const [key, val] of Object.entries(lockFiles)) {
for (const [key, val] of Object.entries(lockFiles) as [
'yarnLock' | 'packageLock' | 'shrinkwrapJson' | 'pnpmShrinkwrap',
string
][]) {
const filePath = getSiblingFileName(fileName, val);
if (await readLocalFile(filePath, 'utf8')) {
lockFiles[key] = filePath;
@ -95,7 +98,7 @@ export async function extractPackageFile(
delete lockFiles.packageLock;
delete lockFiles.shrinkwrapJson;
let npmrc: string;
let npmrc: string | undefined;
const npmrcFileName = getSiblingFileName(fileName, '.npmrc');
let repoNpmrc = await readLocalFile(npmrcFileName, 'utf8');
if (is.string(repoNpmrc)) {
@ -135,15 +138,17 @@ export async function extractPackageFile(
const yarnrcYmlFileName = getSiblingFileName(fileName, '.yarnrc.yml');
const yarnZeroInstall = await isZeroInstall(yarnrcYmlFileName);
let lernaJsonFile: string;
let lernaPackages: string[];
let lernaClient: 'yarn' | 'npm';
let lernaJsonFile: string | undefined;
let lernaPackages: string[] | undefined;
let lernaClient: 'yarn' | 'npm' | undefined;
let hasFancyRefs = false;
let lernaJson: {
packages: string[];
npmClient: string;
useWorkspaces?: boolean;
};
let lernaJson:
| {
packages: string[];
npmClient: string;
useWorkspaces?: boolean;
}
| undefined;
try {
lernaJsonFile = getSiblingFileName(fileName, 'lerna.json');
lernaJson = JSON.parse(await readLocalFile(lernaJsonFile, 'utf8'));
@ -332,14 +337,16 @@ export async function extractPackageFile(
return dep;
}
for (const depType of Object.keys(depTypes)) {
for (const depType of Object.keys(depTypes) as (keyof typeof depTypes)[]) {
let dependencies = packageJson[depType];
if (dependencies) {
try {
if (depType === 'packageManager') {
const match = regEx('^(?<name>.+)@(?<range>.+)$').exec(dependencies);
const match = regEx('^(?<name>.+)@(?<range>.+)$').exec(
dependencies as string
);
// istanbul ignore next
if (!match) {
if (!match?.groups) {
break;
}
dependencies = { [match.groups.name]: match.groups.range };
@ -446,6 +453,6 @@ export async function extractAllPackageFiles(
logger.debug({ packageFile }, 'packageFile has no content');
}
}
await postExtract(npmFiles, config.updateInternalDeps);
await postExtract(npmFiles, !!config.updateInternalDeps);
return npmFiles;
}

View file

@ -12,7 +12,7 @@ export async function getLockedVersions(
logger.debug('Finding locked versions');
for (const packageFile of packageFiles) {
const { yarnLock, npmLock, pnpmShrinkwrap } = packageFile;
const lockFiles = [];
const lockFiles: string[] = [];
if (yarnLock) {
logger.trace('Found yarnLock');
lockFiles.push(yarnLock);
@ -22,14 +22,17 @@ export async function getLockedVersions(
}
const { lockfileVersion, isYarn1 } = lockFileCache[yarnLock];
if (!isYarn1) {
if (lockfileVersion >= 8) {
if (lockfileVersion && lockfileVersion >= 8) {
// https://github.com/yarnpkg/berry/commit/9bcd27ae34aee77a567dd104947407532fa179b3
packageFile.constraints.yarn = '^3.0.0';
} else if (lockfileVersion >= 6) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
packageFile.constraints!.yarn = '^3.0.0';
} else if (lockfileVersion && lockfileVersion >= 6) {
// https://github.com/yarnpkg/berry/commit/f753790380cbda5b55d028ea84b199445129f9ba
packageFile.constraints.yarn = '^2.2.0';
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
packageFile.constraints!.yarn = '^2.2.0';
} else {
packageFile.constraints.yarn = '^2.0.0';
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
packageFile.constraints!.yarn = '^2.0.0';
}
}
for (const dep of packageFile.deps) {
@ -54,18 +57,20 @@ export async function getLockedVersions(
}
const { lockfileVersion } = lockFileCache[npmLock];
if (lockfileVersion === 1) {
if (packageFile.constraints.npm) {
if (packageFile.constraints?.npm) {
// Add a <7 constraint if it's not already a fixed version
if (!semver.valid(packageFile.constraints.npm)) {
packageFile.constraints.npm += ' <7';
}
} else {
packageFile.constraints.npm = '<7';
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
packageFile.constraints!.npm = '<7';
}
}
for (const dep of packageFile.deps) {
dep.lockedVersion = semver.valid(
lockFileCache[npmLock].lockedVersions[dep.depName]
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
lockFileCache[npmLock].lockedVersions[dep.depName!]
);
}
} else if (pnpmShrinkwrap) {

View file

@ -28,10 +28,13 @@ export async function detectMonorepos(
if (packages?.length) {
const internalPackagePatterns = (
is.array(packages) ? packages : [packages]
).map((pattern) => getSiblingFileName(packageFile, pattern));
)
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
.map((pattern) => getSiblingFileName(packageFile!, pattern));
const internalPackageFiles = packageFiles.filter((sp) =>
matchesAnyPattern(
getSubDirectory(sp.packageFile),
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
getSubDirectory(sp.packageFile!),
internalPackagePatterns
)
);

View file

@ -24,7 +24,7 @@ describe('modules/manager/npm/extract/pnpm', () => {
'..'
);
const res = await extractPnpmFilters(workSpaceFilePath);
expect(res).toBeNull();
expect(res).toBeUndefined();
expect(logger.logger.trace).toHaveBeenCalledWith(
{
fileName: expect.any(String),
@ -39,7 +39,7 @@ describe('modules/manager/npm/extract/pnpm', () => {
});
const res = await extractPnpmFilters('pnpm-workspace.yml');
expect(res).toBeNull();
expect(res).toBeUndefined();
expect(logger.logger.trace).toHaveBeenCalledWith(
expect.objectContaining({
fileName: expect.any(String),

View file

@ -15,7 +15,7 @@ import type { PnpmWorkspaceFile } from './types';
export async function extractPnpmFilters(
fileName: string
): Promise<string[] | null> {
): Promise<string[] | undefined> {
try {
const contents = load(await readLocalFile(fileName, 'utf8'), {
json: true,
@ -28,12 +28,12 @@ export async function extractPnpmFilters(
{ fileName },
'Failed to find required "packages" array in pnpm-workspace.yaml'
);
return null;
return undefined;
}
return contents.packages;
} catch (err) {
logger.trace({ fileName, err }, 'Failed to parse pnpm-workspace.yaml');
return null;
return undefined;
}
}
@ -91,7 +91,8 @@ export async function detectPnpmWorkspaces(
}
// search for corresponding pnpm workspace
const pnpmWorkspace = await findPnpmWorkspace(packageFile);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
const pnpmWorkspace = await findPnpmWorkspace(packageFile!);
if (pnpmWorkspace === null) {
continue;
}
@ -113,7 +114,8 @@ export async function detectPnpmWorkspaces(
const packagePaths = packagePathCache.get(workspaceYamlPath);
const isPackageInWorkspace = packagePaths?.some((p) =>
p.endsWith(packageFile)
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
p.endsWith(packageFile!)
);
if (isPackageInWorkspace) {

View file

@ -13,6 +13,8 @@ export interface NpmPackage extends PackageJson {
_id?: any;
dependenciesMeta?: DependenciesMeta;
packageManager?: string;
volta?: PackageJson.Dependency;
}
export type LockFileEntry = Record<

View file

@ -14,7 +14,7 @@ export async function getYarnLock(filePath: string): Promise<LockFile> {
try {
const parsed = parseSyml(yarnLockRaw);
const lockedVersions: Record<string, string> = {};
let lockfileVersion: number;
let lockfileVersion: number | undefined;
for (const [key, val] of Object.entries(parsed)) {
if (key === '__metadata') {

View file

@ -29,6 +29,7 @@ import { ensureTrailingSlash } from '../../../../util/url';
import { NpmDatasource } from '../../../datasource/npm';
import type { PackageFile, PostUpdateConfig, Upgrade } from '../../types';
import { getZeroInstallPaths } from '../extract/yarn';
import type { NpmDepType } from '../types';
import { composeLockFile, parseLockFile } from '../utils';
import * as lerna from './lerna';
import * as npm from './npm';
@ -44,18 +45,18 @@ import * as yarn from './yarn';
// Strips empty values, deduplicates, and returns the directories from filenames
// istanbul ignore next
const getDirs = (arr: string[]): string[] =>
Array.from(new Set(arr.filter(Boolean)));
const getDirs = (arr: (string | null | undefined)[]): string[] =>
Array.from(new Set(arr.filter(is.string)));
// istanbul ignore next
export function determineLockFileDirs(
config: PostUpdateConfig,
packageFiles: AdditionalPackageFiles
): DetermineLockFileDirsResult {
const npmLockDirs = [];
const yarnLockDirs = [];
const pnpmShrinkwrapDirs = [];
const lernaJsonFiles = [];
const npmLockDirs: (string | undefined)[] = [];
const yarnLockDirs: (string | undefined)[] = [];
const pnpmShrinkwrapDirs: (string | undefined)[] = [];
const lernaJsonFiles: (string | undefined)[] = [];
for (const upgrade of config.upgrades) {
if (upgrade.updateType === 'lockFileMaintenance' || upgrade.isRemediation) {
@ -91,7 +92,8 @@ export function determineLockFileDirs(
function getPackageFile(fileName: string): Partial<PackageFile> {
logger.trace('Looking for packageFile: ' + fileName);
for (const packageFile of packageFiles.npm) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
for (const packageFile of packageFiles.npm!) {
if (packageFile.packageFile === fileName) {
logger.trace({ packageFile }, 'Found packageFile');
return packageFile;
@ -101,7 +103,8 @@ export function determineLockFileDirs(
return {};
}
for (const p of config.updatedPackageFiles) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
for (const p of config.updatedPackageFiles!) {
logger.trace(`Checking ${String(p.path)} for lock files`);
const packageFile = getPackageFile(p.path);
// lerna first
@ -147,7 +150,8 @@ export async function writeExistingFiles(
for (const packageFile of npmFiles) {
const basedir = upath.join(
localDir,
upath.dirname(packageFile.packageFile)
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
upath.dirname(packageFile.packageFile!)
);
const npmrc: string = packageFile.npmrc || config.npmrc;
const npmrcFilename = upath.join(basedir, '.npmrc');
@ -171,9 +175,10 @@ export async function writeExistingFiles(
logger.debug(`Writing ${npmLock}`);
let existingNpmLock: string;
try {
existingNpmLock = await getFile(npmLock);
existingNpmLock = (await getFile(npmLock)) ?? '';
} catch (err) {
logger.warn({ err }, 'Error reading npm lock file');
existingNpmLock = '';
}
const { detectedIndent, lockFileParsed: npmLockParsed } =
parseLockFile(existingNpmLock);
@ -182,14 +187,15 @@ export async function writeExistingFiles(
'packages' in npmLockParsed
? Object.keys(npmLockParsed.packages)
: [];
const widens = [];
const widens: string[] = [];
let lockFileChanged = false;
for (const upgrade of config.upgrades) {
if (
upgrade.rangeStrategy === 'widen' &&
upgrade.npmLock === npmLock
) {
widens.push(upgrade.depName);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
widens.push(upgrade.depName!);
}
const { depName } = upgrade;
for (const packageName of packageNames) {
@ -215,7 +221,8 @@ export async function writeExistingFiles(
npmLockParsed.dependencies
) {
widens.forEach((depName) => {
delete npmLockParsed.dependencies[depName];
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
delete npmLockParsed.dependencies![depName];
});
}
} catch (err) {
@ -284,12 +291,15 @@ export async function writeUpdatedPackageFiles(
});
for (const upgrade of config.upgrades) {
if (upgrade.gitRef && upgrade.packageFile === packageFile.path) {
massagedFile[upgrade.depType][upgrade.depName] = massagedFile[
upgrade.depType
][upgrade.depName].replace(
'git+https://github.com',
`git+https://${token}@github.com`
);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
massagedFile[upgrade.depType as NpmDepType][upgrade.depName!] =
massagedFile[
upgrade.depType as NpmDepType
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
][upgrade.depName!].replace(
'git+https://github.com',
`git+https://${token}@github.com`
);
}
}
} catch (err) {
@ -305,7 +315,7 @@ export async function writeUpdatedPackageFiles(
// istanbul ignore next
async function getNpmrcContent(dir: string): Promise<string | null> {
const npmrcFilePath = upath.join(dir, '.npmrc');
let originalNpmrcContent = null;
let originalNpmrcContent: string | null = null;
try {
originalNpmrcContent = await readFile(npmrcFilePath, 'utf8');
logger.debug('npmrc file found in repository');
@ -320,7 +330,7 @@ async function getNpmrcContent(dir: string): Promise<string | null> {
// istanbul ignore next
async function updateNpmrcContent(
dir: string,
originalContent: string,
originalContent: string | null,
additionalLines: string[]
): Promise<void> {
const npmrcFilePath = upath.join(dir, '.npmrc');
@ -341,7 +351,7 @@ async function updateNpmrcContent(
// istanbul ignore next
async function resetNpmrcContent(
dir: string,
originalContent: string
originalContent: string | null
): Promise<void> {
const npmrcFilePath = upath.join(dir, '.npmrc');
if (originalContent) {
@ -422,7 +432,7 @@ export async function updateYarnBinary(
let yarnrcYml = existingYarnrcYmlContent;
try {
const yarnrcYmlFilename = upath.join(lockFileDir, '.yarnrc.yml');
yarnrcYml ||= await getFile(yarnrcYmlFilename);
yarnrcYml ||= (await getFile(yarnrcYmlFilename)) ?? undefined;
const newYarnrcYml = await readLocalFile(yarnrcYmlFilename, 'utf8');
if (!is.string(yarnrcYml) || !is.string(newYarnrcYml)) {
return existingYarnrcYmlContent;
@ -518,7 +528,7 @@ export async function getAdditionalFiles(
let token = '';
try {
({ token } = hostRules.find({
({ token = '' } = hostRules.find({
hostType: config.platform,
url: 'https://api.github.com/',
}));
@ -527,7 +537,7 @@ export async function getAdditionalFiles(
logger.warn({ err }, 'Error getting token for packageFile');
}
const tokenRe = regEx(`${token}`, 'g', false);
const { localDir } = GlobalConfig.get();
const localDir = GlobalConfig.get('localDir')!;
for (const npmLock of dirs.npmLockDirs) {
const lockFileDir = upath.dirname(npmLock);
const fullLockFileDir = upath.join(localDir, lockFileDir);
@ -585,7 +595,9 @@ export async function getAdditionalFiles(
updatedArtifacts.push({
type: 'addition',
path: npmLock,
contents: res.lockFile.replace(tokenRe, ''),
// TODO: can this be undefined?
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
contents: res.lockFile!.replace(tokenRe, ''),
});
}
}
@ -601,8 +613,8 @@ export async function getAdditionalFiles(
npmrcContent,
additionalNpmrcContent
);
let yarnRcYmlFilename: string;
let existingYarnrcYmlContent: string;
let yarnRcYmlFilename: string | undefined;
let existingYarnrcYmlContent: string | undefined;
if (additionalYarnRcYml) {
yarnRcYmlFilename = getSiblingFileName(yarnLock, '.yarnrc.yml');
existingYarnrcYmlContent = await readLocalFile(yarnRcYmlFilename, 'utf8');
@ -674,7 +686,9 @@ export async function getAdditionalFiles(
updatedArtifacts.push({
type: 'addition',
path: lockFileName,
contents: res.lockFile,
//
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
contents: res.lockFile!,
});
await updateYarnOffline(lockFileDir, localDir, updatedArtifacts);
}
@ -689,7 +703,8 @@ export async function getAdditionalFiles(
}
await resetNpmrcContent(fullLockFileDir, npmrcContent);
if (existingYarnrcYmlContent) {
await writeLocalFile(yarnRcYmlFilename, existingYarnrcYmlContent);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
await writeLocalFile(yarnRcYmlFilename!, existingYarnrcYmlContent);
}
}
@ -750,7 +765,9 @@ export async function getAdditionalFiles(
updatedArtifacts.push({
type: 'addition',
path: pnpmShrinkwrap,
contents: res.lockFile,
// TODO: can be undefined?
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
contents: res.lockFile!,
});
}
}
@ -761,7 +778,8 @@ export async function getAdditionalFiles(
let lockFile: string;
logger.debug(`Finding package.json for lerna location "${lernaJsonFile}"`);
const lernaPackageFile = packageFiles.npm.find(
(p) => getSubDirectory(p.packageFile) === getSubDirectory(lernaJsonFile)
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
(p) => getSubDirectory(p.packageFile!) === getSubDirectory(lernaJsonFile)
);
if (!lernaPackageFile) {
logger.debug('No matching package.json found');
@ -842,7 +860,8 @@ export async function getAdditionalFiles(
const filename = packageFile.npmLock || packageFile.yarnLock;
logger.trace('Checking for ' + filename);
const existingContent = await getFile(
filename,
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
filename!,
config.reuseExistingBranch ? config.branchName : config.baseBranch
);
if (existingContent) {
@ -868,7 +887,8 @@ export async function getAdditionalFiles(
logger.debug('File is updated: ' + lockFilePath);
updatedArtifacts.push({
type: 'addition',
path: filename,
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
path: filename!,
contents: newContent,
});
}

View file

@ -1,31 +1,30 @@
import { exec as _exec } from 'child_process';
import { envMock, mockExecAll } from '../../../../../test/exec-util';
import { mocked } from '../../../../../test/util';
import { envMock, exec, mockExecAll } from '../../../../../test/exec-util';
import { env, partial } from '../../../../../test/util';
import { GlobalConfig } from '../../../../config/global';
import * as _env from '../../../../util/exec/env';
import * as _lernaHelper from './lerna';
import type { PackageFile, PostUpdateConfig } from '../../types';
import * as lernaHelper from './lerna';
jest.mock('child_process');
jest.mock('../../../../util/exec/env');
jest.mock('../../npm/post-update/node-version');
const exec: jest.Mock<typeof _exec> = _exec as any;
const env = mocked(_env);
const lernaHelper = mocked(_lernaHelper);
function lernaPkgFile(lernaClient: string) {
function lernaPkgFile(lernaClient: string): Partial<PackageFile> {
return {
lernaClient,
deps: [{ depName: 'lerna', currentValue: '2.0.0' }],
};
}
function lernaPkgFileWithoutLernaDep(lernaClient: string) {
function lernaPkgFileWithoutLernaDep(
lernaClient: string
): Partial<PackageFile> {
return {
lernaClient,
};
}
const config = partial<PostUpdateConfig>({});
describe('modules/manager/npm/post-update/lerna', () => {
describe('generateLockFiles()', () => {
beforeEach(() => {
@ -35,7 +34,12 @@ describe('modules/manager/npm/post-update/lerna', () => {
});
it('returns if no lernaClient', async () => {
const res = await lernaHelper.generateLockFiles({}, 'some-dir', {}, {});
const res = await lernaHelper.generateLockFiles(
{},
'some-dir',
config,
{}
);
expect(res.error).toBeFalse();
});
@ -43,7 +47,7 @@ describe('modules/manager/npm/post-update/lerna', () => {
const res = await lernaHelper.generateLockFiles(
lernaPkgFile('foo'),
'some-dir',
{},
config,
{}
);
expect(res.error).toBeFalse();
@ -55,7 +59,7 @@ describe('modules/manager/npm/post-update/lerna', () => {
const res = await lernaHelper.generateLockFiles(
lernaPkgFile('npm'),
'some-dir',
{},
config,
{},
skipInstalls
);
@ -69,7 +73,7 @@ describe('modules/manager/npm/post-update/lerna', () => {
const res = await lernaHelper.generateLockFiles(
lernaPkgFile('npm'),
'some-dir',
{},
config,
{},
skipInstalls
);
@ -82,7 +86,7 @@ describe('modules/manager/npm/post-update/lerna', () => {
const res = await lernaHelper.generateLockFiles(
lernaPkgFile('yarn'),
'some-dir',
{ constraints: { yarn: '^1.10.0' } },
{ ...config, constraints: { yarn: '^1.10.0' } },
{}
);
expect(execSnapshots).toMatchSnapshot();
@ -94,7 +98,7 @@ describe('modules/manager/npm/post-update/lerna', () => {
const res = await lernaHelper.generateLockFiles(
lernaPkgFileWithoutLernaDep('npm'),
'some-dir',
{},
config,
{}
);
expect(res.error).toBeFalse();
@ -107,7 +111,7 @@ describe('modules/manager/npm/post-update/lerna', () => {
const res = await lernaHelper.generateLockFiles(
lernaPkgFile('npm'),
'some-dir',
{ constraints: { npm: '^6.0.0' } },
{ ...config, constraints: { npm: '^6.0.0' } },
{}
);
expect(res.error).toBeFalse();

View file

@ -4,7 +4,7 @@ import { GlobalConfig } from '../../../../config/global';
import { TEMPORARY_ERROR } from '../../../../constants/error-messages';
import { logger } from '../../../../logger';
import { exec } from '../../../../util/exec';
import type { ExecOptions } from '../../../../util/exec/types';
import type { ExecOptions, ExtraEnv } from '../../../../util/exec/types';
import type { PackageFile, PostUpdateConfig } from '../../types';
import { getNodeConstraint } from './node-version';
import type { GenerateLockFileResult } from './types';
@ -21,7 +21,8 @@ export function getLernaVersion(
);
return 'latest';
}
return lernaDep.currentValue;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
return lernaDep.currentValue!;
}
export async function generateLockFiles(
@ -37,8 +38,8 @@ export async function generateLockFiles(
return { error: false };
}
logger.debug(`Spawning lerna with ${lernaClient} to create lock files`);
const preCommands = [];
const cmd = [];
const preCommands: string[] = [];
const cmd: string[] = [];
let cmdOptions = '';
try {
if (lernaClient === 'yarn') {
@ -74,12 +75,13 @@ export async function generateLockFiles(
}
lernaCommand += cmdOptions;
const tagConstraint = await getNodeConstraint(config);
const extraEnv: ExtraEnv = {
NPM_CONFIG_CACHE: env.NPM_CONFIG_CACHE,
npm_config_store: env.npm_config_store,
};
const execOptions: ExecOptions = {
cwd,
extraEnv: {
NPM_CONFIG_CACHE: env.NPM_CONFIG_CACHE,
npm_config_store: env.npm_config_store,
},
extraEnv,
docker: {
image: 'node',
tagScheme: 'node',
@ -89,8 +91,8 @@ export async function generateLockFiles(
};
// istanbul ignore if
if (GlobalConfig.get('exposeAllEnv')) {
execOptions.extraEnv.NPM_AUTH = env.NPM_AUTH;
execOptions.extraEnv.NPM_EMAIL = env.NPM_EMAIL;
extraEnv.NPM_AUTH = env.NPM_AUTH;
extraEnv.NPM_EMAIL = env.NPM_EMAIL;
}
const lernaVersion = getLernaVersion(lernaPackageFile);
logger.debug('Using lerna version ' + lernaVersion);

View file

@ -4,7 +4,7 @@ import { getSiblingFileName, readLocalFile } from '../../../../util/fs';
import { newlineRegex, regEx } from '../../../../util/regex';
import type { PostUpdateConfig } from '../../types';
async function getNodeFile(filename: string): Promise<string> | null {
async function getNodeFile(filename: string): Promise<string | null> {
try {
const constraint = (await readLocalFile(filename, 'utf8'))
.split(newlineRegex)[0]
@ -19,7 +19,9 @@ async function getNodeFile(filename: string): Promise<string> | null {
return null;
}
function getPackageJsonConstraint(config: PostUpdateConfig): string | null {
function getPackageJsonConstraint(
config: Partial<PostUpdateConfig>
): string | null {
const constraint: string = config.constraints?.node;
if (constraint && semver.validRange(constraint)) {
logger.debug(`Using node constraint "${constraint}" from package.json`);
@ -29,8 +31,8 @@ function getPackageJsonConstraint(config: PostUpdateConfig): string | null {
}
export async function getNodeConstraint(
config: PostUpdateConfig
): Promise<string> | null {
config: Partial<PostUpdateConfig>
): Promise<string | null> {
const { packageFile } = config;
const constraint =
(await getNodeFile(getSiblingFileName(packageFile, '.nvmrc'))) ||

View file

@ -6,7 +6,11 @@ import {
} from '../../../../constants/error-messages';
import { logger } from '../../../../logger';
import { exec } from '../../../../util/exec';
import type { ExecOptions, ToolConstraint } from '../../../../util/exec/types';
import type {
ExecOptions,
ExtraEnv,
ToolConstraint,
} from '../../../../util/exec/types';
import { move, pathExists, readFile, remove } from '../../../../util/fs';
import type { PostUpdateConfig, Upgrade } from '../../types';
import { composeLockFile, parseLockFile } from '../utils';
@ -17,19 +21,19 @@ export async function generateLockFile(
cwd: string,
env: NodeJS.ProcessEnv,
filename: string,
config: PostUpdateConfig = {},
config: Partial<PostUpdateConfig> = {},
upgrades: Upgrade[] = []
): Promise<GenerateLockFileResult> {
logger.debug(`Spawning npm install to create ${cwd}/${filename}`);
const { skipInstalls, postUpdateOptions } = config;
let lockFile = null;
let lockFile: string | null = null;
try {
const npmToolConstraint: ToolConstraint = {
toolName: 'npm',
constraint: config.constraints?.npm,
};
const commands = [];
const commands: string[] = [];
let cmdOptions = '';
if (postUpdateOptions?.includes('npmDedupe') || skipInstalls === false) {
logger.debug('Performing node_modules install');
@ -44,12 +48,13 @@ export async function generateLockFile(
}
const tagConstraint = await getNodeConstraint(config);
const extraEnv: ExtraEnv = {
NPM_CONFIG_CACHE: env.NPM_CONFIG_CACHE,
npm_config_store: env.npm_config_store,
};
const execOptions: ExecOptions = {
cwd,
extraEnv: {
NPM_CONFIG_CACHE: env.NPM_CONFIG_CACHE,
npm_config_store: env.npm_config_store,
},
extraEnv,
toolConstraints: [npmToolConstraint],
docker: {
image: 'node',
@ -59,8 +64,8 @@ export async function generateLockFile(
};
// istanbul ignore if
if (GlobalConfig.get('exposeAllEnv')) {
execOptions.extraEnv.NPM_AUTH = env.NPM_AUTH;
execOptions.extraEnv.NPM_EMAIL = env.NPM_EMAIL;
extraEnv.NPM_AUTH = env.NPM_AUTH;
extraEnv.NPM_EMAIL = env.NPM_EMAIL;
}
if (!upgrades.every((upgrade) => upgrade.isLockfileUpdate)) {
@ -131,14 +136,18 @@ export async function generateLockFile(
const { detectedIndent, lockFileParsed } = parseLockFile(lockFile);
if (lockFileParsed?.lockfileVersion === 2) {
lockUpdates.forEach((lockUpdate) => {
const depType = lockUpdate.depType as
| 'dependencies'
| 'optionalDependencies';
if (
lockFileParsed.packages?.['']?.[lockUpdate.depType]?.[
lockUpdate.depName
lockFileParsed.packages?.['']?.[depType]?.[
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
lockUpdate.depName!
]
) {
lockFileParsed.packages[''][lockUpdate.depType][
lockUpdate.depName
] = lockUpdate.newValue;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
lockFileParsed.packages[''][depType]![lockUpdate.depName!] =
lockUpdate.newValue!;
}
});
lockFile = composeLockFile(lockFileParsed, detectedIndent);

View file

@ -1,34 +1,28 @@
import { exec as _exec } from 'child_process';
import { envMock, mockExecAll } from '../../../../../test/exec-util';
import { envMock, exec, mockExecAll } from '../../../../../test/exec-util';
import { Fixtures } from '../../../../../test/fixtures';
import { mocked } from '../../../../../test/util';
import * as _env from '../../../../util/exec/env';
import * as _fs from '../../../../util/fs/proxies';
import { env, fs, partial } from '../../../../../test/util';
import type { PostUpdateConfig } from '../../types';
import * as _pnpmHelper from './pnpm';
import * as pnpmHelper from './pnpm';
jest.mock('child_process');
jest.mock('../../../../util/exec/env');
jest.mock('../../../../util/fs/proxies');
jest.mock('./node-version');
const exec: jest.Mock<typeof _exec> = _exec as any;
const env = mocked(_env);
const fs = mocked(_fs);
const pnpmHelper = mocked(_pnpmHelper);
delete process.env.NPM_CONFIG_CACHE;
describe('modules/manager/npm/post-update/pnpm', () => {
let config: PostUpdateConfig;
beforeEach(() => {
config = { cacheDir: 'some-cache-dir', constraints: { pnpm: '^2.0.0' } };
jest.resetAllMocks();
config = partial<PostUpdateConfig>({ constraints: { pnpm: '^2.0.0' } });
env.getChildProcessEnv.mockReturnValue(envMock.basic);
});
it('generates lock files', async () => {
const execSnapshots = mockExecAll(exec);
fs.readFile = jest.fn(() => 'package-lock-contents') as never;
fs.readFile.mockResolvedValue('package-lock-contents');
const res = await pnpmHelper.generateLockFile('some-dir', {}, config);
expect(fs.readFile).toHaveBeenCalledTimes(1);
expect(res.lockFile).toBe('package-lock-contents');
@ -37,9 +31,9 @@ describe('modules/manager/npm/post-update/pnpm', () => {
it('catches errors', async () => {
const execSnapshots = mockExecAll(exec);
fs.readFile = jest.fn(() => {
fs.readFile.mockImplementation(() => {
throw new Error('not found');
}) as never;
});
const res = await pnpmHelper.generateLockFile('some-dir', {}, config);
expect(fs.readFile).toHaveBeenCalledTimes(1);
expect(res.error).toBeTrue();
@ -49,7 +43,7 @@ describe('modules/manager/npm/post-update/pnpm', () => {
it('finds pnpm globally', async () => {
const execSnapshots = mockExecAll(exec);
fs.readFile = jest.fn(() => 'package-lock-contents') as never;
fs.readFile.mockResolvedValue('package-lock-contents');
const res = await pnpmHelper.generateLockFile('some-dir', {}, config);
expect(fs.readFile).toHaveBeenCalledTimes(1);
expect(res.lockFile).toBe('package-lock-contents');
@ -58,7 +52,7 @@ describe('modules/manager/npm/post-update/pnpm', () => {
it('performs lock file maintenance', async () => {
const execSnapshots = mockExecAll(exec);
fs.readFile = jest.fn(() => 'package-lock-contents') as never;
fs.readFile.mockResolvedValue('package-lock-contents');
const res = await pnpmHelper.generateLockFile('some-dir', {}, config, [
{ isLockFileMaintenance: true },
]);
@ -70,7 +64,7 @@ describe('modules/manager/npm/post-update/pnpm', () => {
it('uses the new version if packageManager is updated', async () => {
const execSnapshots = mockExecAll(exec);
fs.readFile = jest.fn(() => 'package-lock-contents') as never;
fs.readFile.mockResolvedValue('package-lock-contents');
const res = await pnpmHelper.generateLockFile('some-dir', {}, config, [
{
depType: 'packageManager',
@ -86,12 +80,11 @@ describe('modules/manager/npm/post-update/pnpm', () => {
it('uses constraint version if parent json has constraints', async () => {
const execSnapshots = mockExecAll(exec);
const configTemp = { cacheDir: 'some-cache-dir' };
const configTemp = partial<PostUpdateConfig>({});
const fileContent = Fixtures.get('parent/package.json');
fs.readFile = jest
.fn()
.mockReturnValueOnce(fileContent)
.mockReturnValue('package-lock-contents');
fs.readFile
.mockResolvedValueOnce(fileContent)
.mockResolvedValue('package-lock-contents');
const res = await pnpmHelper.generateLockFile(
'some-folder',
{},
@ -129,12 +122,11 @@ describe('modules/manager/npm/post-update/pnpm', () => {
it('uses packageManager version and puts it into constraint', async () => {
const execSnapshots = mockExecAll(exec);
const configTemp = { cacheDir: 'some-cache-dir' };
const configTemp = partial<PostUpdateConfig>({});
const fileContent = Fixtures.get('manager-field/package.json');
fs.readFile = jest
.fn()
.mockReturnValueOnce(fileContent)
.mockReturnValue('package-lock-contents');
fs.readFile
.mockResolvedValueOnce(fileContent)
.mockResolvedValue('package-lock-contents');
const res = await pnpmHelper.generateLockFile(
'some-folder',
{},

View file

@ -3,7 +3,11 @@ import { GlobalConfig } from '../../../../config/global';
import { TEMPORARY_ERROR } from '../../../../constants/error-messages';
import { logger } from '../../../../logger';
import { exec } from '../../../../util/exec';
import type { ExecOptions, ToolConstraint } from '../../../../util/exec/types';
import type {
ExecOptions,
ExtraEnv,
ToolConstraint,
} from '../../../../util/exec/types';
import { readFile, remove } from '../../../../util/fs';
import type { PostUpdateConfig, Upgrade } from '../../types';
import type { NpmPackage } from '../extract/types';
@ -18,9 +22,9 @@ export async function generateLockFile(
): Promise<GenerateLockFileResult> {
const lockFileName = upath.join(cwd, 'pnpm-lock.yaml');
logger.debug(`Spawning pnpm install to create ${lockFileName}`);
let lockFile = null;
let stdout: string;
let stderr: string;
let lockFile: string | null = null;
let stdout: string | undefined;
let stderr: string | undefined;
let cmd = 'pnpm';
try {
const pnpmToolConstraint: ToolConstraint = {
@ -28,12 +32,13 @@ export async function generateLockFile(
constraint: config.constraints?.pnpm ?? (await getPnpmContraint(cwd)),
};
const tagConstraint = await getNodeConstraint(config);
const extraEnv: ExtraEnv = {
NPM_CONFIG_CACHE: env.NPM_CONFIG_CACHE,
npm_config_store: env.npm_config_store,
};
const execOptions: ExecOptions = {
cwd,
extraEnv: {
NPM_CONFIG_CACHE: env.NPM_CONFIG_CACHE,
npm_config_store: env.npm_config_store,
},
extraEnv,
docker: {
image: 'node',
tagScheme: 'node',
@ -43,8 +48,8 @@ export async function generateLockFile(
};
// istanbul ignore if
if (GlobalConfig.get('exposeAllEnv')) {
execOptions.extraEnv.NPM_AUTH = env.NPM_AUTH;
execOptions.extraEnv.NPM_EMAIL = env.NPM_EMAIL;
extraEnv.NPM_AUTH = env.NPM_AUTH;
extraEnv.NPM_EMAIL = env.NPM_EMAIL;
}
cmd = 'pnpm';
let args = 'install --recursive --lockfile-only';
@ -89,8 +94,8 @@ export async function generateLockFile(
return { lockFile };
}
async function getPnpmContraint(cwd: string): Promise<string> {
let result;
async function getPnpmContraint(cwd: string): Promise<string | undefined> {
let result: string | undefined;
const rootPackageJson = upath.join(cwd, 'package.json');
const content = await readFile(rootPackageJson, 'utf8');
if (content) {

View file

@ -14,7 +14,7 @@ export interface AdditionalPackageFiles {
export interface ArtifactError {
lockFile: string;
stderr: string;
stderr?: string;
}
export interface WriteExistingFilesResult {

View file

@ -10,7 +10,7 @@ import {
import { logger } from '../../../../logger';
import { ExternalHostError } from '../../../../types/errors/external-host-error';
import { exec } from '../../../../util/exec';
import type { ExecOptions } from '../../../../util/exec/types';
import type { ExecOptions, ExtraEnv } from '../../../../util/exec/types';
import { exists, readFile, remove, writeFile } from '../../../../util/fs';
import { newlineRegex, regEx } from '../../../../util/regex';
import { uniqueStrings } from '../../../../util/string';
@ -23,7 +23,7 @@ export async function checkYarnrc(
cwd: string
): Promise<{ offlineMirror: boolean; yarnPath: string | null }> {
let offlineMirror = false;
let yarnPath: string = null;
let yarnPath: string | null = null;
try {
const yarnrc = await readFile(`${cwd}/.yarnrc`, 'utf8');
if (is.string(yarnrc)) {
@ -37,7 +37,7 @@ export async function checkYarnrc(
if (pathLine) {
yarnPath = pathLine.replace(regEx(/^yarn-path\s+"?(.+?)"?$/), '$1');
}
const yarnBinaryExists = await exists(yarnPath);
const yarnBinaryExists = yarnPath ? await exists(yarnPath) : false;
if (!yarnBinaryExists) {
const scrubbedYarnrc = yarnrc.replace(
regEx(/^yarn-path\s+"?.+?"?$/gm),
@ -66,12 +66,12 @@ export function isYarnUpdate(upgrade: Upgrade): boolean {
export async function generateLockFile(
cwd: string,
env: NodeJS.ProcessEnv,
config: PostUpdateConfig = {},
config: Partial<PostUpdateConfig> = {},
upgrades: Upgrade[] = []
): Promise<GenerateLockFileResult> {
const lockFileName = upath.join(cwd, 'yarn.lock');
logger.debug(`Spawning yarn install to create ${lockFileName}`);
let lockFile = null;
let lockFile: string | null = null;
try {
const yarnUpdate = upgrades.find(isYarnUpdate);
const yarnCompatibility = yarnUpdate
@ -93,13 +93,13 @@ export async function generateLockFile(
const preCommands = [installYarn];
const extraEnv: ExecOptions['extraEnv'] = {
const extraEnv: ExtraEnv = {
NPM_CONFIG_CACHE: env.NPM_CONFIG_CACHE,
npm_config_store: env.npm_config_store,
CI: 'true',
};
const commands = [];
const commands: string[] = [];
let cmdOptions = ''; // should have a leading space
if (config.skipInstalls !== false) {
if (isYarn1) {
@ -157,8 +157,8 @@ export async function generateLockFile(
};
// istanbul ignore if
if (GlobalConfig.get('exposeAllEnv')) {
execOptions.extraEnv.NPM_AUTH = env.NPM_AUTH;
execOptions.extraEnv.NPM_EMAIL = env.NPM_EMAIL;
extraEnv.NPM_AUTH = env.NPM_AUTH;
extraEnv.NPM_EMAIL = env.NPM_EMAIL;
}
if (yarnUpdate && !isYarn1) {
@ -179,6 +179,7 @@ export async function generateLockFile(
commands.push(
`yarn upgrade ${lockUpdates
.map((update) => update.depName)
.filter(is.string)
.filter(uniqueStrings)
.join(' ')}${cmdOptions}`
);

View file

@ -6,7 +6,8 @@ import type { RangeConfig } from '../types';
export function getRangeStrategy(config: RangeConfig): RangeStrategy {
const { depType, depName, packageJsonType, currentValue, rangeStrategy } =
config;
const isComplexRange = parseRange(currentValue).length > 1;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
const isComplexRange = parseRange(currentValue!).length > 1;
if (rangeStrategy === 'bump' && isComplexRange) {
logger.debug(
{ currentValue },

View file

@ -62,3 +62,10 @@ export interface ParseLockFileResult {
detectedIndent: string;
lockFileParsed: LockFile | undefined;
}
export type NpmDepType =
| 'dependencies'
| 'devDependencies'
| 'optionalDependencies'
| 'peerDependencies'
| 'resolutions';

View file

@ -4,6 +4,7 @@ import { escapeRegExp, regEx } from '../../../../../util/regex';
import { matchAt, replaceAt } from '../../../../../util/string';
import type { UpdateDependencyConfig } from '../../../types';
import type { DependenciesMeta, NpmPackage } from '../../extract/types';
import type { NpmDepType } from '../../types';
function renameObjKey(
oldObj: DependenciesMeta,
@ -18,35 +19,36 @@ function renameObjKey(
acc[key] = oldObj[key];
}
return acc;
}, {});
}, {} as DependenciesMeta);
}
function replaceAsString(
parsedContents: NpmPackage,
fileContent: string,
depType: string,
depType: NpmDepType | 'dependenciesMeta' | 'packageManager',
depName: string,
oldValue: string,
newValue: string
): string | null {
): string {
if (depType === 'packageManager') {
parsedContents[depType] = newValue;
} else if (depName === oldValue) {
// The old value is the name of the dependency itself
delete Object.assign(parsedContents[depType], {
[newValue]: parsedContents[depType][oldValue],
[newValue]: parsedContents[depType]![oldValue],
})[oldValue];
} else if (depType === 'dependenciesMeta') {
if (oldValue !== newValue) {
parsedContents.dependenciesMeta = renameObjKey(
parsedContents.dependenciesMeta,
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
parsedContents.dependenciesMeta!,
oldValue,
newValue
);
}
} else {
// The old value is the version of the dependency
parsedContents[depType][depName] = newValue;
parsedContents[depType]![depName] = newValue;
}
// Look for the old version number
const searchString = `"${oldValue}"`;
@ -55,9 +57,9 @@ function replaceAsString(
const escapedDepName = escapeRegExp(depName);
const patchRe = regEx(`^(patch:${escapedDepName}@(npm:)?).*#`);
const match = patchRe.exec(oldValue);
if (match) {
if (match && depType === 'resolutions') {
const patch = oldValue.replace(match[0], `${match[1]}${newValue}#`);
parsedContents[depType][depName] = patch;
parsedContents[depType]![depName] = patch;
newString = `"${patch}"`;
}
@ -98,7 +100,8 @@ export function updateDependency({
logger.debug('Updating package.json git digest');
newValue = upgrade.currentRawValue.replace(
upgrade.currentDigest,
upgrade.newDigest.substring(0, upgrade.currentDigest.length)
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
upgrade.newDigest!.substring(0, upgrade.currentDigest.length)
);
} else {
logger.debug('Updating package.json git version tag');
@ -115,36 +118,38 @@ export function updateDependency({
try {
const parsedContents: NpmPackage = JSON.parse(fileContent);
// Save the old version
let oldVersion: string;
let oldVersion: string | undefined;
if (depType === 'packageManager') {
oldVersion = parsedContents[depType];
newValue = `${depName}@${newValue}`;
} else {
oldVersion = parsedContents[depType][depName];
oldVersion = parsedContents[depType as NpmDepType]![depName];
}
if (oldVersion === newValue) {
logger.trace('Version is already updated');
return fileContent;
}
/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */
let newFileContent = replaceAsString(
parsedContents,
fileContent,
depType,
depType as NpmDepType,
depName,
oldVersion,
newValue
oldVersion!,
newValue!
);
if (upgrade.newName) {
newFileContent = replaceAsString(
parsedContents,
newFileContent,
depType,
depType as NpmDepType,
depName,
depName,
upgrade.newName
);
}
/* eslint-enable @typescript-eslint/no-unnecessary-type-assertion */
// istanbul ignore if
if (!newFileContent) {
logger.debug(
@ -154,7 +159,7 @@ export function updateDependency({
return fileContent;
}
if (parsedContents?.resolutions) {
let depKey: string;
let depKey: string | undefined;
if (parsedContents.resolutions[depName]) {
depKey = depName;
} else if (parsedContents.resolutions[`**/${depName}`]) {
@ -179,7 +184,8 @@ export function updateDependency({
'resolutions',
depKey,
parsedContents.resolutions[depKey],
newValue
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
newValue!
);
if (upgrade.newName) {
if (depKey === `**/${depName}`) {

View file

@ -75,7 +75,7 @@ export async function findFirstParentVersion(
for (const parentVersion of parentVersions) {
const constraint = parentDep.releases.find(
(release) => release.version === parentVersion
).dependencies?.[targetDepName];
)?.dependencies?.[targetDepName];
if (!constraint) {
logger.debug(
`${targetDepName} has been removed from ${parentName}@${parentVersion}`

View file

@ -30,11 +30,14 @@ export async function updateLockedDependency(
try {
let packageJson: PackageJson;
let packageLockJson: PackageLockOrEntry;
const detectedIndent = detectIndent(lockFileContent).indent || ' ';
let newPackageJsonContent: string;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
const detectedIndent = detectIndent(lockFileContent!).indent || ' ';
let newPackageJsonContent: string | null | undefined;
try {
packageJson = JSON.parse(packageFileContent);
packageLockJson = JSON.parse(lockFileContent);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
packageJson = JSON.parse(packageFileContent!);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
packageLockJson = JSON.parse(lockFileContent!);
} catch (err) {
logger.warn({ err }, 'Failed to parse files');
return { status: 'update-failed' };
@ -43,7 +46,8 @@ export async function updateLockedDependency(
const lockedDeps = getLockedDependencies(
packageLockJson,
depName,
currentVersion
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
currentVersion!
);
if (lockedDeps.some((dep) => dep.bundled)) {
logger.info(
@ -109,10 +113,12 @@ export async function updateLockedDependency(
// Don't return {} if we're a parent update or else the whole update will fail
// istanbul ignore if: too hard to replicate
if (isParentUpdate) {
const res: UpdateLockedResult = { status, files: {} };
res.files[packageFile] = packageFileContent;
res.files[lockFile] = lockFileContent;
return res;
const files: Record<string, string> = {};
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
files[packageFile!] = packageFileContent!;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
files[lockFile!] = lockFileContent!;
return { status, files: files };
}
return { status };
}
@ -123,7 +129,8 @@ export async function updateLockedDependency(
packageJson,
packageLockJson,
depName,
currentVersion,
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
currentVersion!,
newVersion
);
logger.trace({ deps: lockedDeps, constraints }, 'Matching details');
@ -134,7 +141,7 @@ export async function updateLockedDependency(
);
return { status: 'update-failed' };
}
const parentUpdates: UpdateLockedConfig[] = [];
const parentUpdates: Partial<UpdateLockedConfig>[] = [];
for (const {
parentDepName,
parentVersion,
@ -172,7 +179,7 @@ export async function updateLockedDependency(
logger.debug(
`Update of ${depName} to ${newVersion} can be achieved due to parent ${parentDepName}`
);
const parentUpdate: UpdateLockedConfig = {
const parentUpdate: Partial<UpdateLockedConfig> = {
depName: parentDepName,
currentVersion: parentVersion,
newVersion: parentNewVersion,
@ -187,15 +194,17 @@ export async function updateLockedDependency(
return { status: 'update-failed' };
}
} else if (depType) {
// TODO: `newValue` can probably null
// 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,
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
fileContent: packageFileContent!,
upgrade: { depName, depType, newValue },
});
}
@ -215,9 +224,9 @@ export async function updateLockedDependency(
for (const parentUpdate of parentUpdates) {
const parentUpdateConfig = {
...config,
...parentUpdate,
lockFileContent: newLockFileContent,
packageFileContent: newPackageJsonContent || packageFileContent,
...parentUpdate,
};
const parentUpdateResult = await updateLockedDependency(
parentUpdateConfig,
@ -235,7 +244,7 @@ export async function updateLockedDependency(
newLockFileContent =
parentUpdateResult.files[lockFile] || newLockFileContent;
}
const files = {};
const files: Record<string, string> = {};
if (newLockFileContent) {
files[lockFile] = newLockFileContent;
}

View file

@ -1,4 +1,4 @@
import { loadFixture } from '../../../../../../../test/util';
import { loadFixture, partial } from '../../../../../../../test/util';
import type { UpdateLockedConfig } from '../../../../types';
import { updateLockedDependency } from '.';
@ -10,7 +10,7 @@ describe('modules/manager/npm/update/locked-dependency/yarn-lock/index', () => {
let config: UpdateLockedConfig;
beforeEach(() => {
config = {};
config = partial<UpdateLockedConfig>({ packageFile: 'package.json' });
});
it('returns if cannot parse lock file', () => {

View file

@ -16,7 +16,8 @@ export function updateLockedDependency(
);
let yarnLock: YarnLock;
try {
yarnLock = parseSyml(lockFileContent);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
yarnLock = parseSyml(lockFileContent!);
} catch (err) {
logger.warn({ err }, 'Failed to parse yarn files');
return { status: 'update-failed' };
@ -26,7 +27,12 @@ export function updateLockedDependency(
return { status: 'unsupported' };
}
try {
const lockedDeps = getLockedDependencies(yarnLock, depName, currentVersion);
const lockedDeps = getLockedDependencies(
yarnLock,
depName,
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
currentVersion!
);
if (!lockedDeps.length) {
const newLockedDeps = getLockedDependencies(
yarnLock,
@ -61,7 +67,8 @@ export function updateLockedDependency(
);
return { status: 'update-failed' };
}
let newLockFileContent = lockFileContent;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
let newLockFileContent = lockFileContent!;
for (const dependency of updateLockedDeps) {
const { depName, constraint, newVersion } = dependency;
newLockFileContent = replaceConstraintVersion(

View file

@ -12,7 +12,7 @@ export function bumpPackageVersion(
{ bumpVersion, currentValue },
'Checking if we should bump package.json version'
);
let newPjVersion: string;
let newPjVersion: string | null;
let bumpedContent = content;
try {
if (bumpVersion.startsWith('mirror:')) {

View file

@ -2,11 +2,16 @@ import { loadFixture } from '../../../../test/util';
import type { UpdateLockedConfig } from '../types';
import { updateLockedDependency } from '.';
const lockFileContent = loadFixture('pyproject.11.toml.lock');
const lockFile = 'pyproject.11.toml.lock';
const packageFile = 'pyproject.11.toml';
const lockFileContent = loadFixture(lockFile);
describe('modules/manager/poetry/update-locked', () => {
it('detects already updated', () => {
const config: UpdateLockedConfig = {
packageFile,
lockFile,
lockFileContent,
depName: 'urllib3',
newVersion: '1.26.3',
@ -16,6 +21,8 @@ describe('modules/manager/poetry/update-locked', () => {
it('returns unsupported', () => {
const config: UpdateLockedConfig = {
packageFile,
lockFile,
lockFileContent,
depName: 'urllib3',
newVersion: '1.26.4',

View file

@ -133,7 +133,7 @@ export async function updateArtifacts({
}
const res = writeLockUpdates(updates, lockFilePath, lockFileContent);
return res ? [res] : null;
return [res];
} catch (err) {
/* istanbul ignore next */
return [

View file

@ -123,7 +123,7 @@ export interface Package<T> extends ManagerData<T> {
pinDigests?: boolean;
currentRawValue?: string;
major?: { enabled?: boolean };
prettyDepType?: any;
prettyDepType?: string;
}
export interface LookupUpdate {
@ -159,7 +159,7 @@ export interface PackageDependency<T = Record<string, any>> extends Package<T> {
digestOneAndOnly?: boolean;
fixedVersion?: string;
currentVersion?: string;
lockedVersion?: string;
lockedVersion?: string | null;
propSource?: string;
registryUrls?: string[] | null;
rangeStrategy?: RangeStrategy;
@ -224,13 +224,13 @@ export interface BumpPackageVersionResult {
}
export interface UpdateLockedConfig {
packageFile?: string;
packageFile: string;
packageFileContent?: string;
lockFile?: string;
lockFile: string;
lockFileContent?: string;
depName?: string;
depName: string;
currentVersion?: string;
newVersion?: string;
newVersion: string;
allowParentUpdates?: boolean;
allowHigherOrRemoved?: boolean;
}
@ -296,9 +296,9 @@ export interface PostUpdateConfig<T = Record<string, any>>
ignoreScripts?: boolean;
platform?: string;
upgrades?: Upgrade[];
upgrades: Upgrade[];
npmLock?: string;
yarnLock?: string;
branchName?: string;
branchName: string;
reuseExistingBranch?: boolean;
}

View file

@ -13,7 +13,7 @@ export interface VersioningApi {
isSingleVersion(version: string): boolean;
isStable(version: string): boolean;
isValid(input: string): boolean;
isVersion(input: string): boolean;
isVersion(input: string | undefined | null): boolean;
// digestion of version
getMajor(version: string | SemVer): null | number;

View file

@ -75,7 +75,11 @@ export async function getUpdatedPackageFiles(
} else if (upgrade.isRemediation) {
const { status, files } = await updateLockedDependency({
...upgrade,
depName,
newVersion,
packageFile,
packageFileContent,
lockFile,
lockFileContent,
allowParentUpdates: true,
allowHigherOrRemoved: true,
@ -100,8 +104,11 @@ export async function getUpdatedPackageFiles(
if (updateLockedDependency) {
const { status, files } = await updateLockedDependency({
...upgrade,
lockFile,
depName,
newVersion,
packageFile,
packageFileContent,
lockFile,
lockFileContent,
allowParentUpdates: false,
});

View file

@ -10,7 +10,11 @@ import type { PostUpdateConfig } from '../../../../../modules/manager/types';
import * as _fs from '../../../../../util/fs/proxies';
import * as _hostRules from '../../../../../util/host-rules';
const config: PostUpdateConfig = getConfig();
const config: PostUpdateConfig = {
...getConfig(),
upgrades: [],
branchName: 'some-branch',
};
const fs = mocked(_fs);
const lockFiles = mocked(_lockFiles);

View file

@ -39,35 +39,6 @@
"lib/config/validation-helpers/managers.ts",
"lib/config/validation.ts",
"lib/modules/datasource/github-releases/test/index.ts",
"lib/modules/manager/api.ts",
"lib/modules/manager/index.ts",
"lib/modules/manager/npm/detect.ts",
"lib/modules/manager/npm/extract/index.ts",
"lib/modules/manager/npm/extract/locked-versions.ts",
"lib/modules/manager/npm/extract/monorepo.ts",
"lib/modules/manager/npm/extract/npm.ts",
"lib/modules/manager/npm/extract/pnpm.ts",
"lib/modules/manager/npm/extract/utils.ts",
"lib/modules/manager/npm/extract/yarn.ts",
"lib/modules/manager/npm/index.ts",
"lib/modules/manager/npm/post-update/index.ts",
"lib/modules/manager/npm/post-update/lerna.ts",
"lib/modules/manager/npm/post-update/node-version.ts",
"lib/modules/manager/npm/post-update/npm.ts",
"lib/modules/manager/npm/post-update/pnpm.ts",
"lib/modules/manager/npm/post-update/yarn.ts",
"lib/modules/manager/npm/range.ts",
"lib/modules/manager/npm/update/dependency/index.ts",
"lib/modules/manager/npm/update/index.ts",
"lib/modules/manager/npm/update/locked-dependency/common/parent-version.ts",
"lib/modules/manager/npm/update/locked-dependency/index.ts",
"lib/modules/manager/npm/update/locked-dependency/package-lock/dep-constraints.ts",
"lib/modules/manager/npm/update/locked-dependency/package-lock/get-locked.ts",
"lib/modules/manager/npm/update/locked-dependency/package-lock/index.ts",
"lib/modules/manager/npm/update/locked-dependency/yarn-lock/get-locked.ts",
"lib/modules/manager/npm/update/locked-dependency/yarn-lock/index.ts",
"lib/modules/manager/npm/update/locked-dependency/yarn-lock/replace.ts",
"lib/modules/manager/npm/update/package-version/index.ts",
"lib/modules/platform/api.ts",
"lib/modules/platform/azure/azure-got-wrapper.ts",
"lib/modules/platform/azure/azure-helper.ts",