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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,7 +4,7 @@ import { GlobalConfig } from '../../../../config/global';
import { TEMPORARY_ERROR } from '../../../../constants/error-messages'; import { TEMPORARY_ERROR } from '../../../../constants/error-messages';
import { logger } from '../../../../logger'; import { logger } from '../../../../logger';
import { exec } from '../../../../util/exec'; 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 type { PackageFile, PostUpdateConfig } from '../../types';
import { getNodeConstraint } from './node-version'; import { getNodeConstraint } from './node-version';
import type { GenerateLockFileResult } from './types'; import type { GenerateLockFileResult } from './types';
@ -21,7 +21,8 @@ export function getLernaVersion(
); );
return 'latest'; return 'latest';
} }
return lernaDep.currentValue; // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
return lernaDep.currentValue!;
} }
export async function generateLockFiles( export async function generateLockFiles(
@ -37,8 +38,8 @@ export async function generateLockFiles(
return { error: false }; return { error: false };
} }
logger.debug(`Spawning lerna with ${lernaClient} to create lock files`); logger.debug(`Spawning lerna with ${lernaClient} to create lock files`);
const preCommands = []; const preCommands: string[] = [];
const cmd = []; const cmd: string[] = [];
let cmdOptions = ''; let cmdOptions = '';
try { try {
if (lernaClient === 'yarn') { if (lernaClient === 'yarn') {
@ -74,12 +75,13 @@ export async function generateLockFiles(
} }
lernaCommand += cmdOptions; lernaCommand += cmdOptions;
const tagConstraint = await getNodeConstraint(config); 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 = { const execOptions: ExecOptions = {
cwd, cwd,
extraEnv: { extraEnv,
NPM_CONFIG_CACHE: env.NPM_CONFIG_CACHE,
npm_config_store: env.npm_config_store,
},
docker: { docker: {
image: 'node', image: 'node',
tagScheme: 'node', tagScheme: 'node',
@ -89,8 +91,8 @@ export async function generateLockFiles(
}; };
// istanbul ignore if // istanbul ignore if
if (GlobalConfig.get('exposeAllEnv')) { if (GlobalConfig.get('exposeAllEnv')) {
execOptions.extraEnv.NPM_AUTH = env.NPM_AUTH; extraEnv.NPM_AUTH = env.NPM_AUTH;
execOptions.extraEnv.NPM_EMAIL = env.NPM_EMAIL; extraEnv.NPM_EMAIL = env.NPM_EMAIL;
} }
const lernaVersion = getLernaVersion(lernaPackageFile); const lernaVersion = getLernaVersion(lernaPackageFile);
logger.debug('Using lerna version ' + lernaVersion); 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 { newlineRegex, regEx } from '../../../../util/regex';
import type { PostUpdateConfig } from '../../types'; import type { PostUpdateConfig } from '../../types';
async function getNodeFile(filename: string): Promise<string> | null { async function getNodeFile(filename: string): Promise<string | null> {
try { try {
const constraint = (await readLocalFile(filename, 'utf8')) const constraint = (await readLocalFile(filename, 'utf8'))
.split(newlineRegex)[0] .split(newlineRegex)[0]
@ -19,7 +19,9 @@ async function getNodeFile(filename: string): Promise<string> | null {
return null; return null;
} }
function getPackageJsonConstraint(config: PostUpdateConfig): string | null { function getPackageJsonConstraint(
config: Partial<PostUpdateConfig>
): string | null {
const constraint: string = config.constraints?.node; const constraint: string = config.constraints?.node;
if (constraint && semver.validRange(constraint)) { if (constraint && semver.validRange(constraint)) {
logger.debug(`Using node constraint "${constraint}" from package.json`); logger.debug(`Using node constraint "${constraint}" from package.json`);
@ -29,8 +31,8 @@ function getPackageJsonConstraint(config: PostUpdateConfig): string | null {
} }
export async function getNodeConstraint( export async function getNodeConstraint(
config: PostUpdateConfig config: Partial<PostUpdateConfig>
): Promise<string> | null { ): Promise<string | null> {
const { packageFile } = config; const { packageFile } = config;
const constraint = const constraint =
(await getNodeFile(getSiblingFileName(packageFile, '.nvmrc'))) || (await getNodeFile(getSiblingFileName(packageFile, '.nvmrc'))) ||

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,7 +6,8 @@ import type { RangeConfig } from '../types';
export function getRangeStrategy(config: RangeConfig): RangeStrategy { export function getRangeStrategy(config: RangeConfig): RangeStrategy {
const { depType, depName, packageJsonType, currentValue, rangeStrategy } = const { depType, depName, packageJsonType, currentValue, rangeStrategy } =
config; 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) { if (rangeStrategy === 'bump' && isComplexRange) {
logger.debug( logger.debug(
{ currentValue }, { currentValue },

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -39,35 +39,6 @@
"lib/config/validation-helpers/managers.ts", "lib/config/validation-helpers/managers.ts",
"lib/config/validation.ts", "lib/config/validation.ts",
"lib/modules/datasource/github-releases/test/index.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/api.ts",
"lib/modules/platform/azure/azure-got-wrapper.ts", "lib/modules/platform/azure/azure-got-wrapper.ts",
"lib/modules/platform/azure/azure-helper.ts", "lib/modules/platform/azure/azure-helper.ts",