From b9b08379481ae7f524ef88a65d3cab7574dac43f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 26 Apr 2023 23:21:47 +0000 Subject: [PATCH 01/12] chore(deps): update github/codeql-action action to v2.3.1 (#21829) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 3130613022887a..ee8f722215838f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -30,7 +30,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@b2c19fb9a2a485599ccf4ed5d65527d94bc57226 # v2.3.0 + uses: github/codeql-action/init@8662eabe0e9f338a07350b7fd050732745f93848 # v2.3.1 with: languages: javascript @@ -40,7 +40,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@b2c19fb9a2a485599ccf4ed5d65527d94bc57226 # v2.3.0 + uses: github/codeql-action/autobuild@8662eabe0e9f338a07350b7fd050732745f93848 # v2.3.1 # ℹī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -54,4 +54,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@b2c19fb9a2a485599ccf4ed5d65527d94bc57226 # v2.3.0 + uses: github/codeql-action/analyze@8662eabe0e9f338a07350b7fd050732745f93848 # v2.3.1 From 2202641e7fc3148477a32299484f10f944a7eb3a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 27 Apr 2023 00:17:58 +0000 Subject: [PATCH 02/12] chore(deps): update dependency @types/node to v18.15.12 (#21831) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 1a42e710e59fc8..fa31287768ea0d 100644 --- a/package.json +++ b/package.json @@ -279,7 +279,7 @@ "@types/marshal": "0.5.1", "@types/moo": "0.5.5", "@types/nock": "10.0.3", - "@types/node": "18.15.11", + "@types/node": "18.15.12", "@types/parse-link-header": "2.0.1", "@types/semver": "7.3.13", "@types/semver-stable": "3.0.0", diff --git a/yarn.lock b/yarn.lock index a4245a13fbbea8..9ad5f9e2291650 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3078,10 +3078,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.0.tgz#4668bc392bb6938637b47e98b1f2ed5426f33316" integrity sha512-BsAaKhB+7X+H4GnSjGhJG9Qi8Tw+inU9nJDwmD5CgOmBLEI6ArdhikpLX7DjbjDRDTbqZzU2LSQNZg8WGPiSZQ== -"@types/node@18.15.11": - version "18.15.11" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.11.tgz#b3b790f09cb1696cffcec605de025b088fa4225f" - integrity sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q== +"@types/node@18.15.12": + version "18.15.12" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.12.tgz#833756634e78c829e1254db006468dadbb0c696b" + integrity sha512-Wha1UwsB3CYdqUm2PPzh/1gujGCNtWVUYF0mB00fJFoR4gTyWTDPjSm+zBF787Ahw8vSGgBja90MkgFwvB86Dg== "@types/node@^13.7.0": version "13.13.52" From 78aa91aca8f3702853828d691381d0effb5b69d9 Mon Sep 17 00:00:00 2001 From: RahulGautamSingh Date: Thu, 27 Apr 2023 10:36:07 +0530 Subject: [PATCH 03/12] feat(pnpm): get locked version from `pnpm-lock.yaml` (#21480) Co-authored-by: Michael Kriese --- .../lockfile-parsing/pnpm-lock.yaml | 23 +++ .../npm/extract/locked-versions.spec.ts | 76 ++++++--- .../manager/npm/extract/locked-versions.ts | 23 ++- lib/modules/manager/npm/extract/pnpm.spec.ts | 86 ++++++++++ lib/modules/manager/npm/extract/pnpm.ts | 156 +++++++++++++++++- lib/modules/manager/npm/post-update/types.ts | 3 +- 6 files changed, 336 insertions(+), 31 deletions(-) create mode 100644 lib/modules/manager/npm/__fixtures__/lockfile-parsing/pnpm-lock.yaml diff --git a/lib/modules/manager/npm/__fixtures__/lockfile-parsing/pnpm-lock.yaml b/lib/modules/manager/npm/__fixtures__/lockfile-parsing/pnpm-lock.yaml new file mode 100644 index 00000000000000..d8e3e77aaf37a5 --- /dev/null +++ b/lib/modules/manager/npm/__fixtures__/lockfile-parsing/pnpm-lock.yaml @@ -0,0 +1,23 @@ +lockfileVersion: '6.0' + +dependencies: + xmldoc: + specifier: 1.1.0 + version: 1.1.0 + +packages: + + /sax@1.2.4: + resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==} + dev: false + + /xmldoc@1.1.0: + resolution: {integrity: sha512-5CEmEtW6IeVMEHSIxchhwpwJKnpFFsCOl9J3R2trVPcMsT7loE7jwT/q1Zwzlk3MetuiyCAdpA699gq0E4fgdw==} + dependencies: + sax: 1.2.4 + dev: false + + /sux-1.2.4: #invlaid + resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==} + dev: false + diff --git a/lib/modules/manager/npm/extract/locked-versions.spec.ts b/lib/modules/manager/npm/extract/locked-versions.spec.ts index 8a9ff6b1f6863e..6f411397e710f0 100644 --- a/lib/modules/manager/npm/extract/locked-versions.spec.ts +++ b/lib/modules/manager/npm/extract/locked-versions.spec.ts @@ -2,13 +2,13 @@ import type { PackageFile } from '../../types'; import type { NpmManagerData } from '../types'; import { getLockedVersions } from './locked-versions'; -/** @type any */ const npm = require('./npm'); -/** @type any */ +const pnpm = require('./pnpm'); const yarn = require('./yarn'); jest.mock('./npm'); jest.mock('./yarn'); +jest.mock('./pnpm'); describe('modules/manager/npm/extract/locked-versions', () => { describe('.getLockedVersions()', () => { @@ -17,7 +17,10 @@ describe('modules/manager/npm/extract/locked-versions', () => { ): PackageFile[] { return [ { - managerData: { npmLock: 'package-lock.json', yarnLock: 'yarn.lock' }, + managerData: { + npmLock: 'package-lock.json', + yarnLock: 'yarn.lock', + }, extractedConstraints: {}, deps: [ { depName: 'a', currentValue: '1.0.0' }, @@ -485,32 +488,51 @@ describe('modules/manager/npm/extract/locked-versions', () => { }, ]); }); + }); - it('ignores pnpm', async () => { - const packageFiles = [ - { - managerData: { - pnpmShrinkwrap: 'pnpm-lock.yaml', - }, - deps: [ - { depName: 'a', currentValue: '1.0.0' }, - { depName: 'b', currentValue: '2.0.0' }, - ], - packageFile: 'some-file', + it('uses pnpm-lock', async () => { + pnpm.getPnpmLock.mockReturnValue({ + lockedVersions: { + a: '1.0.0', + b: '2.0.0', + c: '3.0.0', + }, + lockfileVersion: 6.0, + }); + const packageFiles = [ + { + managerData: { + pnpmShrinkwrap: 'pnpm-lock.yaml', }, - ]; - await getLockedVersions(packageFiles); - expect(packageFiles).toEqual([ - { - deps: [ - { currentValue: '1.0.0', depName: 'a' }, - { currentValue: '2.0.0', depName: 'b' }, - ], - lockFiles: ['pnpm-lock.yaml'], - managerData: { pnpmShrinkwrap: 'pnpm-lock.yaml' }, - packageFile: 'some-file', + extractedConstraints: { + pnpm: '>=6.0.0', }, - ]); - }); + deps: [ + { + depName: 'a', + currentValue: '1.0.0', + }, + { + depName: 'b', + currentValue: '2.0.0', + }, + ], + packageFile: 'some-file', + }, + ]; + pnpm.getConstraints.mockReturnValue('>=6.0.0 >=8'); + await getLockedVersions(packageFiles); + expect(packageFiles).toEqual([ + { + extractedConstraints: { pnpm: '>=6.0.0 >=8' }, + deps: [ + { currentValue: '1.0.0', depName: 'a', lockedVersion: '1.0.0' }, + { currentValue: '2.0.0', depName: 'b', lockedVersion: '2.0.0' }, + ], + lockFiles: ['pnpm-lock.yaml'], + managerData: { pnpmShrinkwrap: 'pnpm-lock.yaml' }, + packageFile: 'some-file', + }, + ]); }); }); diff --git a/lib/modules/manager/npm/extract/locked-versions.ts b/lib/modules/manager/npm/extract/locked-versions.ts index 6c3fa3e7a40183..dde7884b90a31f 100644 --- a/lib/modules/manager/npm/extract/locked-versions.ts +++ b/lib/modules/manager/npm/extract/locked-versions.ts @@ -3,6 +3,7 @@ import { logger } from '../../../../logger'; import type { PackageFile } from '../../types'; import type { NpmManagerData } from '../types'; import { getNpmLock } from './npm'; +import { getConstraints, getPnpmLock } from './pnpm'; import type { LockFile } from './types'; import { getYarnLock } from './yarn'; @@ -19,7 +20,7 @@ export async function getLockedVersions( logger.trace('Found yarnLock'); lockFiles.push(yarnLock); if (!lockFileCache[yarnLock]) { - logger.trace('Retrieving/parsing ' + yarnLock); + logger.trace(`Retrieving/parsing ${yarnLock}`); lockFileCache[yarnLock] = await getYarnLock(yarnLock); } const { lockfileVersion, isYarn1 } = lockFileCache[yarnLock]; @@ -87,8 +88,26 @@ export async function getLockedVersions( )!; } } else if (pnpmShrinkwrap) { - logger.debug('TODO: implement pnpm-lock.yaml parsing of lockVersion'); + logger.debug('Found pnpm lock-file'); lockFiles.push(pnpmShrinkwrap); + if (!lockFileCache[pnpmShrinkwrap]) { + logger.trace(`Retrieving/parsing ${pnpmShrinkwrap}`); + lockFileCache[pnpmShrinkwrap] = await getPnpmLock(pnpmShrinkwrap); + } + const { lockfileVersion } = lockFileCache[pnpmShrinkwrap]; + if (lockfileVersion) { + packageFile.extractedConstraints!.pnpm = getConstraints( + lockfileVersion, + packageFile.extractedConstraints!.pnpm + ); + } + + for (const dep of packageFile.deps) { + // TODO: types (#7154) + dep.lockedVersion = semver.valid( + lockFileCache[pnpmShrinkwrap].lockedVersions[dep.depName!] + )!; + } } if (lockFiles.length) { packageFile.lockFiles = lockFiles; diff --git a/lib/modules/manager/npm/extract/pnpm.spec.ts b/lib/modules/manager/npm/extract/pnpm.spec.ts index af0a9fef89a0e5..6174595759eb51 100644 --- a/lib/modules/manager/npm/extract/pnpm.spec.ts +++ b/lib/modules/manager/npm/extract/pnpm.spec.ts @@ -1,4 +1,5 @@ import yaml from 'js-yaml'; +import { Fixtures } from '../../../../../test/fixtures'; import { getFixturePath, logger } from '../../../../../test/util'; import { GlobalConfig } from '../../../../config/global'; import * as fs from '../../../../util/fs'; @@ -6,6 +7,8 @@ import { detectPnpmWorkspaces, extractPnpmFilters, findPnpmWorkspace, + getConstraints, + getPnpmLock, } from './pnpm'; describe('modules/manager/npm/extract/pnpm', () => { @@ -227,4 +230,87 @@ describe('modules/manager/npm/extract/pnpm', () => { ).toBeUndefined(); }); }); + + describe('getConstraints()', () => { + // no constraints + it.each([ + [6.0, undefined, '>=8'], + [5.4, undefined, '>=7 <8'], + [5.3, undefined, '>=6 <7'], + [5.2, undefined, '>=5.10.0 <6'], + [5.1, undefined, '>=3.5.0 <5.9.3'], + [5.0, undefined, '>=3 <3.5.0'], + ])('adds constraints for %f', (lockfileVersion, constraints, expected) => { + expect(getConstraints(lockfileVersion, constraints)).toBe(expected); + }); + + // constraints present + it.each([ + [6.0, '>=8.2.0', '>=8.2.0'], + [6.0, '>=7', '>=7 >=8'], + + [5.4, '^7.2.0', '^7.2.0'], + [5.4, '<7.2.0', '<7.2.0 >=7'], + [5.4, '>7.2.0', '>7.2.0 <8'], + [5.4, '>=6', '>=6 >=7 <8'], + + [5.3, '^6.0.0', '^6.0.0'], + [5.3, '<6.2.0', '<6.2.0 >=6'], + [5.3, '>6.2.0', '>6.2.0 <7'], + [5.3, '>=5', '>=5 >=6 <7'], + + [5.2, '5.10.0', '5.10.0'], + [5.2, '>5.0.0 <5.18.0', '>5.0.0 <5.18.0 >=5.10.0'], + [5.2, '>5.10.0', '>5.10.0 <6'], + [5.2, '>=5', '>=5 >=5.10.0 <6'], + + [5.1, '^4.0.0', '^4.0.0'], + [5.1, '<4', '<4 >=3.5.0'], + [5.1, '>=4', '>=4 <5.9.3'], + [5.1, '>=3', '>=3 >=3.5.0 <5.9.3'], + + [5.0, '3.1.0', '3.1.0'], + [5.0, '^3.0.0', '^3.0.0 <3.5.0'], + [5.0, '>=3', '>=3 <3.5.0'], + [5.0, '>=2', '>=2 >=3 <3.5.0'], + ])('adds constraints for %f', (lockfileVersion, constraints, expected) => { + expect(getConstraints(lockfileVersion, constraints)).toBe(expected); + }); + }); + + describe('.getPnpmLock()', () => { + const readLocalFile = jest.spyOn(fs, 'readLocalFile'); + + it('returns empty if failed to parse', async () => { + readLocalFile.mockResolvedValueOnce(undefined as never); + const res = await getPnpmLock('package.json'); + expect(Object.keys(res.lockedVersions)).toHaveLength(0); + }); + + it('extracts', async () => { + const plocktest1Lock = Fixtures.get('pnpm-monorepo/pnpm-lock.yaml', '..'); + readLocalFile.mockResolvedValueOnce(plocktest1Lock); + const res = await getPnpmLock('package.json'); + expect(Object.keys(res.lockedVersions)).toHaveLength(8); + }); + + it('logs when packagePath is invalid', async () => { + const plocktest1Lock = Fixtures.get( + 'lockfile-parsing/pnpm-lock.yaml', + '..' + ); + readLocalFile.mockResolvedValueOnce(plocktest1Lock); + const res = await getPnpmLock('package.json'); + expect(Object.keys(res.lockedVersions)).toHaveLength(2); + expect(logger.logger.trace).toHaveBeenLastCalledWith( + 'Invalid package path /sux-1.2.4' + ); + }); + + it('returns empty if no deps', async () => { + readLocalFile.mockResolvedValueOnce('{}'); + const res = await getPnpmLock('package.json'); + expect(Object.keys(res.lockedVersions)).toHaveLength(0); + }); + }); }); diff --git a/lib/modules/manager/npm/extract/pnpm.ts b/lib/modules/manager/npm/extract/pnpm.ts index 1f566d1e8bb99f..18e48b8185e160 100644 --- a/lib/modules/manager/npm/extract/pnpm.ts +++ b/lib/modules/manager/npm/extract/pnpm.ts @@ -1,6 +1,7 @@ import is from '@sindresorhus/is'; import { findPackages } from 'find-packages'; import { load } from 'js-yaml'; +import semver from 'semver'; import upath from 'upath'; import { GlobalConfig } from '../../../../config/global'; import { logger } from '../../../../logger'; @@ -10,9 +11,15 @@ import { localPathExists, readLocalFile, } from '../../../../util/fs'; +import { regEx } from '../../../../util/regex'; import type { PackageFile } from '../../types'; +import type { PnpmLockFile } from '../post-update/types'; import type { NpmManagerData } from '../types'; -import type { PnpmWorkspaceFile } from './types'; +import type { LockFile, PnpmWorkspaceFile } from './types'; + +function isPnpmLockfile(obj: any): obj is PnpmLockFile { + return is.plainObject(obj) && 'lockfileVersion' in obj; +} export async function extractPnpmFilters( fileName: string @@ -135,3 +142,150 @@ export async function detectPnpmWorkspaces( } } } + +export async function getPnpmLock(filePath: string): Promise { + try { + const pnpmLockRaw = await readLocalFile(filePath, 'utf8'); + if (!pnpmLockRaw) { + throw new Error('Unable to read pnpm-lock.yaml'); + } + + const lockParsed = load(pnpmLockRaw); + if (!isPnpmLockfile(lockParsed)) { + throw new Error('Invalid or empty lockfile'); + } + logger.trace({ lockParsed }, 'pnpm lockfile parsed'); + + // field lockfileVersion is type string in lockfileVersion = 6 and type number in < 6 + const lockfileVersion: number = is.number(lockParsed.lockfileVersion) + ? lockParsed.lockfileVersion + : parseFloat(lockParsed.lockfileVersion); + + const lockedVersions: Record = {}; + const packagePathRegex = regEx( + /^\/(?.+)(?:@|\/)(?[^/@]+)$/ + ); // eg. "/(@|/)" + + for (const packagePath of Object.keys(lockParsed.packages ?? {})) { + const result = packagePath.match(packagePathRegex); + if (!result?.groups) { + logger.trace(`Invalid package path ${packagePath}`); + continue; + } + + const packageName = result.groups.packageName; + const version = result.groups.version; + logger.trace({ + packagePath, + packageName, + version, + }); + lockedVersions[packageName] = version; + } + return { + lockedVersions, + lockfileVersion, + }; + } catch (err) { + logger.debug({ filePath, err }, 'Warning: Exception parsing pnpm lockfile'); + return { lockedVersions: {} }; + } +} + +export function getConstraints( + lockfileVersion: number, + constraints?: string +): string { + let newConstraints = constraints; + + // find matching lockfileVersion and use its constraints + // if no match found use lockfileVersion 5 + // lockfileVersion 5 is the minimum version required to generate the pnpm-lock.yaml file + const { lowerBound, upperBound, lowerConstraint, upperConstraint } = + lockToPnpmVersionMapping.find( + (m) => m.lockfileVersion === lockfileVersion + ) ?? { + lockfileVersion: 5.0, + lowerBound: '2.24.0', + upperBound: '3.5.0', + lowerConstraint: '>=3', + upperConstraint: '<3.5.0', + }; + + // inorder to ensure that the constraint doesn't allow any pnpm versions that can't generate the extracted lockfileVersion + // compare the current constraint to the lowerBound and upperBound of the lockfileVersion + // if the current constraint is not comaptible, add the lowerConstraint and upperConstraint, whichever is needed + if (newConstraints) { + // if constraint satisfies versions lower than lowerBound add the lowerConstraint to narrow the range + if (semver.satisfies(lowerBound, newConstraints)) { + newConstraints += ` ${lowerConstraint}`; + } + + // if constraint satisfies versions higher than upperBound add the upperConstraint to narrow the range + if ( + upperBound && + upperConstraint && + semver.satisfies(upperBound, newConstraints) + ) { + newConstraints += ` ${upperConstraint}`; + } + } + // if no constraint is present, add the lowerConstraint and upperConstraint corresponding to the lockfileVersion + else { + newConstraints = `${lowerConstraint}${ + upperConstraint ? ` ${upperConstraint}` : '' + }`; + } + + return newConstraints; +} + +/** + pnpm lockfiles have corresponding version numbers called "lockfileVersion" + each lockfileVersion can only be generated by a certain pnpm version ranges + eg. lockfileVersion: 5.4 can only be generated by pnpm version >=7 && <8 + official list can be found here : https:github.com/pnpm/spec/tree/master/lockfile + we use the mapping present below to find the compatible pnpm version range for a given lockfileVersion + + the various terms used in the mapping are explained below: + lowerConstriant : lowest pnpm version that can generate the lockfileVersion + upperConstraint : highest pnpm version that can generate the lockfileVersion + lowerBound : highest pnpm version that is less than the lowerConstraint + upperBound : lowest pnpm version that is greater than upperConstraint + + For handling future lockfileVersions, we need to: + 1. add a upperBound and upperConstraint to the current lastest lockfileVersion + 2. add an object for the new lockfileVersion with lowerBound and lowerConstraint + */ + +const lockToPnpmVersionMapping = [ + { lockfileVersion: 6.0, lowerBound: '7.32.0', lowerConstraint: '>=8' }, + { + lockfileVersion: 5.4, + lowerBound: '6.35.1', + upperBound: '8.0.0', + lowerConstraint: '>=7', + upperConstraint: '<8', + }, + { + lockfileVersion: 5.3, + lowerBound: '5.18.10', + upperBound: '7.0.0', + lowerConstraint: '>=6', + upperConstraint: '<7', + }, + { + lockfileVersion: 5.2, + lowerBound: '5.9.3', + upperBound: '5.18.10', + lowerConstraint: '>=5.10.0', + upperConstraint: '<6', + }, + { + lockfileVersion: 5.1, + lowerBound: '3.4.1', + upperBound: '5.9.3', + lowerConstraint: '>=3.5.0', + upperConstraint: '<5.9.3', + }, +]; diff --git a/lib/modules/manager/npm/post-update/types.ts b/lib/modules/manager/npm/post-update/types.ts index 56b50b78cc9ae7..a134a951e79134 100644 --- a/lib/modules/manager/npm/post-update/types.ts +++ b/lib/modules/manager/npm/post-update/types.ts @@ -31,7 +31,8 @@ export interface GenerateLockFileResult { } export interface PnpmLockFile { - lockfileVersion?: number; + lockfileVersion: number | string; + packages?: Record; } export interface YarnRcYmlFile { From a6dfa70c1d46e16645658189534b9a8d81b5ace4 Mon Sep 17 00:00:00 2001 From: Jamie Magee Date: Wed, 26 Apr 2023 22:06:22 -0700 Subject: [PATCH 04/12] chore: migrate to `structuredClone` (#20885) Co-authored-by: Rhys Arkins --- lib/config/massage.ts | 5 ++--- lib/config/presets/index.ts | 3 +-- lib/config/utils.ts | 6 ++--- lib/logger/utils.ts | 10 +++++---- .../aws-machine-image/index.spec.ts | 6 ++--- lib/modules/datasource/aws-rds/index.spec.ts | 4 ++-- lib/modules/datasource/docker/index.spec.ts | 22 +++++++++---------- .../datasource/jenkins-plugins/index.ts | 2 +- .../datasource/kubernetes-api/index.spec.ts | 2 +- .../manager/gradle/parser/common.spec.ts | 2 +- lib/modules/manager/gradle/parser/common.ts | 3 +-- .../update/locked-dependency/index.spec.ts | 3 +-- lib/modules/platform/github/index.spec.ts | 4 ++-- lib/util/clone.ts | 5 +++++ .../memory-cache-strategy.spec.ts | 11 +++++----- .../package-cache-strategy.spec.ts | 3 +-- lib/util/host-rules.ts | 5 ++--- lib/util/http/github.spec.ts | 2 +- lib/workers/repository/init/index.ts | 8 +++++-- .../repository/onboarding/branch/config.ts | 5 ++--- lib/workers/repository/package-files.ts | 3 +-- lib/workers/repository/process/fetch.ts | 3 +-- lib/workers/repository/process/index.ts | 3 +-- .../process/lookup/filter-checks.spec.ts | 3 +-- .../repository/process/lookup/index.ts | 3 +-- .../update/pr/changelog/release-notes.spec.ts | 5 ++--- 26 files changed, 63 insertions(+), 68 deletions(-) diff --git a/lib/config/massage.ts b/lib/config/massage.ts index 094237c11bfdb2..5130edd2bd5c34 100644 --- a/lib/config/massage.ts +++ b/lib/config/massage.ts @@ -1,5 +1,4 @@ import is from '@sindresorhus/is'; -import { clone } from '../util/clone'; import { getOptions } from './options'; import type { PackageRule, RenovateConfig, UpdateType } from './types'; @@ -17,7 +16,7 @@ export function massageConfig(config: RenovateConfig): RenovateConfig { } }); } - const massagedConfig = clone(config); + const massagedConfig = structuredClone(config); for (const [key, val] of Object.entries(config)) { if (allowedStrings.includes(key) && is.string(val)) { massagedConfig[key] = [val]; @@ -56,7 +55,7 @@ export function massageConfig(config: RenovateConfig): RenovateConfig { PackageRule ][]) { if (updateTypes.includes(key)) { - let newRule = clone(rule); + let newRule = structuredClone(rule); Object.keys(newRule).forEach((newKey) => { if (!(newKey.startsWith(`match`) || newKey.startsWith('exclude'))) { delete newRule[newKey]; diff --git a/lib/config/presets/index.ts b/lib/config/presets/index.ts index 24459e102cb960..32d17ea8212751 100644 --- a/lib/config/presets/index.ts +++ b/lib/config/presets/index.ts @@ -6,7 +6,6 @@ import { import { logger } from '../../logger'; import { ExternalHostError } from '../../types/errors/external-host-error'; import * as memCache from '../../util/cache/memory'; -import { clone } from '../../util/clone'; import { regEx } from '../../util/regex'; import * as massage from '../massage'; import * as migration from '../migration'; @@ -274,7 +273,7 @@ export async function resolveConfigPresets( _ignorePresets?: string[], existingPresets: string[] = [] ): Promise { - let ignorePresets = clone(_ignorePresets); + let ignorePresets = structuredClone(_ignorePresets); if (!ignorePresets || ignorePresets.length === 0) { ignorePresets = inputConfig.ignorePresets ?? []; } diff --git a/lib/config/utils.ts b/lib/config/utils.ts index 4c891e21c14bd7..106d9e168f1453 100644 --- a/lib/config/utils.ts +++ b/lib/config/utils.ts @@ -1,5 +1,4 @@ import { logger } from '../logger'; -import { clone } from '../util/clone'; import * as options from './options'; import type { RenovateConfig } from './types'; @@ -11,9 +10,8 @@ export function mergeChildConfig< if (!child) { return parent as never; } - const parentConfig = clone(parent); - // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion - const childConfig = clone(child!); + const parentConfig = structuredClone(parent); + const childConfig = structuredClone(child); const config: Record = { ...parentConfig, ...childConfig }; for (const option of options.getOptions()) { if ( diff --git a/lib/logger/utils.ts b/lib/logger/utils.ts index d1697686784080..4d0297cedb823b 100644 --- a/lib/logger/utils.ts +++ b/lib/logger/utils.ts @@ -3,7 +3,6 @@ import is from '@sindresorhus/is'; import bunyan from 'bunyan'; import fs from 'fs-extra'; import { RequestError as HttpError } from 'got'; -import { clone } from '../util/clone'; import { redactedFields, sanitize } from '../util/sanitize'; import type { BunyanRecord, BunyanStream } from './types'; @@ -65,7 +64,7 @@ export default function prepareError(err: Error): Record { // handle got error if (err instanceof HttpError) { const options: Record = { - headers: clone(err.options.headers), + headers: structuredClone(err.options.headers), url: err.options.url?.toString(), hostType: err.options.context.hostType, }; @@ -82,8 +81,11 @@ export default function prepareError(err: Error): Record { statusCode: err.response?.statusCode, statusMessage: err.response?.statusMessage, body: - err.name === 'TimeoutError' ? undefined : clone(err.response.body), - headers: clone(err.response.headers), + // istanbul ignore if: not easily testable + err.name === 'TimeoutError' + ? undefined + : structuredClone(err.response.body), + headers: structuredClone(err.response.headers), httpVersion: err.response.httpVersion, retryCount: err.response.retryCount, }; diff --git a/lib/modules/datasource/aws-machine-image/index.spec.ts b/lib/modules/datasource/aws-machine-image/index.spec.ts index ff8f7aef304409..6349da52d92953 100644 --- a/lib/modules/datasource/aws-machine-image/index.spec.ts +++ b/lib/modules/datasource/aws-machine-image/index.spec.ts @@ -347,7 +347,7 @@ describe('modules/datasource/aws-machine-image/index', () => { packageName: '[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["with one matching image to return that image"]}]', }); - expect(res).toStrictEqual({ + expect(res).toEqual({ releases: [ { isDeprecated: false, @@ -366,7 +366,7 @@ describe('modules/datasource/aws-machine-image/index', () => { packageName: '[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["with one deprecated matching image to return that image"]}]', }); - expect(res).toStrictEqual({ + expect(res).toEqual({ releases: [ { isDeprecated: true, @@ -385,7 +385,7 @@ describe('modules/datasource/aws-machine-image/index', () => { packageName: '[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["with 3 matching image to return the newest image"]}]', }); - expect(res).toStrictEqual({ + expect(res).toEqual({ releases: [ { isDeprecated: false, diff --git a/lib/modules/datasource/aws-rds/index.spec.ts b/lib/modules/datasource/aws-rds/index.spec.ts index d0eda95cfac8ff..7d46e5493c6eb4 100644 --- a/lib/modules/datasource/aws-rds/index.spec.ts +++ b/lib/modules/datasource/aws-rds/index.spec.ts @@ -119,7 +119,7 @@ describe('modules/datasource/aws-rds/index', () => { datasource: AwsRdsDataSource.id, packageName: '[{"Name":"engine","Values":["mysql"]}]', }); - expect(res).toStrictEqual({ + expect(res).toEqual({ releases: [ { isDeprecated: true, @@ -138,7 +138,7 @@ describe('modules/datasource/aws-rds/index', () => { datasource: AwsRdsDataSource.id, packageName: '[{"Name":"engine","Values":["mysql"]}]', }); - expect(res).toStrictEqual({ + expect(res).toEqual({ releases: [ { isDeprecated: false, diff --git a/lib/modules/datasource/docker/index.spec.ts b/lib/modules/datasource/docker/index.spec.ts index 17ce2dee16a6c4..fe3979b58e57cc 100644 --- a/lib/modules/datasource/docker/index.spec.ts +++ b/lib/modules/datasource/docker/index.spec.ts @@ -1830,7 +1830,7 @@ describe('modules/datasource/docker/index', () => { datasource: DockerDatasource.id, packageName: 'registry.company.com/node', }); - expect(res).toStrictEqual({ + expect(res).toEqual({ registryUrl: 'https://registry.company.com', releases: [ { @@ -1881,7 +1881,7 @@ describe('modules/datasource/docker/index', () => { datasource: DockerDatasource.id, packageName: 'registry.company.com/node', }); - expect(res).toStrictEqual({ + expect(res).toEqual({ registryUrl: 'https://registry.company.com', releases: [ { @@ -1935,7 +1935,7 @@ describe('modules/datasource/docker/index', () => { datasource: DockerDatasource.id, packageName: 'registry.company.com/node', }); - expect(res).toStrictEqual({ + expect(res).toEqual({ registryUrl: 'https://registry.company.com', releases: [], sourceUrl: 'https://github.com/renovatebot/renovate', @@ -1962,7 +1962,7 @@ describe('modules/datasource/docker/index', () => { datasource: DockerDatasource.id, packageName: 'registry.company.com/node', }); - expect(res).toStrictEqual({ + expect(res).toEqual({ registryUrl: 'https://registry.company.com', releases: [], }); @@ -1986,7 +1986,7 @@ describe('modules/datasource/docker/index', () => { datasource: DockerDatasource.id, packageName: 'registry.company.com/node', }); - expect(res).toStrictEqual({ + expect(res).toEqual({ registryUrl: 'https://registry.company.com', releases: [], }); @@ -2007,7 +2007,7 @@ describe('modules/datasource/docker/index', () => { datasource: DockerDatasource.id, packageName: 'registry.company.com/node', }); - expect(res).toStrictEqual({ + expect(res).toEqual({ registryUrl: 'https://registry.company.com', releases: [], }); @@ -2048,7 +2048,7 @@ describe('modules/datasource/docker/index', () => { datasource: DockerDatasource.id, packageName: 'registry.company.com/node', }); - expect(res).toStrictEqual({ + expect(res).toEqual({ registryUrl: 'https://registry.company.com', releases: [ { @@ -2093,7 +2093,7 @@ describe('modules/datasource/docker/index', () => { datasource: DockerDatasource.id, packageName: 'registry.company.com/node', }); - expect(res).toStrictEqual({ + expect(res).toEqual({ registryUrl: 'https://registry.company.com', releases: [ { @@ -2123,7 +2123,7 @@ describe('modules/datasource/docker/index', () => { datasource: DockerDatasource.id, packageName: 'registry.company.com/node', }); - expect(res).toStrictEqual({ + expect(res).toEqual({ registryUrl: 'https://registry.company.com', releases: [], }); @@ -2173,7 +2173,7 @@ describe('modules/datasource/docker/index', () => { datasource: DockerDatasource.id, packageName: 'registry.company.com/node', }); - expect(res).toStrictEqual({ + expect(res).toEqual({ registryUrl: 'https://registry.company.com', releases: [], }); @@ -2229,7 +2229,7 @@ describe('modules/datasource/docker/index', () => { datasource: DockerDatasource.id, packageName: 'ghcr.io/visualon/drone-git', }); - expect(res).toStrictEqual({ + expect(res).toEqual({ registryUrl: 'https://ghcr.io', sourceUrl: 'https://github.com/visualon/drone-git', releases: [{ version: '1.0.0' }], diff --git a/lib/modules/datasource/jenkins-plugins/index.ts b/lib/modules/datasource/jenkins-plugins/index.ts index aef686da9b17ae..d8f717c28dfcd7 100644 --- a/lib/modules/datasource/jenkins-plugins/index.ts +++ b/lib/modules/datasource/jenkins-plugins/index.ts @@ -33,7 +33,7 @@ export class JenkinsPluginsDatasource extends Datasource { return null; } - const result = clone(plugin); + const result = structuredClone(plugin); const versions = await this.getJenkinsPluginVersions(); const releases = versions[packageName]; result.releases = releases ? clone(releases) : []; diff --git a/lib/modules/datasource/kubernetes-api/index.spec.ts b/lib/modules/datasource/kubernetes-api/index.spec.ts index d4b2a954e4ca93..7b453fc2d8415c 100644 --- a/lib/modules/datasource/kubernetes-api/index.spec.ts +++ b/lib/modules/datasource/kubernetes-api/index.spec.ts @@ -16,7 +16,7 @@ describe('modules/datasource/kubernetes-api/index', () => { packageName: 'CSIStorageCapacity', }); expect(res).not.toBeNull(); - expect(res).toStrictEqual({ + expect(res).toEqual({ releases: [ { version: 'storage.k8s.io/v1beta1' }, { version: 'storage.k8s.io/v1' }, diff --git a/lib/modules/manager/gradle/parser/common.spec.ts b/lib/modules/manager/gradle/parser/common.spec.ts index 9b2c2339dead2f..8c377a1345911c 100644 --- a/lib/modules/manager/gradle/parser/common.spec.ts +++ b/lib/modules/manager/gradle/parser/common.spec.ts @@ -59,7 +59,7 @@ describe('modules/manager/gradle/parser/common', () => { it('prependNestingDepth', () => { ctx.tmpNestingDepth = ctx.varTokens = [token]; prependNestingDepth(ctx); - expect(ctx.varTokens).toStrictEqual([token, token]); + expect(ctx.varTokens).toEqual([token, token]); coalesceVariable(ctx); expect(ctx).toMatchObject({ diff --git a/lib/modules/manager/gradle/parser/common.ts b/lib/modules/manager/gradle/parser/common.ts index 6f5c17790d20c0..e72d582ea0ac8f 100644 --- a/lib/modules/manager/gradle/parser/common.ts +++ b/lib/modules/manager/gradle/parser/common.ts @@ -1,5 +1,4 @@ import { lexer, parser, query as q } from 'good-enough-parser'; -import { clone } from '../../../../util/clone'; import { regEx } from '../../../../util/regex'; import type { Ctx, @@ -52,7 +51,7 @@ export function reduceNestingDepth(ctx: Ctx): Ctx { } export function prependNestingDepth(ctx: Ctx): Ctx { - ctx.varTokens = [...clone(ctx.tmpNestingDepth), ...ctx.varTokens]; + ctx.varTokens = [...structuredClone(ctx.tmpNestingDepth), ...ctx.varTokens]; return ctx; } diff --git a/lib/modules/manager/npm/update/locked-dependency/index.spec.ts b/lib/modules/manager/npm/update/locked-dependency/index.spec.ts index 857621abaaf9a0..930836f4ff0de9 100644 --- a/lib/modules/manager/npm/update/locked-dependency/index.spec.ts +++ b/lib/modules/manager/npm/update/locked-dependency/index.spec.ts @@ -1,7 +1,6 @@ import { updateLockedDependency } from '../..'; import { Fixtures } from '../../../../../../test/fixtures'; import * as httpMock from '../../../../../../test/http-mock'; -import { clone } from '../../../../../util/clone'; import type { UpdateLockedConfig } from '../../../types'; const packageFileContent = Fixtures.get('package.json', './package-lock'); @@ -114,7 +113,7 @@ describe('modules/manager/npm/update/locked-dependency/index', () => { }); it('fails to remediate if parent dep cannot support', async () => { - const acceptsModified = clone(acceptsJson); + const acceptsModified = structuredClone(acceptsJson); acceptsModified.versions['2.0.0'] = {}; httpMock .scope('https://registry.npmjs.org') diff --git a/lib/modules/platform/github/index.spec.ts b/lib/modules/platform/github/index.spec.ts index 91d35bc7177cf7..b787c71f2ed7ca 100644 --- a/lib/modules/platform/github/index.spec.ts +++ b/lib/modules/platform/github/index.spec.ts @@ -208,7 +208,7 @@ describe('modules/platform/github/index', () => { }); const repos = await github.getRepos(); - expect(repos).toStrictEqual(['a/b', 'c/d']); + expect(repos).toEqual(['a/b', 'c/d']); }); it('should return an array of repos when using GitHub App Installation Token', async () => { @@ -236,7 +236,7 @@ describe('modules/platform/github/index', () => { }); const repos = await github.getRepos(); - expect(repos).toStrictEqual(['a/b', 'c/d']); + expect(repos).toEqual(['a/b', 'c/d']); }); }); diff --git a/lib/util/clone.ts b/lib/util/clone.ts index 67813c024241fd..422add0b37d904 100644 --- a/lib/util/clone.ts +++ b/lib/util/clone.ts @@ -1,5 +1,10 @@ import { quickStringify } from './stringify'; +/** + * Creates a deep clone of an object. + * @deprecated Use {@link structuredClone} instead. + * @param input The object to clone. + */ export function clone(input: T | null = null): T { const stringifiedInput = quickStringify(input); return stringifiedInput ? JSON.parse(stringifiedInput) : null; diff --git a/lib/util/github/graphql/cache-strategies/memory-cache-strategy.spec.ts b/lib/util/github/graphql/cache-strategies/memory-cache-strategy.spec.ts index 88849193a12b66..71ab7203107807 100644 --- a/lib/util/github/graphql/cache-strategies/memory-cache-strategy.spec.ts +++ b/lib/util/github/graphql/cache-strategies/memory-cache-strategy.spec.ts @@ -1,6 +1,5 @@ import { DateTime, Settings } from 'luxon'; import * as memCache from '../../../cache/memory'; -import { clone } from '../../../clone'; import type { GithubDatasourceItem, GithubGraphqlCacheRecord } from '../types'; import { GithubGraphqlMemoryCacheStrategy } from './memory-cache-strategy'; @@ -45,7 +44,7 @@ describe('util/github/graphql/cache-strategies/memory-cache-strategy', () => { items, createdAt: isoTs('2022-10-01 15:30'), }; - memCache.set('github-graphql-cache:foo:bar', clone(cacheRecord)); + memCache.set('github-graphql-cache:foo:bar', structuredClone(cacheRecord)); // At this moment, cache is valid let now = '2022-10-31 15:29:59'; @@ -85,7 +84,7 @@ describe('util/github/graphql/cache-strategies/memory-cache-strategy', () => { items: oldItems, createdAt: isoTs('2022-10-30 12:00'), }; - memCache.set('github-graphql-cache:foo:bar', clone(cacheRecord)); + memCache.set('github-graphql-cache:foo:bar', structuredClone(cacheRecord)); const now = '2022-10-31 15:30'; mockTime(now); @@ -121,7 +120,7 @@ describe('util/github/graphql/cache-strategies/memory-cache-strategy', () => { items: oldItems, createdAt: isoTs('2022-10-30 12:00'), }; - memCache.set('github-graphql-cache:foo:bar', clone(cacheRecord)); + memCache.set('github-graphql-cache:foo:bar', structuredClone(cacheRecord)); const now = '2022-10-31 15:30'; mockTime(now); @@ -147,7 +146,7 @@ describe('util/github/graphql/cache-strategies/memory-cache-strategy', () => { items: oldItems, createdAt: isoTs('2022-12-31 12:00'), }; - memCache.set('github-graphql-cache:foo:bar', clone(cacheRecord)); + memCache.set('github-graphql-cache:foo:bar', structuredClone(cacheRecord)); const now = '2022-12-31 23:59'; mockTime(now); @@ -191,7 +190,7 @@ describe('util/github/graphql/cache-strategies/memory-cache-strategy', () => { items, createdAt: isoTs('2022-10-30 12:00'), }; - memCache.set('github-graphql-cache:foo:bar', clone(cacheRecord)); + memCache.set('github-graphql-cache:foo:bar', structuredClone(cacheRecord)); const now = '2022-10-31 15:30'; mockTime(now); diff --git a/lib/util/github/graphql/cache-strategies/package-cache-strategy.spec.ts b/lib/util/github/graphql/cache-strategies/package-cache-strategy.spec.ts index 80c02add16a5b3..b288f2eeb6d139 100644 --- a/lib/util/github/graphql/cache-strategies/package-cache-strategy.spec.ts +++ b/lib/util/github/graphql/cache-strategies/package-cache-strategy.spec.ts @@ -1,6 +1,5 @@ import { DateTime, Settings } from 'luxon'; import * as packageCache from '../../../cache/package'; -import { clone } from '../../../clone'; import type { GithubDatasourceItem, GithubGraphqlCacheRecord } from '../types'; import { GithubGraphqlPackageCacheStrategy } from './package-cache-strategy'; @@ -31,7 +30,7 @@ describe('util/github/graphql/cache-strategies/package-cache-strategy', () => { items: oldItems, createdAt: isoTs('2022-10-15 12:00'), }; - cacheGet.mockResolvedValueOnce(clone(cacheRecord)); + cacheGet.mockResolvedValueOnce(structuredClone(cacheRecord)); const now = '2022-10-30 12:00'; mockTime(now); diff --git a/lib/util/host-rules.ts b/lib/util/host-rules.ts index 0f5e7219451779..9ee45b797cb6af 100644 --- a/lib/util/host-rules.ts +++ b/lib/util/host-rules.ts @@ -2,7 +2,6 @@ import is from '@sindresorhus/is'; import merge from 'deepmerge'; import { logger } from '../logger'; import type { HostRule, HostRuleSearchResult } from '../types'; -import { clone } from './clone'; import * as sanitize from './sanitize'; import { toBase64 } from './string'; import { parseUrl, validateUrl } from './url'; @@ -16,7 +15,7 @@ interface LegacyHostRule { } function migrateRule(rule: LegacyHostRule & HostRule): HostRule { - const cloned: LegacyHostRule & HostRule = clone(rule); + const cloned: LegacyHostRule & HostRule = structuredClone(rule); delete cloned.hostName; delete cloned.domainName; delete cloned.baseUrl; @@ -187,7 +186,7 @@ export function findAll({ hostType }: { hostType: string }): HostRule[] { * @returns a deep copy of all known host rules without any filtering */ export function getAll(): HostRule[] { - return clone(hostRules); + return structuredClone(hostRules); } export function clear(): void { diff --git a/lib/util/http/github.spec.ts b/lib/util/http/github.spec.ts index f2383c4b715d35..fe85535129ac93 100644 --- a/lib/util/http/github.spec.ts +++ b/lib/util/http/github.spec.ts @@ -621,7 +621,7 @@ describe('util/http/github', () => { .reply(200, { data: { repository } }); const res = await githubApi.requestGraphql(graphqlQuery); - expect(res?.data).toStrictEqual({ repository }); + expect(res?.data).toEqual({ repository }); }); it('queryRepoField', async () => { diff --git a/lib/workers/repository/init/index.ts b/lib/workers/repository/init/index.ts index 89296f134f1ed0..10eb930580fd6f 100644 --- a/lib/workers/repository/init/index.ts +++ b/lib/workers/repository/init/index.ts @@ -3,7 +3,6 @@ import { applySecretsToConfig } from '../../../config/secrets'; import type { RenovateConfig } from '../../../config/types'; import { logger } from '../../../logger'; import { platform } from '../../../modules/platform'; -import { clone } from '../../../util/clone'; import { cloneSubmodules, setUserRepoConfig } from '../../../util/git'; import { getAll } from '../../../util/host-rules'; import { checkIfConfigured } from '../configured'; @@ -14,7 +13,12 @@ import { getRepoConfig } from './config'; import { detectVulnerabilityAlerts } from './vulnerability'; function initializeConfig(config: RenovateConfig): RenovateConfig { - return { ...clone(config), errors: [], warnings: [], branchList: [] }; + return { + ...structuredClone(config), + errors: [], + warnings: [], + branchList: [], + }; } function warnOnUnsupportedOptions(config: RenovateConfig): void { diff --git a/lib/workers/repository/onboarding/branch/config.ts b/lib/workers/repository/onboarding/branch/config.ts index 8350dba61b087d..fdceac6ba71f82 100644 --- a/lib/workers/repository/onboarding/branch/config.ts +++ b/lib/workers/repository/onboarding/branch/config.ts @@ -6,13 +6,12 @@ import type { RenovateSharedConfig, } from '../../../../config/types'; import { logger } from '../../../../logger'; -import { clone } from '../../../../util/clone'; import { EditorConfig, JSONWriter } from '../../../../util/json-writer'; async function getOnboardingConfig( config: RenovateConfig -): Promise { - let onboardingConfig = clone(config.onboardingConfig); +): Promise { + let onboardingConfig = structuredClone(config.onboardingConfig); let orgPreset: string | undefined; diff --git a/lib/workers/repository/package-files.ts b/lib/workers/repository/package-files.ts index 02887c20c9ca2b..b33bec994c6d6f 100644 --- a/lib/workers/repository/package-files.ts +++ b/lib/workers/repository/package-files.ts @@ -1,7 +1,6 @@ import is from '@sindresorhus/is'; import { logger } from '../../logger'; import type { PackageFile } from '../../modules/manager/types'; -import { clone } from '../../util/clone'; export class PackageFiles { private static data = new Map | null>(); @@ -45,7 +44,7 @@ export class PackageFiles { let removed = false; let truncated = false; - const data = new Map(clone(Array.from(this.data))); + const data = new Map(structuredClone(Array.from(this.data))); // filter all deps with skip reason for (const managers of [...data.values()].filter(is.truthy)) { diff --git a/lib/workers/repository/process/fetch.ts b/lib/workers/repository/process/fetch.ts index 92ae3315d113fc..9f9c2ec29d1623 100644 --- a/lib/workers/repository/process/fetch.ts +++ b/lib/workers/repository/process/fetch.ts @@ -14,7 +14,6 @@ import type { import { ExternalHostError } from '../../../types/errors/external-host-error'; import * as memCache from '../../../util/cache/memory'; import type { LookupStats } from '../../../util/cache/memory/types'; -import { clone } from '../../../util/clone'; import { applyPackageRules } from '../../../util/package-rules'; import * as p from '../../../util/promises'; import { PackageFiles } from '../package-files'; @@ -38,7 +37,7 @@ async function fetchDepUpdates( packageFileConfig: RenovateConfig & PackageFile, indep: PackageDependency ): Promise { - const dep = clone(indep); + const dep = structuredClone(indep); dep.updates = []; if (is.string(dep.depName)) { dep.depName = dep.depName.trim(); diff --git a/lib/workers/repository/process/index.ts b/lib/workers/repository/process/index.ts index 62596fd29d9a4b..0593f539bc42bb 100644 --- a/lib/workers/repository/process/index.ts +++ b/lib/workers/repository/process/index.ts @@ -8,7 +8,6 @@ import type { PackageFile } from '../../../modules/manager/types'; import { platform } from '../../../modules/platform'; import { scm } from '../../../modules/platform/scm'; import { getCache } from '../../../util/cache/repository'; -import { clone } from '../../../util/clone'; import { getBranchList } from '../../../util/git'; import { configRegexPredicate } from '../../../util/regex'; import { addSplit } from '../../../util/split'; @@ -23,7 +22,7 @@ async function getBaseBranchConfig( ): Promise { logger.debug(`baseBranch: ${baseBranch}`); - let baseBranchConfig: RenovateConfig = clone(config); + let baseBranchConfig: RenovateConfig = structuredClone(config); if ( config.useBaseBranchConfig === 'merge' && diff --git a/lib/workers/repository/process/lookup/filter-checks.spec.ts b/lib/workers/repository/process/lookup/filter-checks.spec.ts index 8c210db87d6526..9e950ed7073d49 100644 --- a/lib/workers/repository/process/lookup/filter-checks.spec.ts +++ b/lib/workers/repository/process/lookup/filter-checks.spec.ts @@ -1,7 +1,6 @@ import { getConfig, mocked } from '../../../../../test/util'; import type { Release } from '../../../../modules/datasource'; import * as allVersioning from '../../../../modules/versioning'; -import { clone } from '../../../../util/clone'; import * as _dateUtil from '../../../../util/date'; import * as _mergeConfidence from '../../../../util/merge-confidence'; import { toMs } from '../../../../util/pretty-time'; @@ -43,7 +42,7 @@ describe('workers/repository/process/lookup/filter-checks', () => { beforeEach(() => { config = getConfig(); config.currentVersion = '1.0.0'; - sortedReleases = clone(releases); + sortedReleases = structuredClone(releases); jest.resetAllMocks(); dateUtil.getElapsedMs.mockReturnValueOnce(toMs('3 days') ?? 0); dateUtil.getElapsedMs.mockReturnValueOnce(toMs('5 days') ?? 0); diff --git a/lib/workers/repository/process/lookup/index.ts b/lib/workers/repository/process/lookup/index.ts index 1434370d15f64d..0b087ede0e9410 100644 --- a/lib/workers/repository/process/lookup/index.ts +++ b/lib/workers/repository/process/lookup/index.ts @@ -16,7 +16,6 @@ import { import { getRangeStrategy } from '../../../../modules/manager'; import * as allVersioning from '../../../../modules/versioning'; import { ExternalHostError } from '../../../../types/errors/external-host-error'; -import { clone } from '../../../../util/clone'; import { applyPackageRules } from '../../../../util/package-rules'; import { regEx } from '../../../../util/regex'; import { getBucket } from './bucket'; @@ -81,7 +80,7 @@ export async function lookupUpdates( return res; } - dependency = clone(await getPkgReleases(config)); + dependency = structuredClone(await getPkgReleases(config)); if (!dependency) { // If dependency lookup fails then warn and return const warning: ValidationMessage = { diff --git a/lib/workers/repository/update/pr/changelog/release-notes.spec.ts b/lib/workers/repository/update/pr/changelog/release-notes.spec.ts index ae031163b8f29c..978bef659f4a44 100644 --- a/lib/workers/repository/update/pr/changelog/release-notes.spec.ts +++ b/lib/workers/repository/update/pr/changelog/release-notes.spec.ts @@ -2,7 +2,6 @@ import { DateTime } from 'luxon'; import { Fixtures } from '../../../../../../test/fixtures'; import * as httpMock from '../../../../../../test/http-mock'; import { mocked, partial } from '../../../../../../test/util'; -import { clone } from '../../../../../util/clone'; import * as githubGraphql from '../../../../../util/github/graphql'; import * as _hostRules from '../../../../../util/host-rules'; import { toBase64 } from '../../../../../util/string'; @@ -1136,7 +1135,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { it('handles github sourceDirectory', async () => { const sourceDirectory = 'packages/foo'; - const subdirTree = clone(githubTreeResponse); + const subdirTree = structuredClone(githubTreeResponse); for (const file of subdirTree.tree) { file.path = `${sourceDirectory}/${file.path}`; } @@ -1307,7 +1306,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { it('handles gitlab sourceDirectory', async () => { const sourceDirectory = 'packages/foo'; - const response = clone(gitlabTreeResponse).map((file) => ({ + const response = structuredClone(gitlabTreeResponse).map((file) => ({ ...file, path: `${sourceDirectory}/${file.path}`, })); From fc0fb1980ce30e3cfc430fbfabf29d58505fc927 Mon Sep 17 00:00:00 2001 From: RahulGautamSingh Date: Thu, 27 Apr 2023 10:37:38 +0530 Subject: [PATCH 05/12] feat: onboarding branch cache (#21768) Co-authored-by: Rhys Arkins --- lib/util/cache/repository/types.ts | 7 ++ .../onboarding/branch/check.spec.ts | 66 +++++++++++++++++++ .../repository/onboarding/branch/check.ts | 20 +++++- .../onboarding/branch/index.spec.ts | 14 ++++ .../repository/onboarding/branch/index.ts | 27 +++++++- .../branch/onboarding-branch-cache.spec.ts | 61 +++++++++++++++++ .../branch/onboarding-branch-cache.ts | 30 +++++++++ 7 files changed, 222 insertions(+), 3 deletions(-) create mode 100644 lib/workers/repository/onboarding/branch/check.spec.ts create mode 100644 lib/workers/repository/onboarding/branch/onboarding-branch-cache.spec.ts create mode 100644 lib/workers/repository/onboarding/branch/onboarding-branch-cache.ts diff --git a/lib/util/cache/repository/types.ts b/lib/util/cache/repository/types.ts index 3146e6e06385c4..6f5cbfe32601da 100644 --- a/lib/util/cache/repository/types.ts +++ b/lib/util/cache/repository/types.ts @@ -26,6 +26,12 @@ export interface BranchUpgradeCache { sourceUrl?: string; } +export interface OnboardingBranchCache { + onboardingBranch: string; + defaultBranchSha: string; + onboardingBranchSha: string; +} + export interface PrCache { fingerprint: string; /** @@ -100,6 +106,7 @@ export interface RepoCacheData { github?: Record; }; prComments?: Record>; + onboardingBranchCache?: OnboardingBranchCache; } export interface RepoCache { diff --git a/lib/workers/repository/onboarding/branch/check.spec.ts b/lib/workers/repository/onboarding/branch/check.spec.ts new file mode 100644 index 00000000000000..add73cf44a0076 --- /dev/null +++ b/lib/workers/repository/onboarding/branch/check.spec.ts @@ -0,0 +1,66 @@ +import { + RenovateConfig, + git, + mocked, + partial, + platform, +} from '../../../../../test/util'; +import { REPOSITORY_CLOSED_ONBOARDING } from '../../../../constants/error-messages'; +import { logger } from '../../../../logger'; +import type { Pr } from '../../../../modules/platform/types'; +import * as _cache from '../../../../util/cache/repository'; +import { isOnboarded } from './check'; + +jest.mock('../../../../util/cache/repository'); +jest.mock('../../../../util/git'); + +const cache = mocked(_cache); + +describe('workers/repository/onboarding/branch/check', () => { + const config = partial({ + requireConfig: 'required', + suppressNotifications: [], + }); + + it('skips normal onboarding check if onboardingCache is valid', async () => { + cache.getCache.mockReturnValueOnce({ + onboardingBranchCache: { + onboardingBranch: 'configure/renovate', + defaultBranchSha: 'default-sha', + onboardingBranchSha: 'onboarding-sha', + }, + }); + git.getBranchCommit + .mockReturnValueOnce('default-sha') + .mockReturnValueOnce('onboarding-sha'); + const res = await isOnboarded(config); + expect(res).toBeFalse(); + expect(logger.debug).toHaveBeenCalledWith( + 'Onboarding cache is valid. Repo is not onboarded' + ); + }); + + it('continues with normal logic if onboardingCache is invalid', async () => { + cache.getCache.mockReturnValueOnce({ + onboardingBranchCache: { + onboardingBranch: 'configure/renovate', + defaultBranchSha: 'default-sha', + onboardingBranchSha: 'onboarding-sha', + }, + }); + git.getFileList.mockResolvedValue([]); + await isOnboarded(config); + expect(logger.debug).not.toHaveBeenCalledWith( + 'Onboarding cache is valid. Repo is not onboarded' + ); + }); + + it('continues with normal logic if closedPr exists', async () => { + cache.getCache.mockReturnValue({}); + platform.findPr.mockResolvedValue(partial()); + git.getFileList.mockResolvedValue([]); + await expect(isOnboarded(config)).rejects.toThrow( + REPOSITORY_CLOSED_ONBOARDING + ); + }); +}); diff --git a/lib/workers/repository/onboarding/branch/check.ts b/lib/workers/repository/onboarding/branch/check.ts index 7f77ded5485d6d..25f7d21a229c54 100644 --- a/lib/workers/repository/onboarding/branch/check.ts +++ b/lib/workers/repository/onboarding/branch/check.ts @@ -9,7 +9,7 @@ import { Pr, platform } from '../../../../modules/platform'; import { ensureComment } from '../../../../modules/platform/comment'; import { getCache } from '../../../../util/cache/repository'; import { readLocalFile } from '../../../../util/fs'; -import { getFileList } from '../../../../util/git'; +import { getBranchCommit, getFileList } from '../../../../util/git'; async function findFile(fileName: string): Promise { logger.debug(`findFile(${fileName})`); @@ -61,7 +61,24 @@ export async function isOnboarded(config: RenovateConfig): Promise { logger.debug('Config file will be ignored'); return true; } + + const pr = await closedPrExists(config); const cache = getCache(); + const onboardingBranchCache = cache?.onboardingBranchCache; + // if onboarding cache is present and base branch has not been updated branch is not onboarded + // if closed pr exists then presence of onboarding cache doesn't matter as we need to skip onboarding + if ( + !pr && + onboardingBranchCache && + onboardingBranchCache.defaultBranchSha === + getBranchCommit(config.defaultBranch!) && + onboardingBranchCache.onboardingBranchSha === + getBranchCommit(config.onboardingBranch!) + ) { + logger.debug('Onboarding cache is valid. Repo is not onboarded'); + return false; + } + if (cache.configFileName) { logger.debug('Checking cached config file name'); try { @@ -104,7 +121,6 @@ export async function isOnboarded(config: RenovateConfig): Promise { throw new Error(REPOSITORY_NO_CONFIG); } - const pr = await closedPrExists(config); if (!pr) { logger.debug('Found no closed onboarding PR'); return false; diff --git a/lib/workers/repository/onboarding/branch/index.spec.ts b/lib/workers/repository/onboarding/branch/index.spec.ts index 1569fab4c451c3..04fc5ed1856d67 100644 --- a/lib/workers/repository/onboarding/branch/index.spec.ts +++ b/lib/workers/repository/onboarding/branch/index.spec.ts @@ -21,6 +21,7 @@ import * as _cache from '../../../../util/cache/repository'; import type { FileAddition } from '../../../../util/git/types'; import { OnboardingState } from '../common'; import * as _config from './config'; +import * as _onboardingCache from './onboarding-branch-cache'; import * as _rebase from './rebase'; import { checkOnboardingBranch } from '.'; @@ -32,12 +33,21 @@ jest.mock('../../../../util/cache/repository'); jest.mock('../../../../util/fs'); jest.mock('../../../../util/git'); jest.mock('./config'); +jest.mock('./onboarding-branch-cache'); const cache = mocked(_cache); +const onboardingCache = mocked(_onboardingCache); describe('workers/repository/onboarding/branch/index', () => { describe('checkOnboardingBranch', () => { let config: RenovateConfig; + const dummyCache = { + onboardingBranchCache: { + onboardingBranch: 'configure/renovate', + defaultBranchSha: 'default-sha', + onboardingBranchSha: 'onboarding-sha', + }, + }; beforeEach(() => { memCache.init(); @@ -168,9 +178,11 @@ describe('workers/repository/onboarding/branch/index', () => { }); it('detects repo is onboarded via file', async () => { + cache.getCache.mockReturnValue(dummyCache); git.getFileList.mockResolvedValueOnce(['renovate.json']); const res = await checkOnboardingBranch(config); expect(res.repoIsOnboarded).toBeTrue(); + expect(onboardingCache.deleteOnboardingCache).toHaveBeenCalledTimes(1); // removes onboarding cache when repo is onboarded }); it('handles removed cached file name', async () => { @@ -251,6 +263,7 @@ describe('workers/repository/onboarding/branch/index', () => { }); it('updates onboarding branch', async () => { + cache.getCache.mockReturnValue(dummyCache); git.getFileList.mockResolvedValue(['package.json']); platform.findPr.mockResolvedValue(null); platform.getBranchPr.mockResolvedValueOnce(mock()); @@ -259,6 +272,7 @@ describe('workers/repository/onboarding/branch/index', () => { expect(res.repoIsOnboarded).toBeFalse(); expect(res.branchList).toEqual(['renovate/configure']); expect(git.checkoutBranch).toHaveBeenCalledTimes(1); + expect(onboardingCache.setOnboardingCache).toHaveBeenCalledTimes(1); // update onboarding cache expect(scm.commitAndPush).toHaveBeenCalledTimes(0); }); diff --git a/lib/workers/repository/onboarding/branch/index.ts b/lib/workers/repository/onboarding/branch/index.ts index 2add4def7375e7..c8f50ecefa8575 100644 --- a/lib/workers/repository/onboarding/branch/index.ts +++ b/lib/workers/repository/onboarding/branch/index.ts @@ -8,13 +8,21 @@ import { } from '../../../../constants/error-messages'; import { logger } from '../../../../logger'; import { Pr, platform } from '../../../../modules/platform'; -import { checkoutBranch, setGitAuthor } from '../../../../util/git'; +import { + checkoutBranch, + getBranchCommit, + setGitAuthor, +} from '../../../../util/git'; import { extractAllDependencies } from '../../extract'; import { mergeRenovateConfig } from '../../init/merge'; import { OnboardingState } from '../common'; import { getOnboardingPr, isOnboarded } from './check'; import { getOnboardingConfig } from './config'; import { createOnboardingBranch } from './create'; +import { + deleteOnboardingCache, + setOnboardingCache, +} from './onboarding-branch-cache'; import { rebaseOnboardingBranch } from './rebase'; export async function checkOnboardingBranch( @@ -26,6 +34,9 @@ export async function checkOnboardingBranch( const repoIsOnboarded = await isOnboarded(config); if (repoIsOnboarded) { logger.debug('Repo is onboarded'); + + // delete onboarding cache + deleteOnboardingCache(); return { ...config, repoIsOnboarded }; } if (config.isFork && config.forkProcessing !== 'enabled') { @@ -47,6 +58,13 @@ export async function checkOnboardingBranch( { branch: config.onboardingBranch, commit, onboarding: true }, 'Branch updated' ); + + // update onboarding cache + setOnboardingCache( + config.onboardingBranch!, + getBranchCommit(config.defaultBranch!)!, + commit + ); } // istanbul ignore if if (platform.refreshPr) { @@ -78,6 +96,13 @@ export async function checkOnboardingBranch( { branch: onboardingBranch, commit, onboarding: true }, 'Branch created' ); + + // set onboarding branch cache + setOnboardingCache( + config.onboardingBranch!, + getBranchCommit(config.defaultBranch!)!, + commit + ); } } if (!GlobalConfig.get('dryRun')) { diff --git a/lib/workers/repository/onboarding/branch/onboarding-branch-cache.spec.ts b/lib/workers/repository/onboarding/branch/onboarding-branch-cache.spec.ts new file mode 100644 index 00000000000000..10307e5a3ec374 --- /dev/null +++ b/lib/workers/repository/onboarding/branch/onboarding-branch-cache.spec.ts @@ -0,0 +1,61 @@ +import { mocked } from '../../../../../test/util'; +import * as _cache from '../../../../util/cache/repository'; +import type { RepoCacheData } from '../../../../util/cache/repository/types'; +import { + deleteOnboardingCache, + setOnboardingCache, +} from './onboarding-branch-cache'; + +jest.mock('../../../../util/cache/repository'); +const cache = mocked(_cache); + +describe('workers/repository/onboarding/branch/onboarding-branch-cache', () => { + it('sets new cache', () => { + const dummyCache = {} satisfies RepoCacheData; + cache.getCache.mockReturnValueOnce(dummyCache); + setOnboardingCache('configure/renovate', 'default-sha', 'onboarding-sha'); + expect(dummyCache).toEqual({ + onboardingBranchCache: { + onboardingBranch: 'configure/renovate', + defaultBranchSha: 'default-sha', + onboardingBranchSha: 'onboarding-sha', + }, + }); + }); + + it('updates old cache', () => { + const dummyCache = { + onboardingBranchCache: { + onboardingBranch: 'configure/renovate', + defaultBranchSha: 'default-sha', + onboardingBranchSha: 'onboarding-sha', + }, + } satisfies RepoCacheData; + cache.getCache.mockReturnValueOnce(dummyCache); + setOnboardingCache( + 'configure/renovate', + 'default-sha-1', + 'onboarding-sha-1' + ); + expect(dummyCache).toEqual({ + onboardingBranchCache: { + onboardingBranch: 'configure/renovate', + defaultBranchSha: 'default-sha-1', + onboardingBranchSha: 'onboarding-sha-1', + }, + }); + }); + + it('deletes cache', () => { + const dummyCache = { + onboardingBranchCache: { + onboardingBranch: 'configure/renovate', + defaultBranchSha: 'default-sha', + onboardingBranchSha: 'onboarding-sha', + }, + } satisfies RepoCacheData; + cache.getCache.mockReturnValueOnce(dummyCache); + deleteOnboardingCache(); + expect(dummyCache.onboardingBranchCache).toBeUndefined(); + }); +}); diff --git a/lib/workers/repository/onboarding/branch/onboarding-branch-cache.ts b/lib/workers/repository/onboarding/branch/onboarding-branch-cache.ts new file mode 100644 index 00000000000000..a6ecfcb29677b2 --- /dev/null +++ b/lib/workers/repository/onboarding/branch/onboarding-branch-cache.ts @@ -0,0 +1,30 @@ +import { logger } from '../../../../logger'; +import { getCache } from '../../../../util/cache/repository'; + +export function setOnboardingCache( + onboardingBranch: string, + defaultBranchSha: string, + onboardingBranchSha: string +): void { + const cache = getCache(); + const onboardingCache = { + onboardingBranch, + defaultBranchSha, + onboardingBranchSha, + }; + if (cache.onboardingBranchCache) { + logger.debug('Update Onboarding Cache'); + } else { + logger.debug('Create Onboarding Cache'); + } + cache.onboardingBranchCache = onboardingCache; +} + +export function deleteOnboardingCache(): void { + const cache = getCache(); + + if (cache?.onboardingBranchCache) { + logger.debug('Delete Onboarding Cache'); + delete cache.onboardingBranchCache; + } +} From 541cb0cb90700db7f133e86abd19c563ca82f84f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Malte=20M=C3=BCnch?= Date: Thu, 27 Apr 2023 07:16:59 +0200 Subject: [PATCH 06/12] docs: do not git add in post upgrade tasks (#21828) --- docs/usage/configuration-options.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md index 6b46b8c499012e..862fc39935e064 100644 --- a/docs/usage/configuration-options.md +++ b/docs/usage/configuration-options.md @@ -2554,6 +2554,10 @@ A list of commands that are executed after Renovate has updated a dependency but You can use variable templating in your commands if [`allowPostUpgradeCommandTemplating`](https://docs.renovatebot.com/self-hosted-configuration/#allowpostupgradecommandtemplating) is enabled. + +!!! note + Do not use `git add` in your commands to add new files to be tracked, add them by including them in your [`fileFilters`](https://docs.renovatebot.com/self-hosted-configuration/#filefilters) instead. + ### fileFilters A list of glob-style matchers that determine which files will be included in the final commit made by Renovate. From 1574baea6c21fc370539e5a532ebafd1064d6fc7 Mon Sep 17 00:00:00 2001 From: Jamie Magee Date: Wed, 26 Apr 2023 22:18:45 -0700 Subject: [PATCH 07/12] refactor: satisfy type checks for `array.includes` (#21834) --- lib/modules/manager/npm/extract/monorepo.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/modules/manager/npm/extract/monorepo.ts b/lib/modules/manager/npm/extract/monorepo.ts index ea5db558a15e7c..2f3c2cc3c4a544 100644 --- a/lib/modules/manager/npm/extract/monorepo.ts +++ b/lib/modules/manager/npm/extract/monorepo.ts @@ -42,7 +42,10 @@ export async function detectMonorepos( .filter(Boolean); p.deps?.forEach((dep) => { - if (internalPackageNames.includes(dep.depName)) { + if ( + is.string(dep.depName) && + internalPackageNames.includes(dep.depName) + ) { dep.isInternal = true; } }); From 242e4908344a17632fc9774e56e97c49052bc49a Mon Sep 17 00:00:00 2001 From: Rhys Arkins Date: Thu, 27 Apr 2023 08:38:40 +0200 Subject: [PATCH 08/12] chore: forbid user bug reports (#21736) Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com> --- .github/ISSUE_TEMPLATE/bug_report.yml | 11 ++++++----- .github/label-actions.yml | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 3b1d63988d8f22..e0be4a300ebb64 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,13 +1,14 @@ name: Bug report -description: Bug reports require a minimal reproduction and should be preceded by a Discussion first. -labels: ['type:bug', 'status:requirements', 'priority-5-triage'] +description: Bug reports are only for maintainers. If you are a Renovate user, create a Discussion instead. +labels: + ['type:bug', 'status:requirements', 'priority-5-triage', 'needs-discussion'] body: - type: markdown attributes: value: | - Don't create a Bug Report like this without a GitHub Discussion and [minimal reproduction](https://github.com/renovatebot/renovate/blob/main/docs/development/minimal-reproductions.md) first. - Invalid bug reports waste the time of maintainers and can confuse other users so they will closed, locked and deleted. - You are always welcome to create Discussions and new users should always do that instead. + STOP! Only create a Bug Report if you're a maintainer of Renovate. + If you're a user of Renovate, please create a GitHub Discussion. + Please go back a step and select Discussion instead of Issue. - type: dropdown id: how-are-you-running-renovate diff --git a/.github/label-actions.yml b/.github/label-actions.yml index 016b4113daa4bb..e0f3205b11fd67 100644 --- a/.github/label-actions.yml +++ b/.github/label-actions.yml @@ -188,10 +188,10 @@ **Please create a GitHub Discussion instead of this issue.** - As this issue has been labeled as `needs-discussion` by a maintainer it is being automatically closed and locked. It will later be deleted to avoid polluting repository searches with duplicate or incorrect content. + We only want Renovate maintainers to create new Issues. If needed, a Renovate maintainer will create an Issue after your Discussion been triaged and confirmed. As a Renovate user, please create a GitHub Discussion in this repo instead. - This doesn't necessarily mean that what you've reported isn't a problem or a reasonable idea, but it does mean that it should be raised as a Discussion first and not an Issue. Please create a Discussion now, assuming you still wish to discuss it. + This Issue will now be closed and locked. We may later batch-delete this issue. This way we keep Issues actionable, and free of duplicates or wrong bug reports. Thanks, The Renovate team From 012c0ac2fe32832e60a62bde405c0a241efd314c Mon Sep 17 00:00:00 2001 From: RahulGautamSingh Date: Thu, 27 Apr 2023 18:12:25 +0530 Subject: [PATCH 09/12] feat: npm workspaces (#21274) Co-authored-by: Michael Kriese --- .../__snapshots__/monorepo.spec.ts.snap | 16 +- lib/modules/manager/npm/extract/monorepo.ts | 2 +- lib/modules/manager/npm/post-update/index.ts | 2 +- .../manager/npm/post-update/npm.spec.ts | 264 +++++++++++++++++- lib/modules/manager/npm/post-update/npm.ts | 110 +++++++- lib/modules/manager/npm/types.ts | 1 - lib/modules/manager/types.ts | 1 + lib/util/url.spec.ts | 12 + lib/util/url.ts | 4 + 9 files changed, 391 insertions(+), 21 deletions(-) diff --git a/lib/modules/manager/npm/extract/__snapshots__/monorepo.spec.ts.snap b/lib/modules/manager/npm/extract/__snapshots__/monorepo.spec.ts.snap index 7c1028ec447179..70e2789482f224 100644 --- a/lib/modules/manager/npm/extract/__snapshots__/monorepo.spec.ts.snap +++ b/lib/modules/manager/npm/extract/__snapshots__/monorepo.spec.ts.snap @@ -42,11 +42,11 @@ exports[`modules/manager/npm/extract/monorepo .extractPackageFile() updates inte ], "managerData": { "hasPackageManager": undefined, - "hasWorkspaces": false, "lernaClient": undefined, "lernaJsonFile": "lerna.json", "npmLock": undefined, "packageJsonName": "@org/a", + "workspacesPackages": undefined, "yarnLock": undefined, "yarnZeroInstall": undefined, }, @@ -57,11 +57,11 @@ exports[`modules/manager/npm/extract/monorepo .extractPackageFile() updates inte { "managerData": { "hasPackageManager": undefined, - "hasWorkspaces": false, "lernaClient": undefined, "lernaJsonFile": "lerna.json", "npmLock": undefined, "packageJsonName": "@org/b", + "workspacesPackages": undefined, "yarnLock": undefined, "yarnZeroInstall": undefined, }, @@ -114,11 +114,11 @@ exports[`modules/manager/npm/extract/monorepo .extractPackageFile() uses lerna p ], "managerData": { "hasPackageManager": undefined, - "hasWorkspaces": false, "lernaClient": undefined, "lernaJsonFile": "lerna.json", "npmLock": undefined, "packageJsonName": "@org/a", + "workspacesPackages": undefined, "yarnLock": undefined, "yarnZeroInstall": undefined, }, @@ -129,11 +129,11 @@ exports[`modules/manager/npm/extract/monorepo .extractPackageFile() uses lerna p { "managerData": { "hasPackageManager": undefined, - "hasWorkspaces": false, "lernaClient": undefined, "lernaJsonFile": "lerna.json", "npmLock": undefined, "packageJsonName": "@org/b", + "workspacesPackages": undefined, "yarnLock": undefined, "yarnZeroInstall": undefined, }, @@ -162,11 +162,13 @@ exports[`modules/manager/npm/extract/monorepo .extractPackageFile() uses yarn wo { "managerData": { "hasPackageManager": undefined, - "hasWorkspaces": true, "lernaClient": "yarn", "lernaJsonFile": "lerna.json", "npmLock": undefined, "packageJsonName": "@org/a", + "workspacesPackages": [ + "packages/*", + ], "yarnLock": undefined, "yarnZeroInstall": undefined, }, @@ -177,11 +179,13 @@ exports[`modules/manager/npm/extract/monorepo .extractPackageFile() uses yarn wo { "managerData": { "hasPackageManager": undefined, - "hasWorkspaces": true, "lernaClient": "yarn", "lernaJsonFile": "lerna.json", "npmLock": undefined, "packageJsonName": "@org/b", + "workspacesPackages": [ + "packages/*", + ], "yarnLock": undefined, "yarnZeroInstall": undefined, }, diff --git a/lib/modules/manager/npm/extract/monorepo.ts b/lib/modules/manager/npm/extract/monorepo.ts index 2f3c2cc3c4a544..d296c28af8d846 100644 --- a/lib/modules/manager/npm/extract/monorepo.ts +++ b/lib/modules/manager/npm/extract/monorepo.ts @@ -59,7 +59,7 @@ export async function detectMonorepos( subPackage.managerData.yarnLock ??= yarnLock; subPackage.managerData.npmLock ??= npmLock; subPackage.skipInstalls = skipInstalls && subPackage.skipInstalls; // skip if both are true - subPackage.managerData.hasWorkspaces = !!workspacesPackages; + subPackage.managerData.workspacesPackages = workspacesPackages; subPackage.npmrc ??= npmrc; if (p.extractedConstraints) { diff --git a/lib/modules/manager/npm/post-update/index.ts b/lib/modules/manager/npm/post-update/index.ts index 9bb9e793f9ee4c..5fa20722a8b18b 100644 --- a/lib/modules/manager/npm/post-update/index.ts +++ b/lib/modules/manager/npm/post-update/index.ts @@ -119,7 +119,7 @@ export function determineLockFileDirs( } else if ( packageFile.managerData?.lernaJsonFile && packageFile.managerData.yarnLock && - !packageFile.managerData.hasWorkspaces + !packageFile.managerData.workspacesPackages?.length ) { lernaJsonFiles.push(packageFile.managerData.lernaJsonFile); } else { diff --git a/lib/modules/manager/npm/post-update/npm.spec.ts b/lib/modules/manager/npm/post-update/npm.spec.ts index 3ecda1373e87a0..42d4851b9781e6 100644 --- a/lib/modules/manager/npm/post-update/npm.spec.ts +++ b/lib/modules/manager/npm/post-update/npm.spec.ts @@ -30,7 +30,7 @@ describe('modules/manager/npm/post-update/npm', () => { const skipInstalls = true; const postUpdateOptions = ['npmDedupe']; const updates = [ - { depName: 'some-dep', newVersion: '1.0.1', isLockfileUpdate: false }, + { packageName: 'some-dep', newVersion: '1.0.1', isLockfileUpdate: false }, ]; const res = await npmHelper.generateLockFile( 'some-dir', @@ -50,7 +50,7 @@ describe('modules/manager/npm/post-update/npm', () => { fs.readLocalFile.mockResolvedValueOnce('package-lock-contents'); const skipInstalls = true; const updates = [ - { depName: 'some-dep', newVersion: '1.0.1', isLockfileUpdate: true }, + { packageName: 'some-dep', newVersion: '1.0.1', isLockfileUpdate: true }, ]; const res = await npmHelper.generateLockFile( 'some-dir', @@ -73,11 +73,12 @@ describe('modules/manager/npm/post-update/npm', () => { const skipInstalls = true; const updates = [ { - depName: 'postcss', + packageName: 'postcss', depType: 'dependencies', newVersion: '8.4.8', newValue: '^8.0.0', isLockfileUpdate: true, + managerData: {}, // intentional: edge-case test for workspaces }, ]; const res = await npmHelper.generateLockFile( @@ -307,4 +308,261 @@ describe('modules/manager/npm/post-update/npm', () => { }, ]); }); + + describe('installs workspace only packages separately', () => { + const updates = [ + { + packageFile: 'some-dir/docs/a/package.json', + packageName: 'abbrev', + depType: 'dependencies', + newVersion: '1.1.0', + newValue: '^1.0.0', + isLockfileUpdate: true, + managerData: { + workspacesPackages: ['docs/*', 'web/*'], + }, + }, + { + packageFile: 'some-dir/web/b/package.json', + packageName: 'xmldoc', + depType: 'dependencies', + newVersion: '2.2.0', + newValue: '^2.0.0', + isLockfileUpdate: true, + managerData: { + workspacesPackages: ['docs/*', 'web/*'], + }, + }, + { + packageFile: 'some-dir/docs/a/package.json', + packageName: 'postcss', + depType: 'dependencies', + newVersion: '8.4.8', + newValue: '^8.0.0', + isLockfileUpdate: true, + managerData: { + workspacesPackages: ['docs/*', 'web/*'], + }, + }, + { + packageFile: 'some-dir/package.json', + packageName: 'chalk', + depType: 'dependencies', + newVersion: '9.4.8', + newValue: '^9.0.0', + isLockfileUpdate: true, + managerData: { + workspacesPackages: ['docs/*', 'web/*'], + }, + }, + { + packageFile: 'some-dir/web/b/package.json', + packageName: 'postcss', + depType: 'dependencies', + newVersion: '8.4.8', + newValue: '^8.0.0', + isLockfileUpdate: true, + managerData: { + workspacesPackages: ['docs/*', 'web/*'], + }, + }, + { + packageFile: 'some-dir/package.json', + packageName: 'postcss', + depType: 'dependencies', + newVersion: '8.4.8', + newValue: '^8.0.0', + isLockfileUpdate: true, + managerData: { + workspacesPackages: ['docs/*', 'web/*'], + }, + }, + { + packageFile: 'some-dir/web/b/package.json', + packageName: 'hello', + depType: 'dependencies', + newVersion: '1.1.1', + newValue: '^1.0.0', + isLockfileUpdate: true, + managerData: { + workspacesPackages: ['docs/*', 'web/*'], + }, + }, + { + packageFile: 'some-dir/docs/a/package.json', + packageName: 'hello', + depType: 'dependencies', + newVersion: '1.1.1', + newValue: '^1.0.0', + isLockfileUpdate: true, + managerData: { + workspacesPackages: ['docs/*', 'web/*'], + }, + }, + ]; + + it('workspace in sub-folder', async () => { + const execSnapshots = mockExecAll(); + fs.readLocalFile.mockResolvedValueOnce('package-lock content'); + const skipInstalls = true; + const res = await npmHelper.generateLockFile( + 'some-dir', + {}, + 'package-lock.json', + { skipInstalls }, + updates + ); + expect(fs.readLocalFile).toHaveBeenCalledTimes(1); + expect(res.error).toBeUndefined(); + expect(execSnapshots).toMatchObject([ + { + cmd: 'npm install --package-lock-only --no-audit --ignore-scripts --workspace=docs/a abbrev@1.1.0 hello@1.1.1', + }, + { + cmd: 'npm install --package-lock-only --no-audit --ignore-scripts --workspace=web/b xmldoc@2.2.0 hello@1.1.1', + }, + + { + cmd: 'npm install --package-lock-only --no-audit --ignore-scripts chalk@9.4.8 postcss@8.4.8', + }, + ]); + }); + + it('workspace in root folder', async () => { + const modifiedUpdates = updates.map((update) => { + return { + ...update, + packageFile: update.packageFile.replace('some-dir/', ''), + }; + }); + const execSnapshots = mockExecAll(); + fs.readLocalFile.mockResolvedValueOnce('package-lock content'); + const skipInstalls = true; + const res = await npmHelper.generateLockFile( + '.', + {}, + 'package-lock.json', + { skipInstalls }, + modifiedUpdates + ); + expect(fs.readLocalFile).toHaveBeenCalledTimes(1); + expect(res.error).toBeUndefined(); + expect(execSnapshots).toMatchObject([ + { + cmd: 'npm install --package-lock-only --no-audit --ignore-scripts --workspace=docs/a abbrev@1.1.0 hello@1.1.1', + }, + { + cmd: 'npm install --package-lock-only --no-audit --ignore-scripts --workspace=web/b xmldoc@2.2.0 hello@1.1.1', + }, + + { + cmd: 'npm install --package-lock-only --no-audit --ignore-scripts chalk@9.4.8 postcss@8.4.8', + }, + ]); + expect( + npmHelper.divideWorkspaceAndRootDeps('.', modifiedUpdates) + ).toMatchObject({ + lockRootUpdates: [ + { + packageFile: 'package.json', + packageName: 'chalk', + depType: 'dependencies', + newVersion: '9.4.8', + newValue: '^9.0.0', + isLockfileUpdate: true, + managerData: { + workspacesPackages: ['docs/*', 'web/*'], + }, + }, + { + packageFile: 'package.json', + packageName: 'postcss', + depType: 'dependencies', + newVersion: '8.4.8', + newValue: '^8.0.0', + isLockfileUpdate: true, + managerData: { + workspacesPackages: ['docs/*', 'web/*'], + }, + }, + ], + lockWorkspacesUpdates: [ + { + packageFile: 'docs/a/package.json', + packageName: 'abbrev', + depType: 'dependencies', + newVersion: '1.1.0', + newValue: '^1.0.0', + isLockfileUpdate: true, + managerData: { + workspacesPackages: ['docs/*', 'web/*'], + }, + workspace: 'docs/a', + }, + { + packageFile: 'web/b/package.json', + packageName: 'xmldoc', + depType: 'dependencies', + newVersion: '2.2.0', + newValue: '^2.0.0', + isLockfileUpdate: true, + managerData: { + workspacesPackages: ['docs/*', 'web/*'], + }, + workspace: 'web/b', + }, + { + packageFile: 'docs/a/package.json', + packageName: 'postcss', + depType: 'dependencies', + newVersion: '8.4.8', + newValue: '^8.0.0', + isLockfileUpdate: true, + managerData: { + workspacesPackages: ['docs/*', 'web/*'], + }, + workspace: 'docs/a', + }, + { + packageFile: 'web/b/package.json', + packageName: 'postcss', + depType: 'dependencies', + newVersion: '8.4.8', + newValue: '^8.0.0', + isLockfileUpdate: true, + managerData: { + workspacesPackages: ['docs/*', 'web/*'], + }, + workspace: 'web/b', + }, + { + packageFile: 'web/b/package.json', + packageName: 'hello', + depType: 'dependencies', + newVersion: '1.1.1', + newValue: '^1.0.0', + isLockfileUpdate: true, + managerData: { + workspacesPackages: ['docs/*', 'web/*'], + }, + workspace: 'web/b', + }, + { + packageFile: 'docs/a/package.json', + packageName: 'hello', + depType: 'dependencies', + newVersion: '1.1.1', + newValue: '^1.0.0', + isLockfileUpdate: true, + managerData: { + workspacesPackages: ['docs/*', 'web/*'], + }, + workspace: 'docs/a', + }, + ], + workspaces: new Set(['docs/a', 'web/b']), + rootDeps: new Set(['chalk@9.4.8', 'postcss@8.4.8']), + }); + }); + }); }); diff --git a/lib/modules/manager/npm/post-update/npm.ts b/lib/modules/manager/npm/post-update/npm.ts index f53bf63a0bea34..fa41fdad02601a 100644 --- a/lib/modules/manager/npm/post-update/npm.ts +++ b/lib/modules/manager/npm/post-update/npm.ts @@ -1,3 +1,6 @@ +// TODO: types (#7154) +import is from '@sindresorhus/is'; +import minimatch from 'minimatch'; import upath from 'upath'; import { GlobalConfig } from '../../../../config/global'; import { @@ -17,6 +20,7 @@ import { readLocalFile, renameLocalFile, } from '../../../../util/fs'; +import { trimSlashes } from '../../../../util/url'; import type { PostUpdateConfig, Upgrade } from '../../types'; import { composeLockFile, parseLockFile } from '../utils'; import { getNodeToolConstraint } from './node-version'; @@ -81,15 +85,35 @@ export async function generateLockFile( // rangeStrategy = update-lockfile const lockUpdates = upgrades.filter((upgrade) => upgrade.isLockfileUpdate); - if (lockUpdates.length) { + + // divide the deps in two categories: workspace and root + const { lockRootUpdates, lockWorkspacesUpdates, workspaces, rootDeps } = + divideWorkspaceAndRootDeps(lockFileDir, lockUpdates); + + if (workspaces.size && lockWorkspacesUpdates.length) { + logger.debug('Performing lockfileUpdate (npm-workspaces)'); + for (const workspace of workspaces) { + const currentWorkspaceUpdates = lockWorkspacesUpdates + .filter((update) => update.workspace === workspace) + .map((update) => update.managerData?.packageKey) + .filter((packageKey) => !rootDeps.has(packageKey)); + + if (currentWorkspaceUpdates.length) { + const updateCmd = `npm install ${cmdOptions} --workspace=${workspace} ${currentWorkspaceUpdates.join( + ' ' + )}`; + commands.push(updateCmd); + } + } + } + + if (lockRootUpdates.length) { logger.debug('Performing lockfileUpdate (npm)'); const updateCmd = - `npm install ${cmdOptions}` + - lockUpdates - // TODO: types (#7154) - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - .map((update) => ` ${update.depName}@${update.newVersion}`) - .join(''); + `npm install ${cmdOptions} ` + + lockRootUpdates + .map((update) => update.managerData?.packageKey) + .join(' '); commands.push(updateCmd); } @@ -150,8 +174,10 @@ export async function generateLockFile( | 'optionalDependencies'; // TODO #7154 - if (lockFileParsed.packages?.['']?.[depType]?.[lockUpdate.depName!]) { - lockFileParsed.packages[''][depType]![lockUpdate.depName!] = + if ( + lockFileParsed.packages?.['']?.[depType]?.[lockUpdate.packageName!] + ) { + lockFileParsed.packages[''][depType]![lockUpdate.packageName!] = lockUpdate.newValue!; } }); @@ -176,3 +202,69 @@ export async function generateLockFile( } return { lockFile }; } + +export function divideWorkspaceAndRootDeps( + lockFileDir: string, + lockUpdates: Upgrade[] +): { + lockRootUpdates: Upgrade[]; + lockWorkspacesUpdates: Upgrade[]; + workspaces: Set; + rootDeps: Set; +} { + const lockRootUpdates: Upgrade[] = []; // stores all upgrades which are present in root package.json + const lockWorkspacesUpdates: Upgrade[] = []; // stores all upgrades which are present in workspaces package.json + const workspaces = new Set(); // name of all workspaces + const rootDeps = new Set(); // packageName of all upgrades in root package.json (makes it check duplicate deps in root) + + // divide the deps in two categories: workspace and root + for (const upgrade of lockUpdates) { + upgrade.managerData ??= {}; + upgrade.managerData.packageKey = generatePackageKey( + upgrade.packageName!, + upgrade.newVersion! + ); + if ( + upgrade.managerData.workspacesPackages?.length && + is.string(upgrade.packageFile) + ) { + const workspacePatterns = upgrade.managerData.workspacesPackages; // glob pattern or directory name/path + const packageFileDir = trimSlashes( + upgrade.packageFile.replace('package.json', '') + ); + + // workspaceDir = packageFileDir - lockFileDir + const workspaceDir = trimSlashes(packageFileDir.replace(lockFileDir, '')); + + if (is.nonEmptyString(workspaceDir)) { + let workspaceName: string | undefined; + // compare workspaceDir to workspace patterns + // stop when the first match is found and + // add workspaceDir to workspaces set and upgrade object + for (const workspacePattern of workspacePatterns ?? []) { + if (minimatch(workspaceDir, workspacePattern)) { + workspaceName = workspaceDir; + break; + } + } + if ( + workspaceName && + !rootDeps.has(upgrade.managerData.packageKey) // prevent same dep from existing in root and workspace + ) { + workspaces.add(workspaceName); + upgrade.workspace = workspaceName; + lockWorkspacesUpdates.push(upgrade); + } + continue; + } + } + lockRootUpdates.push(upgrade); + rootDeps.add(upgrade.managerData.packageKey); + } + + return { lockRootUpdates, lockWorkspacesUpdates, workspaces, rootDeps }; +} + +function generatePackageKey(packageName: string, version: string): string { + return `${packageName}@${version}`; +} diff --git a/lib/modules/manager/npm/types.ts b/lib/modules/manager/npm/types.ts index 9fa430cd8d7430..526afafbf17382 100644 --- a/lib/modules/manager/npm/types.ts +++ b/lib/modules/manager/npm/types.ts @@ -81,7 +81,6 @@ export interface NpmLockFiles { export interface NpmManagerData extends NpmLockFiles, Record { hasPackageManager?: boolean; - hasWorkspaces?: boolean; lernaClient?: string; lernaJsonFile?: string; lernaPackages?: string[]; diff --git a/lib/modules/manager/types.ts b/lib/modules/manager/types.ts index c81b729d1c8470..6e26451238eff8 100644 --- a/lib/modules/manager/types.ts +++ b/lib/modules/manager/types.ts @@ -155,6 +155,7 @@ export interface PackageDependency> } export interface Upgrade> extends PackageDependency { + workspace?: string; isLockfileUpdate?: boolean; currentRawValue?: any; depGroup?: string; diff --git a/lib/util/url.spec.ts b/lib/util/url.spec.ts index 27652dc9c515f6..b9a5b723a5be62 100644 --- a/lib/util/url.spec.ts +++ b/lib/util/url.spec.ts @@ -8,6 +8,7 @@ import { parseUrl, replaceUrlPath, resolveBaseUrl, + trimSlashes, trimTrailingSlash, validateUrl, } from './url'; @@ -122,6 +123,17 @@ describe('util/url', () => { expect(trimTrailingSlash('foo//////')).toBe('foo'); }); + it('trimSlashes', () => { + expect(trimSlashes('foo')).toBe('foo'); + expect(trimSlashes('/foo')).toBe('foo'); + expect(trimSlashes('foo/')).toBe('foo'); + expect(trimSlashes('//////foo//////')).toBe('foo'); + expect(trimSlashes('foo/bar')).toBe('foo/bar'); + expect(trimSlashes('/foo/bar')).toBe('foo/bar'); + expect(trimSlashes('foo/bar/')).toBe('foo/bar'); + expect(trimSlashes('/foo/bar/')).toBe('foo/bar'); + }); + it('ensureTrailingSlash', () => { expect(ensureTrailingSlash('')).toBe('/'); expect(ensureTrailingSlash('/')).toBe('/'); diff --git a/lib/util/url.ts b/lib/util/url.ts index 7d4c1551d819d2..a082ba8aebdb87 100644 --- a/lib/util/url.ts +++ b/lib/util/url.ts @@ -30,6 +30,10 @@ export function trimLeadingSlash(path: string): string { return path.replace(/^\/+/, ''); } +export function trimSlashes(path: string): string { + return trimLeadingSlash(trimTrailingSlash(path)); +} + /** * Resolves an input path against a base URL * From d8cd68638ed857029ff7d4019c93b11120f50591 Mon Sep 17 00:00:00 2001 From: Michael Kriese Date: Thu, 27 Apr 2023 15:36:09 +0200 Subject: [PATCH 10/12] fix(datasource/docker): replace legacy url resolve (#21841) --- lib/modules/datasource/docker/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/modules/datasource/docker/index.ts b/lib/modules/datasource/docker/index.ts index 7dd36c4fd3061c..88ab4354ffcea1 100644 --- a/lib/modules/datasource/docker/index.ts +++ b/lib/modules/datasource/docker/index.ts @@ -1,4 +1,3 @@ -import URL from 'node:url'; import { ECR } from '@aws-sdk/client-ecr'; import type { ECRClientConfig } from '@aws-sdk/client-ecr'; import is from '@sindresorhus/is'; @@ -911,7 +910,7 @@ export class DockerDatasource extends Datasource { ? `${url}&last=${linkHeader.next.last}` : null; } else { - url = linkHeader?.next ? URL.resolve(url, linkHeader.next.url) : null; + url = linkHeader?.next ? new URL(linkHeader.next.url, url).href : null; } page += 1; } while (url && page < 20); From d03f96d0e3f2fcf154f8658749747910a105caf5 Mon Sep 17 00:00:00 2001 From: Michael Kriese Date: Thu, 27 Apr 2023 15:44:19 +0200 Subject: [PATCH 11/12] feat: deprecate node v19 (#21843) --- docs/development/local-development.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/development/local-development.md b/docs/development/local-development.md index d1714fe95a8132..c390be6694e3f7 100644 --- a/docs/development/local-development.md +++ b/docs/development/local-development.md @@ -11,7 +11,7 @@ For example, if you think anything is unclear, or you think something needs to b You need the following dependencies for local development: - Git `>=2.33.0` -- Node.js `>=18.12.0` +- Node.js `^18.12.0 || >=20.0.0` - Yarn `^1.22.5` - C++ compiler diff --git a/package.json b/package.json index fa31287768ea0d..6c0f3f92e49f86 100644 --- a/package.json +++ b/package.json @@ -133,7 +133,7 @@ }, "engines-next": { "description": "Versions other than the below are deprecated and a warning will be logged", - "node": ">=18.12.0" + "node": "^18.12.0 || >=20.0.0" }, "dependencies": { "@aws-sdk/client-codecommit": "3.312.0", From de3cb95e91569768608df6a10d7e166f221c1243 Mon Sep 17 00:00:00 2001 From: Rhys Arkins Date: Thu, 27 Apr 2023 17:03:06 +0200 Subject: [PATCH 12/12] fix(github): log res if no repository returned --- lib/modules/platform/github/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/modules/platform/github/index.ts b/lib/modules/platform/github/index.ts index ffc5f1ab893144..87a6f619f5fc81 100644 --- a/lib/modules/platform/github/index.ts +++ b/lib/modules/platform/github/index.ts @@ -393,6 +393,7 @@ export async function initRepo({ repo = res?.data?.repository; // istanbul ignore if if (!repo) { + logger.debug({ res }, 'No repository returned'); throw new Error(REPOSITORY_NOT_FOUND); } // istanbul ignore if