From 868ebbef636de7a78b719dc607001e3de391f32c Mon Sep 17 00:00:00 2001 From: Michael Kriese Date: Wed, 20 Apr 2022 08:40:20 +0200 Subject: [PATCH] refactor(manager): final strict null checks (#15185) * refactor(manager): final strict null checks * refactor: fix type issues * test: fix mocking --- .../manager/bundler/update-locked.spec.ts | 4 + .../manager/composer/update-locked.spec.ts | 6 ++ lib/modules/manager/index.ts | 14 +-- lib/modules/manager/npm/extract/index.ts | 37 ++++---- .../manager/npm/extract/locked-versions.ts | 23 +++-- lib/modules/manager/npm/extract/monorepo.ts | 7 +- lib/modules/manager/npm/extract/pnpm.spec.ts | 4 +- lib/modules/manager/npm/extract/pnpm.ts | 12 +-- lib/modules/manager/npm/extract/types.ts | 2 + lib/modules/manager/npm/extract/yarn.ts | 2 +- lib/modules/manager/npm/post-update/index.ts | 88 ++++++++++++------- .../manager/npm/post-update/lerna.spec.ts | 40 +++++---- lib/modules/manager/npm/post-update/lerna.ts | 22 ++--- .../manager/npm/post-update/node-version.ts | 10 ++- lib/modules/manager/npm/post-update/npm.ts | 39 ++++---- .../manager/npm/post-update/pnpm.spec.ts | 46 ++++------ lib/modules/manager/npm/post-update/pnpm.ts | 29 +++--- lib/modules/manager/npm/post-update/types.ts | 2 +- lib/modules/manager/npm/post-update/yarn.ts | 19 ++-- lib/modules/manager/npm/range.ts | 3 +- lib/modules/manager/npm/types.ts | 7 ++ .../manager/npm/update/dependency/index.ts | 40 +++++---- .../common/parent-version.ts | 2 +- .../locked-dependency/package-lock/index.ts | 41 +++++---- .../locked-dependency/yarn-lock/index.spec.ts | 4 +- .../locked-dependency/yarn-lock/index.ts | 13 ++- .../npm/update/package-version/index.ts | 2 +- .../manager/poetry/update-locked.spec.ts | 9 +- .../manager/terraform/lockfile/index.ts | 2 +- lib/modules/manager/types.ts | 16 ++-- lib/modules/versioning/types.ts | 2 +- .../repository/update/branch/get-updated.ts | 9 +- .../update/branch/lock-files/index.spec.ts | 6 +- tsconfig.strict.json | 29 ------ 34 files changed, 337 insertions(+), 254 deletions(-) diff --git a/lib/modules/manager/bundler/update-locked.spec.ts b/lib/modules/manager/bundler/update-locked.spec.ts index 92840526f81141..61bd70fff6f0c3 100644 --- a/lib/modules/manager/bundler/update-locked.spec.ts +++ b/lib/modules/manager/bundler/update-locked.spec.ts @@ -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', diff --git a/lib/modules/manager/composer/update-locked.spec.ts b/lib/modules/manager/composer/update-locked.spec.ts index d797e732f94a1a..62cd056ec2833d 100644 --- a/lib/modules/manager/composer/update-locked.spec.ts +++ b/lib/modules/manager/composer/update-locked.spec.ts @@ -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', diff --git a/lib/modules/manager/index.ts b/lib/modules/manager/index.ts index 1cd65b45a3454e..08e5eae3560d9d 100644 --- a/lib/modules/manager/index.ts +++ b/lib/modules/manager/index.ts @@ -17,7 +17,7 @@ const languageList = Object.values(ProgrammingLanguage); export function get( 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 => managers; export async function detectAllGlobalConfig(): Promise { 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); diff --git a/lib/modules/manager/npm/extract/index.ts b/lib/modules/manager/npm/extract/index.ts index 7d9dcc4d612e2d..56e909caa40374 100644 --- a/lib/modules/manager/npm/extract/index.ts +++ b/lib/modules/manager/npm/extract/index.ts @@ -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('^(?.+)@(?.+)$').exec(dependencies); + const match = regEx('^(?.+)@(?.+)$').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; } diff --git a/lib/modules/manager/npm/extract/locked-versions.ts b/lib/modules/manager/npm/extract/locked-versions.ts index 09dd8c5ec27453..442ae37e2df227 100644 --- a/lib/modules/manager/npm/extract/locked-versions.ts +++ b/lib/modules/manager/npm/extract/locked-versions.ts @@ -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) { diff --git a/lib/modules/manager/npm/extract/monorepo.ts b/lib/modules/manager/npm/extract/monorepo.ts index 3f26126ad8abb8..bcbbaf89d6cd40 100644 --- a/lib/modules/manager/npm/extract/monorepo.ts +++ b/lib/modules/manager/npm/extract/monorepo.ts @@ -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 ) ); diff --git a/lib/modules/manager/npm/extract/pnpm.spec.ts b/lib/modules/manager/npm/extract/pnpm.spec.ts index 93f4548dcce295..ff034ea3ad6e34 100644 --- a/lib/modules/manager/npm/extract/pnpm.spec.ts +++ b/lib/modules/manager/npm/extract/pnpm.spec.ts @@ -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), diff --git a/lib/modules/manager/npm/extract/pnpm.ts b/lib/modules/manager/npm/extract/pnpm.ts index 20339689bbba63..93136088f595d2 100644 --- a/lib/modules/manager/npm/extract/pnpm.ts +++ b/lib/modules/manager/npm/extract/pnpm.ts @@ -15,7 +15,7 @@ import type { PnpmWorkspaceFile } from './types'; export async function extractPnpmFilters( fileName: string -): Promise { +): Promise { 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) { diff --git a/lib/modules/manager/npm/extract/types.ts b/lib/modules/manager/npm/extract/types.ts index 8d01d3d18d0e55..2ba3a6fc89437c 100644 --- a/lib/modules/manager/npm/extract/types.ts +++ b/lib/modules/manager/npm/extract/types.ts @@ -13,6 +13,8 @@ export interface NpmPackage extends PackageJson { _id?: any; dependenciesMeta?: DependenciesMeta; packageManager?: string; + + volta?: PackageJson.Dependency; } export type LockFileEntry = Record< diff --git a/lib/modules/manager/npm/extract/yarn.ts b/lib/modules/manager/npm/extract/yarn.ts index 1b3874908a4874..f45e2378b5ad07 100644 --- a/lib/modules/manager/npm/extract/yarn.ts +++ b/lib/modules/manager/npm/extract/yarn.ts @@ -14,7 +14,7 @@ export async function getYarnLock(filePath: string): Promise { try { const parsed = parseSyml(yarnLockRaw); const lockedVersions: Record = {}; - let lockfileVersion: number; + let lockfileVersion: number | undefined; for (const [key, val] of Object.entries(parsed)) { if (key === '__metadata') { diff --git a/lib/modules/manager/npm/post-update/index.ts b/lib/modules/manager/npm/post-update/index.ts index 5552c277de7a29..869c48df4f5722 100644 --- a/lib/modules/manager/npm/post-update/index.ts +++ b/lib/modules/manager/npm/post-update/index.ts @@ -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 { 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 { 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 { // istanbul ignore next async function updateNpmrcContent( dir: string, - originalContent: string, + originalContent: string | null, additionalLines: string[] ): Promise { 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 { 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, }); } diff --git a/lib/modules/manager/npm/post-update/lerna.spec.ts b/lib/modules/manager/npm/post-update/lerna.spec.ts index 1017651f357aea..01d29491f310a5 100644 --- a/lib/modules/manager/npm/post-update/lerna.spec.ts +++ b/lib/modules/manager/npm/post-update/lerna.spec.ts @@ -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 = _exec as any; -const env = mocked(_env); -const lernaHelper = mocked(_lernaHelper); - -function lernaPkgFile(lernaClient: string) { +function lernaPkgFile(lernaClient: string): Partial { return { lernaClient, deps: [{ depName: 'lerna', currentValue: '2.0.0' }], }; } -function lernaPkgFileWithoutLernaDep(lernaClient: string) { +function lernaPkgFileWithoutLernaDep( + lernaClient: string +): Partial { return { lernaClient, }; } +const config = partial({}); + 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(); diff --git a/lib/modules/manager/npm/post-update/lerna.ts b/lib/modules/manager/npm/post-update/lerna.ts index 5591af8389d1ad..745d48d1cbec2a 100644 --- a/lib/modules/manager/npm/post-update/lerna.ts +++ b/lib/modules/manager/npm/post-update/lerna.ts @@ -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); diff --git a/lib/modules/manager/npm/post-update/node-version.ts b/lib/modules/manager/npm/post-update/node-version.ts index 5805719beb597a..ad4f4b168182ff 100644 --- a/lib/modules/manager/npm/post-update/node-version.ts +++ b/lib/modules/manager/npm/post-update/node-version.ts @@ -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 | null { +async function getNodeFile(filename: string): Promise { try { const constraint = (await readLocalFile(filename, 'utf8')) .split(newlineRegex)[0] @@ -19,7 +19,9 @@ async function getNodeFile(filename: string): Promise | null { return null; } -function getPackageJsonConstraint(config: PostUpdateConfig): string | null { +function getPackageJsonConstraint( + config: Partial +): 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 | null { + config: Partial +): Promise { const { packageFile } = config; const constraint = (await getNodeFile(getSiblingFileName(packageFile, '.nvmrc'))) || diff --git a/lib/modules/manager/npm/post-update/npm.ts b/lib/modules/manager/npm/post-update/npm.ts index 5bb0b977097da8..e562ba5747dd86 100644 --- a/lib/modules/manager/npm/post-update/npm.ts +++ b/lib/modules/manager/npm/post-update/npm.ts @@ -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 = {}, upgrades: Upgrade[] = [] ): Promise { 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); diff --git a/lib/modules/manager/npm/post-update/pnpm.spec.ts b/lib/modules/manager/npm/post-update/pnpm.spec.ts index 6a38875fbb023f..9c781005af1721 100644 --- a/lib/modules/manager/npm/post-update/pnpm.spec.ts +++ b/lib/modules/manager/npm/post-update/pnpm.spec.ts @@ -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 = _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({ 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({}); 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({}); 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', {}, diff --git a/lib/modules/manager/npm/post-update/pnpm.ts b/lib/modules/manager/npm/post-update/pnpm.ts index 0b46c381b845cb..5345a5023097ac 100644 --- a/lib/modules/manager/npm/post-update/pnpm.ts +++ b/lib/modules/manager/npm/post-update/pnpm.ts @@ -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 { 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 { - let result; +async function getPnpmContraint(cwd: string): Promise { + let result: string | undefined; const rootPackageJson = upath.join(cwd, 'package.json'); const content = await readFile(rootPackageJson, 'utf8'); if (content) { diff --git a/lib/modules/manager/npm/post-update/types.ts b/lib/modules/manager/npm/post-update/types.ts index 13e92446e5b48c..ea2ada772e5079 100644 --- a/lib/modules/manager/npm/post-update/types.ts +++ b/lib/modules/manager/npm/post-update/types.ts @@ -14,7 +14,7 @@ export interface AdditionalPackageFiles { export interface ArtifactError { lockFile: string; - stderr: string; + stderr?: string; } export interface WriteExistingFilesResult { diff --git a/lib/modules/manager/npm/post-update/yarn.ts b/lib/modules/manager/npm/post-update/yarn.ts index b76fb9fd300c24..875220bad1b121 100644 --- a/lib/modules/manager/npm/post-update/yarn.ts +++ b/lib/modules/manager/npm/post-update/yarn.ts @@ -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 = {}, upgrades: Upgrade[] = [] ): Promise { 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}` ); diff --git a/lib/modules/manager/npm/range.ts b/lib/modules/manager/npm/range.ts index f5df53d539c8d4..051f6c736018de 100644 --- a/lib/modules/manager/npm/range.ts +++ b/lib/modules/manager/npm/range.ts @@ -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 }, diff --git a/lib/modules/manager/npm/types.ts b/lib/modules/manager/npm/types.ts index 187e787b8ad89f..1e7b6d5c93397e 100644 --- a/lib/modules/manager/npm/types.ts +++ b/lib/modules/manager/npm/types.ts @@ -62,3 +62,10 @@ export interface ParseLockFileResult { detectedIndent: string; lockFileParsed: LockFile | undefined; } + +export type NpmDepType = + | 'dependencies' + | 'devDependencies' + | 'optionalDependencies' + | 'peerDependencies' + | 'resolutions'; diff --git a/lib/modules/manager/npm/update/dependency/index.ts b/lib/modules/manager/npm/update/dependency/index.ts index c1a518ef33b205..5eb8d9329a0efe 100644 --- a/lib/modules/manager/npm/update/dependency/index.ts +++ b/lib/modules/manager/npm/update/dependency/index.ts @@ -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}`) { diff --git a/lib/modules/manager/npm/update/locked-dependency/common/parent-version.ts b/lib/modules/manager/npm/update/locked-dependency/common/parent-version.ts index 6fcd02f24b541b..42b494780314cd 100644 --- a/lib/modules/manager/npm/update/locked-dependency/common/parent-version.ts +++ b/lib/modules/manager/npm/update/locked-dependency/common/parent-version.ts @@ -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}` diff --git a/lib/modules/manager/npm/update/locked-dependency/package-lock/index.ts b/lib/modules/manager/npm/update/locked-dependency/package-lock/index.ts index 3c27c700d48248..25cabb0d34af42 100644 --- a/lib/modules/manager/npm/update/locked-dependency/package-lock/index.ts +++ b/lib/modules/manager/npm/update/locked-dependency/package-lock/index.ts @@ -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 = {}; + // 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[] = []; 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 = { 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 = {}; if (newLockFileContent) { files[lockFile] = newLockFileContent; } diff --git a/lib/modules/manager/npm/update/locked-dependency/yarn-lock/index.spec.ts b/lib/modules/manager/npm/update/locked-dependency/yarn-lock/index.spec.ts index bc9f29e4d5eb16..7c9b9b431f4a36 100644 --- a/lib/modules/manager/npm/update/locked-dependency/yarn-lock/index.spec.ts +++ b/lib/modules/manager/npm/update/locked-dependency/yarn-lock/index.spec.ts @@ -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({ packageFile: 'package.json' }); }); it('returns if cannot parse lock file', () => { diff --git a/lib/modules/manager/npm/update/locked-dependency/yarn-lock/index.ts b/lib/modules/manager/npm/update/locked-dependency/yarn-lock/index.ts index b0fe6ccc052056..77ec2ac1cc5fa1 100644 --- a/lib/modules/manager/npm/update/locked-dependency/yarn-lock/index.ts +++ b/lib/modules/manager/npm/update/locked-dependency/yarn-lock/index.ts @@ -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( diff --git a/lib/modules/manager/npm/update/package-version/index.ts b/lib/modules/manager/npm/update/package-version/index.ts index f42fa79cd4c625..100cf45cbd3857 100644 --- a/lib/modules/manager/npm/update/package-version/index.ts +++ b/lib/modules/manager/npm/update/package-version/index.ts @@ -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:')) { diff --git a/lib/modules/manager/poetry/update-locked.spec.ts b/lib/modules/manager/poetry/update-locked.spec.ts index 3cc91943b42edb..2151734f477966 100644 --- a/lib/modules/manager/poetry/update-locked.spec.ts +++ b/lib/modules/manager/poetry/update-locked.spec.ts @@ -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', diff --git a/lib/modules/manager/terraform/lockfile/index.ts b/lib/modules/manager/terraform/lockfile/index.ts index a5af21f1a470bb..c8765fa7dccde0 100644 --- a/lib/modules/manager/terraform/lockfile/index.ts +++ b/lib/modules/manager/terraform/lockfile/index.ts @@ -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 [ diff --git a/lib/modules/manager/types.ts b/lib/modules/manager/types.ts index 29d3760750070b..70936babec5447 100644 --- a/lib/modules/manager/types.ts +++ b/lib/modules/manager/types.ts @@ -123,7 +123,7 @@ export interface Package extends ManagerData { pinDigests?: boolean; currentRawValue?: string; major?: { enabled?: boolean }; - prettyDepType?: any; + prettyDepType?: string; } export interface LookupUpdate { @@ -159,7 +159,7 @@ export interface PackageDependency> extends Package { 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> ignoreScripts?: boolean; platform?: string; - upgrades?: Upgrade[]; + upgrades: Upgrade[]; npmLock?: string; yarnLock?: string; - branchName?: string; + branchName: string; reuseExistingBranch?: boolean; } diff --git a/lib/modules/versioning/types.ts b/lib/modules/versioning/types.ts index 0927d662915a49..838431e809f005 100644 --- a/lib/modules/versioning/types.ts +++ b/lib/modules/versioning/types.ts @@ -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; diff --git a/lib/workers/repository/update/branch/get-updated.ts b/lib/workers/repository/update/branch/get-updated.ts index 73771939962845..669b87dbfc443b 100644 --- a/lib/workers/repository/update/branch/get-updated.ts +++ b/lib/workers/repository/update/branch/get-updated.ts @@ -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, }); diff --git a/lib/workers/repository/update/branch/lock-files/index.spec.ts b/lib/workers/repository/update/branch/lock-files/index.spec.ts index 2fe27648a29eb1..bc32122962d98d 100644 --- a/lib/workers/repository/update/branch/lock-files/index.spec.ts +++ b/lib/workers/repository/update/branch/lock-files/index.spec.ts @@ -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); diff --git a/tsconfig.strict.json b/tsconfig.strict.json index e3c3d894ed8915..863f94cd1bfae5 100644 --- a/tsconfig.strict.json +++ b/tsconfig.strict.json @@ -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",