diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 02a98a4d978..291b96ba821 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,7 +34,7 @@ env: # are we on a release branch? DO_REALEASE: ${{ github.ref_name == github.event.repository.default_branch || github.ref_name == 'next' || startsWith(github.ref_name, 'maint/')}} NODE_VERSION: 22 - PDM_VERSION: 2.24.0 # renovate: datasource=pypi depName=pdm + PDM_VERSION: 2.24.1 # renovate: datasource=pypi depName=pdm DRY_RUN: true TEST_LEGACY_DECRYPTION: true SPARSE_CHECKOUT: |- @@ -605,7 +605,7 @@ jobs: os: ${{ runner.os }} - name: Setup PDM - uses: pdm-project/setup-pdm@deb8d8a4e2a03aabcef6f2cc981923fc6b29ef99 # v4.3 + uses: pdm-project/setup-pdm@94a823180e06fcde4ad29308721954a521c96ed0 # v4.4 with: python-version-file: .python-version version: ${{ env.PDM_VERSION }} diff --git a/.nvmrc b/.nvmrc index 7d41c735d71..b8ffd70759f 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -22.14.0 +22.15.0 diff --git a/lib/modules/datasource/npm/__snapshots__/index.spec.ts.snap b/lib/modules/datasource/npm/__snapshots__/index.spec.ts.snap index ebdec15fcca..c4526e97b83 100644 --- a/lib/modules/datasource/npm/__snapshots__/index.spec.ts.snap +++ b/lib/modules/datasource/npm/__snapshots__/index.spec.ts.snap @@ -30,7 +30,6 @@ exports[`modules/datasource/npm/index > should fetch package info from custom re exports[`modules/datasource/npm/index > should fetch package info from npm 1`] = ` { - "isPrivate": false, "registryUrl": "https://registry.npmjs.org", "releases": [ { diff --git a/lib/modules/datasource/npm/get.spec.ts b/lib/modules/datasource/npm/get.spec.ts index 1bf32e7eccd..8dc7ba8fdca 100644 --- a/lib/modules/datasource/npm/get.spec.ts +++ b/lib/modules/datasource/npm/get.spec.ts @@ -2,7 +2,8 @@ import { ExternalHostError } from '../../../types/errors/external-host-error'; import * as _packageCache from '../../../util/cache/package'; import * as hostRules from '../../../util/host-rules'; import { Http } from '../../../util/http'; -import { CACHE_REVISION, getDependency } from './get'; +import type { HttpResponse } from '../../../util/http/types'; +import { getDependency } from './get'; import { resolveRegistryUrl, setNpmrc } from './npmrc'; import * as httpMock from '~test/http-mock'; @@ -563,69 +564,96 @@ describe('modules/datasource/npm/get', () => { `); }); - it('discards cache with no revision', async () => { - setNpmrc('registry=https://test.org\n_authToken=XXX'); - - packageCache.get.mockResolvedValueOnce({ - some: 'result', - cacheData: { softExpireAt: '2099' }, - }); - - httpMock - .scope('https://test.org') - .get('/@neutrinojs%2Freact') - .reply(200, { - name: '@neutrinojs/react', + describe('cache', () => { + const httpResponse: HttpResponse = { + statusCode: 200, + body: { + name: 'test', + repository: { + type: 'git', + url: 'https://github.com/octocat/Hello-World/tree/master/packages/test', + directory: 'packages/foo', + }, versions: { '1.0.0': {} }, - }); - const registryUrl = resolveRegistryUrl('@neutrinojs/react'); - const dep = await getDependency(http, registryUrl, '@neutrinojs/react'); + 'dist-tags': { latest: '1.0.0' }, + }, + headers: { 'cache-control': 'max-age=180, public' }, + }; - expect(dep?.releases).toHaveLength(1); - }); + it('returns unexpired cache', async () => { + vi.setSystemTime('2024-06-15T00:14:59.999Z'); + packageCache.get.mockResolvedValue({ + etag: 'some-etag', + timestamp: '2024-06-15T00:00:00.000Z', + httpResponse, + }); - it('returns unexpired cache', async () => { - packageCache.get.mockResolvedValueOnce({ - some: 'result', - cacheData: { revision: CACHE_REVISION, softExpireAt: '2099' }, + const dep = await getDependency( + http, + 'https://example.com', + 'some-package', + ); + + expect(dep).toEqual({ + registryUrl: 'https://example.com', + releases: [{ version: '1.0.0' }], + sourceDirectory: 'packages/foo', + sourceUrl: + 'https://github.com/octocat/Hello-World/tree/master/packages/test', + tags: { latest: '1.0.0' }, + }); }); - const dep = await getDependency(http, 'https://some.url', 'some-package'); - expect(dep).toMatchObject({ some: 'result' }); - }); - it('returns soft expired cache if revalidated', async () => { - packageCache.get.mockResolvedValueOnce({ - some: 'result', - cacheData: { - revision: CACHE_REVISION, - softExpireAt: '2020', + it('returns soft expired cache if revalidated', async () => { + vi.setSystemTime('2024-06-15T00:15:00.000Z'); + packageCache.get.mockResolvedValue({ etag: 'some-etag', - }, + timestamp: '2024-06-15T00:00:00.000Z', + httpResponse, + }); + setNpmrc('registry=https://example.com\n_authToken=XXX'); + httpMock.scope('https://example.com').get('/some-package').reply(304); + + const dep = await getDependency( + http, + 'https://example.com', + 'some-package', + ); + + expect(dep).toEqual({ + registryUrl: 'https://example.com', + releases: [{ version: '1.0.0' }], + sourceDirectory: 'packages/foo', + sourceUrl: + 'https://github.com/octocat/Hello-World/tree/master/packages/test', + tags: { latest: '1.0.0' }, + }); }); - setNpmrc('registry=https://test.org\n_authToken=XXX'); - - httpMock.scope('https://test.org').get('/@neutrinojs%2Freact').reply(304); - const registryUrl = resolveRegistryUrl('@neutrinojs/react'); - const dep = await getDependency(http, registryUrl, '@neutrinojs/react'); - expect(dep).toMatchObject({ some: 'result' }); - }); - it('returns soft expired cache on npmjs error', async () => { - packageCache.get.mockResolvedValueOnce({ - some: 'result', - cacheData: { - revision: CACHE_REVISION, - softExpireAt: '2020', + it('returns soft expired cache on npmjs error', async () => { + vi.setSystemTime('2024-06-15T00:15:00.000Z'); + packageCache.get.mockResolvedValue({ etag: 'some-etag', - }, + timestamp: '2024-06-15T00:00:00.000Z', + httpResponse, + }); + setNpmrc('registry=https://example.com\n_authToken=XXX'); + httpMock.scope('https://example.com').get('/some-package').reply(500); + + const dep = await getDependency( + http, + 'https://example.com', + 'some-package', + ); + + expect(dep).toEqual({ + registryUrl: 'https://example.com', + releases: [{ version: '1.0.0' }], + sourceDirectory: 'packages/foo', + sourceUrl: + 'https://github.com/octocat/Hello-World/tree/master/packages/test', + tags: { latest: '1.0.0' }, + }); }); - - httpMock - .scope('https://registry.npmjs.org') - .get('/@neutrinojs%2Freact') - .reply(500); - const registryUrl = resolveRegistryUrl('@neutrinojs/react'); - const dep = await getDependency(http, registryUrl, '@neutrinojs/react'); - expect(dep).toMatchObject({ some: 'result' }); }); }); diff --git a/lib/modules/datasource/npm/get.ts b/lib/modules/datasource/npm/get.ts index 4bee6946abd..19353759423 100644 --- a/lib/modules/datasource/npm/get.ts +++ b/lib/modules/datasource/npm/get.ts @@ -1,23 +1,17 @@ -import url from 'node:url'; import is from '@sindresorhus/is'; -import { DateTime } from 'luxon'; import { z } from 'zod'; -import { GlobalConfig } from '../../../config/global'; import { HOST_DISABLED } from '../../../constants/error-messages'; import { logger } from '../../../logger'; import { ExternalHostError } from '../../../types/errors/external-host-error'; -import * as packageCache from '../../../util/cache/package'; import * as hostRules from '../../../util/host-rules'; import type { Http } from '../../../util/http'; +import { PackageHttpCacheProvider } from '../../../util/http/cache/package-http-cache-provider'; import type { HttpOptions } from '../../../util/http/types'; import { regEx } from '../../../util/regex'; -import { HttpCacheStats } from '../../../util/stats'; import { asTimestamp } from '../../../util/timestamp'; import { joinUrlParts } from '../../../util/url'; import type { Release, ReleaseResult } from '../types'; -import type { CachedReleaseResult, NpmResponse } from './types'; - -export const CACHE_REVISION = 1; +import type { NpmResponse } from './types'; const SHORT_REPO_REGEX = regEx( /^((?bitbucket|github|gitlab):)?(?[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)$/, @@ -81,53 +75,12 @@ export async function getDependency( const packageUrl = joinUrlParts(registryUrl, packageName.replace('/', '%2F')); - // Now check the persistent cache - const cacheNamespace = 'datasource-npm:data'; - const cachedResult = await packageCache.get( - cacheNamespace, - packageUrl, - ); - if (cachedResult?.cacheData) { - if (cachedResult.cacheData.revision === CACHE_REVISION) { - const softExpireAt = DateTime.fromISO( - cachedResult.cacheData.softExpireAt, - ); - if (softExpireAt.isValid && softExpireAt > DateTime.local()) { - logger.trace('Cached result is not expired - reusing'); - HttpCacheStats.incLocalHits(packageUrl); - delete cachedResult.cacheData; - return cachedResult; - } - - logger.trace('Cached result is soft expired'); - HttpCacheStats.incLocalMisses(packageUrl); - } else { - logger.trace( - `Package cache for npm package "${packageName}" is from an old revision - discarding`, - ); - delete cachedResult.cacheData; - } - } - const cacheMinutes = 15; - const softExpireAt = DateTime.local().plus({ minutes: cacheMinutes }).toISO(); - let cacheHardTtlMinutes = GlobalConfig.get('cacheHardTtlMinutes'); - if ( - !( - is.number(cacheHardTtlMinutes) && - /* istanbul ignore next: needs test */ cacheHardTtlMinutes > cacheMinutes - ) - ) { - cacheHardTtlMinutes = cacheMinutes; - } - - const uri = url.parse(packageUrl); - try { - const options: HttpOptions = {}; - if (cachedResult?.cacheData?.etag) { - logger.trace({ packageName }, 'Using cached etag'); - options.headers = { 'If-None-Match': cachedResult.cacheData.etag }; - } + const cacheProvider = new PackageHttpCacheProvider({ + namespace: 'datasource-npm:cache-provider', + checkAuthorizationHeader: false, + }); + const options: HttpOptions = { cacheProvider }; // set abortOnError for registry.npmjs.org if no hostRule with explicit abortOnError exists if ( @@ -145,23 +98,8 @@ export async function getDependency( }); } - const raw = await http.getJsonUnchecked(packageUrl, options); - if (cachedResult?.cacheData && raw.statusCode === 304) { - logger.trace(`Cached npm result for ${packageName} is revalidated`); - HttpCacheStats.incRemoteHits(packageUrl); - cachedResult.cacheData.softExpireAt = softExpireAt; - await packageCache.set( - cacheNamespace, - packageUrl, - cachedResult, - cacheHardTtlMinutes, - ); - delete cachedResult.cacheData; - return cachedResult; - } - HttpCacheStats.incRemoteMisses(packageUrl); - const etag = raw.headers.etag; - const res = raw.body; + const resp = await http.getJsonUnchecked(packageUrl, options); + const { body: res } = resp; if (!res.versions || !Object.keys(res.versions).length) { // Registry returned a 200 OK but with no versions logger.debug(`No versions returned for npm dependency ${packageName}`); @@ -226,25 +164,16 @@ export async function getDependency( } return release; }); - logger.trace({ dep }, 'dep'); - const cacheControl = raw.headers?.['cache-control']; - if ( - is.nonEmptyString(cacheControl) && - regEx(/(^|,)\s*public\s*(,|$)/).test(cacheControl) - ) { - dep.isPrivate = false; - const cacheData = { revision: CACHE_REVISION, softExpireAt, etag }; - await packageCache.set( - cacheNamespace, - packageUrl, - { ...dep, cacheData }, - etag - ? /* istanbul ignore next: needs test */ cacheHardTtlMinutes - : cacheMinutes, - ); - } else { + + const isPublic = resp.headers?.['cache-control'] + ?.toLocaleLowerCase() + ?.split(regEx(/\s*,\s*/)) + ?.includes('public'); + if (!isPublic) { dep.isPrivate = true; } + + logger.trace({ dep }, 'dep'); return dep; } catch (err) { const actualError = err instanceof ExternalHostError ? err.err : err; @@ -259,15 +188,6 @@ export async function getDependency( } if (err instanceof ExternalHostError) { - if (cachedResult) { - logger.warn( - { err, host: uri.host }, - `npm host error, reusing expired cached result instead`, - ); - delete cachedResult.cacheData; - return cachedResult; - } - if (actualError.name === 'ParseError' && actualError.body) { actualError.body = 'err.body deleted by Renovate'; err.err = actualError; diff --git a/lib/modules/datasource/npm/index.spec.ts b/lib/modules/datasource/npm/index.spec.ts index bdf408555de..3de47a006ac 100644 --- a/lib/modules/datasource/npm/index.spec.ts +++ b/lib/modules/datasource/npm/index.spec.ts @@ -55,10 +55,10 @@ describe('modules/datasource/npm/index', () => { httpMock .scope('https://registry.npmjs.org') .get('/foobar') - .reply(200, npmResponse, { 'Cache-control': 'public, expires=300' }); + .reply(200, npmResponse, { 'Cache-Control': 'public, expires=300' }); const res = await getPkgReleases({ datasource, packageName: 'foobar' }); expect(res).toMatchSnapshot(); - expect(res?.isPrivate).toBeFalse(); + expect(res?.isPrivate).toBeUndefined(); }); it('should parse repo url', async () => { diff --git a/lib/modules/datasource/npm/types.ts b/lib/modules/datasource/npm/types.ts index 2cd011d84bc..d07b93bf15e 100644 --- a/lib/modules/datasource/npm/types.ts +++ b/lib/modules/datasource/npm/types.ts @@ -1,6 +1,5 @@ import type { PackageRule } from '../../../config/types'; import type { HostRule } from '../../../types'; -import type { ReleaseResult } from '../types'; export interface NpmrcRules { hostRules: HostRule[]; @@ -33,12 +32,4 @@ export interface NpmResponse { 'dist-tags'?: Record; } -export interface CachedReleaseResult extends ReleaseResult { - cacheData?: { - revision?: number; - etag: string | undefined; - softExpireAt: string; - }; -} - export type Npmrc = Record; diff --git a/lib/modules/manager/api.ts b/lib/modules/manager/api.ts index 53325aa09e6..682908beb5a 100644 --- a/lib/modules/manager/api.ts +++ b/lib/modules/manager/api.ts @@ -85,7 +85,6 @@ import * as preCommit from './pre-commit'; import * as pub from './pub'; import * as puppet from './puppet'; import * as pyenv from './pyenv'; -import * as renovateConfigPresets from './renovate-config-presets'; import * as rubyVersion from './ruby-version'; import * as runtimeVersion from './runtime-version'; import * as sbt from './sbt'; @@ -195,7 +194,6 @@ api.set('pre-commit', preCommit); api.set('pub', pub); api.set('puppet', puppet); api.set('pyenv', pyenv); -api.set('renovate-config-presets', renovateConfigPresets); api.set('ruby-version', rubyVersion); api.set('runtime-version', runtimeVersion); api.set('sbt', sbt); diff --git a/lib/modules/manager/nuget/__snapshots__/extract.spec.ts.snap b/lib/modules/manager/nuget/__snapshots__/extract.spec.ts.snap index 3fe1319db60..d5e6f9a56e9 100644 --- a/lib/modules/manager/nuget/__snapshots__/extract.spec.ts.snap +++ b/lib/modules/manager/nuget/__snapshots__/extract.spec.ts.snap @@ -15,56 +15,56 @@ exports[`modules/manager/nuget/extract > extractPackageFile() > extracts all dep "depType": "nuget", }, { - "currentValue": "undefined", + "currentValue": undefined, "datasource": "nuget", "depName": "NotUpdatable3", "depType": "nuget", "skipReason": "invalid-version", }, { + "currentValue": "[1.2.3, 3.2.1)", "datasource": "nuget", "depName": "NotUpdatable3", "depType": "nuget", - "skipReason": "invalid-version", }, { + "currentValue": "[1.2.3, 3.2.1]", "datasource": "nuget", "depName": "NotUpdatable3", "depType": "nuget", - "skipReason": "invalid-version", }, { + "currentValue": "(1.2.3, 3.2.1)", "datasource": "nuget", "depName": "NotUpdatable3", "depType": "nuget", - "skipReason": "invalid-version", }, { + "currentValue": "(1.2.3,)", "datasource": "nuget", "depName": "NotUpdatable2", "depType": "nuget", - "skipReason": "invalid-version", }, { + "currentValue": "[,1.2.3)", "datasource": "nuget", "depName": "NotUpdatable1", "depType": "nuget", - "skipReason": "invalid-version", }, { - "currentValue": "1.2.3", + "currentValue": "[1.2.3,)", "datasource": "nuget", "depName": "Range3", "depType": "nuget", }, { - "currentValue": "1.2.3", + "currentValue": "[1.2.3,]", "datasource": "nuget", "depName": "Range2", "depType": "nuget", }, { - "currentValue": "1.2.3", + "currentValue": "[1.2.3]", "datasource": "nuget", "depName": "Range1", "depType": "nuget", @@ -124,10 +124,11 @@ exports[`modules/manager/nuget/extract > extractPackageFile() > extracts all dep "depType": "nuget", }, { + "currentValue": "$(UnknownVariable)", "datasource": "nuget", "depName": "Microsoft.AspNetCore.Hosting", "depType": "nuget", - "skipReason": "invalid-version", + "skipReason": "contains-variable", }, { "currentValue": "4.5.0", @@ -155,49 +156,49 @@ exports[`modules/manager/nuget/extract > extractPackageFile() > extracts all dep "depType": "nuget", }, { + "currentValue": "[1.2.3, 3.2.1)", "datasource": "nuget", "depName": "NotUpdatable3", "depType": "nuget", - "skipReason": "invalid-version", }, { + "currentValue": "[1.2.3, 3.2.1]", "datasource": "nuget", "depName": "NotUpdatable3", "depType": "nuget", - "skipReason": "invalid-version", }, { + "currentValue": "(1.2.3, 3.2.1)", "datasource": "nuget", "depName": "NotUpdatable3", "depType": "nuget", - "skipReason": "invalid-version", }, { + "currentValue": "(1.2.3,)", "datasource": "nuget", "depName": "NotUpdatable2", "depType": "nuget", - "skipReason": "invalid-version", }, { + "currentValue": "[,1.2.3)", "datasource": "nuget", "depName": "NotUpdatable1", "depType": "nuget", - "skipReason": "invalid-version", }, { - "currentValue": "1.2.3", + "currentValue": "[1.2.3,)", "datasource": "nuget", "depName": "Range3", "depType": "nuget", }, { - "currentValue": "1.2.3", + "currentValue": "[1.2.3,]", "datasource": "nuget", "depName": "Range2", "depType": "nuget", }, { - "currentValue": "1.2.3", + "currentValue": "[1.2.3]", "datasource": "nuget", "depName": "Range1", "depType": "nuget", diff --git a/lib/modules/manager/nuget/extract.ts b/lib/modules/manager/nuget/extract.ts index d974b9d5f0e..1de38b12264 100644 --- a/lib/modules/manager/nuget/extract.ts +++ b/lib/modules/manager/nuget/extract.ts @@ -3,7 +3,6 @@ import type { XmlNode } from 'xmldoc'; import { XmlDocument, XmlElement } from 'xmldoc'; import { logger } from '../../../logger'; import { getSiblingFileName, localPathExists } from '../../../util/fs'; -import { regEx } from '../../../util/regex'; import { NugetDatasource } from '../../datasource/nuget'; import { getDep } from '../dockerfile/extract'; import type { @@ -20,15 +19,7 @@ import { applyRegistries, findVersion, getConfiguredRegistries } from './util'; * This article mentions that Nuget 3.x and later tries to restore the lowest possible version * regarding to given version range. * 1.3.4 equals [1.3.4,) - * Due to guarantee that an update of package version will result in its usage by the next restore + build operation, - * only following constrained versions make sense - * 1.3.4, [1.3.4], [1.3.4, ], [1.3.4, ) - * The update of the right boundary does not make sense regarding to the lowest version restore rule, - * so we don't include it in the extracting regexp */ -const checkVersion = regEx( - /^\s*(?:[[])?(?:(?[^"(,[\]]+)\s*(?:,\s*[)\]]|])?)\s*$/, -); const elemNames = new Set([ 'PackageReference', 'PackageVersion', @@ -83,28 +74,24 @@ function extractDepsFromXml(xmlNode: XmlDocument): NugetPackageDependency[] { currentValue = currentValue ?.trim() ?.replace(/^\$\((\w+)\)$/, (match, key) => { + sharedVariableName = key; const val = vars.get(key); if (val) { - sharedVariableName = key; return val; } return match; }); if (sharedVariableName) { - dep.sharedVariableName = sharedVariableName; - } - - currentValue = checkVersion - .exec(currentValue) - ?.groups?.currentValue?.trim(); - - if (currentValue) { - dep.currentValue = currentValue; - } else { - dep.skipReason = 'invalid-version'; + if (currentValue === `$(${sharedVariableName})`) { + // this means that be failed to find/replace the variable + dep.skipReason = 'contains-variable'; + } else { + dep.sharedVariableName = sharedVariableName; + } } + dep.currentValue = currentValue; results.push(dep); } else if (name === 'Sdk') { const depName = attr?.Name; diff --git a/lib/modules/manager/nuget/readme.md b/lib/modules/manager/nuget/readme.md index e3608effb8b..a1cd09f47cc 100644 --- a/lib/modules/manager/nuget/readme.md +++ b/lib/modules/manager/nuget/readme.md @@ -19,3 +19,23 @@ For Renovate to work with .NET Framework projects, you need to update these file - `.vbproj` - `.props` - `.targets` + +### Disabling updates for pinned versions + +In Nuget, when you use versions like `Version="1.2.3"` then it means "1.2.3 or greater, up to v2" +When you use versions like `Version="[1.2.3]"` then it means "exactly 1.2.3". + +If you would like Renovate to disable updating of exact versions (warning: you might end up years out of date and not realize it) then here is an example configuration to achieve that: + +```json +{ + "packageRules": [ + { + "description": "Skip pinned versions", + "matchManagers": ["nuget"], + "matchCurrentValue": "/^\\[[^,]+\\]$/", + "enabled": false + } + ] +} +``` diff --git a/lib/modules/manager/renovate-config-presets/extract.spec.ts b/lib/modules/manager/renovate-config-presets/extract.spec.ts deleted file mode 100644 index 62b8ca90ad9..00000000000 --- a/lib/modules/manager/renovate-config-presets/extract.spec.ts +++ /dev/null @@ -1,259 +0,0 @@ -import { codeBlock } from 'common-tags'; -import { extractPackageFile } from '.'; - -describe('modules/manager/renovate-config-presets/extract', () => { - describe('extractPackageFile()', () => { - it('returns null for empty file', () => { - expect(extractPackageFile('', 'renovate.json')).toBeNull(); - }); - - it('returns null for invalid file', () => { - expect( - extractPackageFile('this-is-not-json-object', 'renovate.json'), - ).toBeNull(); - }); - - it('returns null for a config file without presets', () => { - expect( - extractPackageFile( - codeBlock` - { - "draftPR": true - } - `, - 'renovate.json', - ), - ).toBeNull(); - }); - - it('returns null for a config file only contains built-in presets', () => { - expect( - extractPackageFile( - codeBlock` - { - "extends": ["config:recommended", ":label(test)", "helpers:pinGitHubActionDigests"] - } - `, - 'renovate.json', - ), - ).toBeNull(); - }); - - it('provides skipReason for unsupported preset sources', () => { - expect( - extractPackageFile( - codeBlock` - { - "extends": [ - "fastcore", - "http://my.server/users/me/repos/renovate-presets/raw/default.json", - "local>renovate/presets", - "local>renovate/presets2#1.2.3" - ] - } - `, - 'renovate.json', - ), - ).toEqual({ - deps: [ - { - depName: 'renovate-config-fastcore', - skipReason: 'unsupported-datasource', - }, - { - depName: - 'http://my.server/users/me/repos/renovate-presets/raw/default.json', - skipReason: 'unsupported-datasource', - }, - { - depName: 'renovate/presets', - skipReason: 'unsupported-datasource', - }, - { - depName: 'renovate/presets2', - skipReason: 'unsupported-datasource', - }, - ], - }); - }); - - it('provides skipReason for presets without versions', () => { - expect( - extractPackageFile( - codeBlock` - { - "extends": [ - "github>abc/foo", - "gitlab>abc/bar:xyz", - "gitea>cde/foo//path/xyz" - ] - } - `, - 'renovate.json', - ), - ).toEqual({ - deps: [ - { - depName: 'abc/foo', - skipReason: 'unspecified-version', - }, - { - depName: 'abc/bar', - skipReason: 'unspecified-version', - }, - { - depName: 'cde/foo', - skipReason: 'unspecified-version', - }, - ], - }); - }); - - it('extracts from a config file with GitHub hosted presets', () => { - expect( - extractPackageFile( - codeBlock` - { - "extends": [ - "github>abc/foo#1.2.3", - "github>abc/bar:xyz#1.2.3", - "github>cde/foo//path/xyz#1.2.3", - "github>cde/bar:xyz/sub#1.2.3" - ] - } - `, - 'renovate.json', - ), - ).toEqual({ - deps: [ - { - datasource: 'github-tags', - depName: 'abc/foo', - currentValue: '1.2.3', - }, - { - datasource: 'github-tags', - depName: 'abc/bar', - currentValue: '1.2.3', - }, - { - datasource: 'github-tags', - depName: 'cde/foo', - currentValue: '1.2.3', - }, - { - datasource: 'github-tags', - depName: 'cde/bar', - currentValue: '1.2.3', - }, - ], - }); - }); - - it('extracts from a config file with GitLab hosted presets', () => { - expect( - extractPackageFile( - codeBlock` - { - "extends": [ - "gitlab>abc/foo#1.2.3", - "gitlab>abc/bar:xyz#1.2.3", - "gitlab>cde/foo//path/xyz#1.2.3", - "gitlab>cde/bar:xyz/sub#1.2.3" - ] - } - `, - 'renovate.json', - ), - ).toEqual({ - deps: [ - { - datasource: 'gitlab-tags', - depName: 'abc/foo', - currentValue: '1.2.3', - }, - { - datasource: 'gitlab-tags', - depName: 'abc/bar', - currentValue: '1.2.3', - }, - { - datasource: 'gitlab-tags', - depName: 'cde/foo', - currentValue: '1.2.3', - }, - { - datasource: 'gitlab-tags', - depName: 'cde/bar', - currentValue: '1.2.3', - }, - ], - }); - }); - - it('extracts from a config file with Gitea hosted presets', () => { - expect( - extractPackageFile( - codeBlock` - { - "extends": [ - "gitea>abc/foo#1.2.3", - "gitea>abc/bar:xyz#1.2.3", - "gitea>cde/foo//path/xyz#1.2.3", - "gitea>cde/bar:xyz/sub#1.2.3" - ] - } - `, - 'renovate.json', - ), - ).toEqual({ - deps: [ - { - datasource: 'gitea-tags', - depName: 'abc/foo', - currentValue: '1.2.3', - }, - { - datasource: 'gitea-tags', - depName: 'abc/bar', - currentValue: '1.2.3', - }, - { - datasource: 'gitea-tags', - depName: 'cde/foo', - currentValue: '1.2.3', - }, - { - datasource: 'gitea-tags', - depName: 'cde/bar', - currentValue: '1.2.3', - }, - ], - }); - }); - - it('supports JSON5', () => { - expect( - extractPackageFile( - codeBlock` - { - // comments are permitted - "extends": [ - "github>abc/foo#1.2.3", - ], - } - `, - 'renovate.json5', - ), - ).toEqual({ - deps: [ - { - datasource: 'github-tags', - depName: 'abc/foo', - currentValue: '1.2.3', - }, - ], - }); - }); - }); -}); diff --git a/lib/modules/manager/renovate-config-presets/extract.ts b/lib/modules/manager/renovate-config-presets/extract.ts deleted file mode 100644 index aaa80d4a51d..00000000000 --- a/lib/modules/manager/renovate-config-presets/extract.ts +++ /dev/null @@ -1,59 +0,0 @@ -import is from '@sindresorhus/is'; -import { parsePreset } from '../../../config/presets/parse'; -import { logger } from '../../../logger'; -import { GiteaTagsDatasource } from '../../datasource/gitea-tags'; -import { GithubTagsDatasource } from '../../datasource/github-tags'; -import { GitlabTagsDatasource } from '../../datasource/gitlab-tags'; -import type { PackageDependency, PackageFileContent } from '../types'; -import { RenovateJsonSchema } from './schema'; - -const supportedPresetSources: Record = { - github: GithubTagsDatasource.id, - gitlab: GitlabTagsDatasource.id, - gitea: GiteaTagsDatasource.id, -}; - -export function extractPackageFile( - content: string, - packageFile: string, -): PackageFileContent | null { - logger.trace(`renovate-config-presets.extractPackageFile(${packageFile})`); - const config = RenovateJsonSchema.safeParse(content); - if (!config.success) { - logger.debug({ packageFile, err: config.error }, 'Invalid Renovate Config'); - return null; - } - - const deps: PackageDependency[] = []; - - for (const preset of config.data.extends) { - const parsedPreset = parsePreset(preset); - const datasource = supportedPresetSources[parsedPreset.presetSource]; - - if (is.nullOrUndefined(datasource)) { - if (parsedPreset.presetSource !== 'internal') { - deps.push({ - depName: parsedPreset.repo, - skipReason: 'unsupported-datasource', - }); - } - continue; - } - - if (is.nullOrUndefined(parsedPreset.tag)) { - deps.push({ - depName: parsedPreset.repo, - skipReason: 'unspecified-version', - }); - continue; - } - - deps.push({ - depName: parsedPreset.repo, - datasource, - currentValue: parsedPreset.tag, - }); - } - - return is.nonEmptyArray(deps) ? { deps } : null; -} diff --git a/lib/modules/manager/renovate-config-presets/index.ts b/lib/modules/manager/renovate-config-presets/index.ts deleted file mode 100644 index 3b4c1bd7310..00000000000 --- a/lib/modules/manager/renovate-config-presets/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { configFileNames } from '../../../config/app-strings'; -import { GiteaTagsDatasource } from '../../datasource/gitea-tags'; -import { GithubTagsDatasource } from '../../datasource/github-tags'; -import { GitlabTagsDatasource } from '../../datasource/gitlab-tags'; - -export { extractPackageFile } from './extract'; - -export const url = '../../../config-presets.md'; - -export const defaultConfig = { - fileMatch: configFileNames - .filter((name) => name !== 'package.json') - .map((name) => `^${name.replaceAll('.', '\\.')}$`), -}; - -export const supportedDatasources = [ - GithubTagsDatasource.id, - GitlabTagsDatasource.id, - GiteaTagsDatasource.id, -]; diff --git a/lib/modules/manager/renovate-config-presets/readme.md b/lib/modules/manager/renovate-config-presets/readme.md deleted file mode 100644 index a3fe04c8fe9..00000000000 --- a/lib/modules/manager/renovate-config-presets/readme.md +++ /dev/null @@ -1,21 +0,0 @@ -Renovate supports updating [Shareable Config Presets](../../../config-presets.md) for Renovate configuration. - -The preset versions are only updated if the version is already pinned. -For example, `github>user/renovate-config#1.2.3` will be updated to `github>user/renovate-config#1.2.4` if the `1.2.4` version is available, but `github>user/renovate-config` will not be pinned. - -```json -{ - "extends": [ - "github>user/renovate-config#1.2.3", - "github>user/renovate-config:group" - ] -} -``` - -### Unsupported Config - -- [Local presets](../../../config-presets.md#local-presets) -- [HTTP URLs presets](../../../config-presets.md#fetching-presets-from-an-http-server) -- [`package.json` file config](../../../configuration-options.md) (deprecated) -- [`npm` hosted presets](../../../config-presets.md#npm-hosted-presets) (deprecated) -- `extends` inside sub objects, like `packageRules` diff --git a/lib/modules/manager/renovate-config-presets/schema.ts b/lib/modules/manager/renovate-config-presets/schema.ts deleted file mode 100644 index be1dfbabead..00000000000 --- a/lib/modules/manager/renovate-config-presets/schema.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { z } from 'zod'; -import { Json5 } from '../../../util/schema-utils'; - -export const RenovateJsonSchema = Json5.pipe( - z.object({ - extends: z.array(z.string()), - }), -); diff --git a/lib/util/cache/package/types.ts b/lib/util/cache/package/types.ts index b99b3fc067e..819ec3400d0 100644 --- a/lib/util/cache/package/types.ts +++ b/lib/util/cache/package/types.ts @@ -85,7 +85,7 @@ export type PackageCacheNamespace = | 'datasource-maven:head-requests' | 'datasource-maven:metadata-xml' | 'datasource-node-version' - | 'datasource-npm:data' + | 'datasource-npm:cache-provider' | 'datasource-nuget-v3' | 'datasource-orb' | 'datasource-packagist' diff --git a/lib/workers/repository/updates/__snapshots__/generate.spec.ts.snap b/lib/workers/repository/updates/__snapshots__/generate.spec.ts.snap index ce17e9898aa..37b0524702d 100644 --- a/lib/workers/repository/updates/__snapshots__/generate.spec.ts.snap +++ b/lib/workers/repository/updates/__snapshots__/generate.spec.ts.snap @@ -26,6 +26,7 @@ exports[`workers/repository/updates/generate > generateBranchConfig() > handles "prettyDepType": "dependency", "recreateClosed": false, "releaseTimestamp": undefined, + "skipInstalls": true, "upgrades": [ { "branchName": "some-branch", @@ -114,6 +115,7 @@ exports[`workers/repository/updates/generate > generateBranchConfig() > handles "prettyDepType": "dependency", "recreateClosed": false, "releaseTimestamp": undefined, + "skipInstalls": true, "upgrades": [ { "branchName": "some-branch", @@ -192,6 +194,7 @@ exports[`workers/repository/updates/generate > generateBranchConfig() > handles "prettyDepType": "dependency", "recreateClosed": true, "releaseTimestamp": undefined, + "skipInstalls": true, "upgrades": [ { "branchName": "some-branch", @@ -237,6 +240,7 @@ exports[`workers/repository/updates/generate > generateBranchConfig() > handles "prettyNewVersion": "v1.0.1", "recreateClosed": false, "releaseTimestamp": undefined, + "skipInstalls": true, "upgrades": [ { "branchName": "some-branch", diff --git a/lib/workers/repository/updates/generate.ts b/lib/workers/repository/updates/generate.ts index 200a487ccf0..c310514ec98 100644 --- a/lib/workers/repository/updates/generate.ts +++ b/lib/workers/repository/updates/generate.ts @@ -459,6 +459,11 @@ export function generateBranchConfig( } } + // Set skipInstalls to false if any upgrade in the branch has it false + config.skipInstalls = config.upgrades.every( + (upgrade) => upgrade.skipInstalls !== false, + ); + const tableRows = config.upgrades .map(getTableValues) .filter((x): x is string[] => is.array(x, is.string)); diff --git a/package.json b/package.json index a62e5629526..6260d343cae 100644 --- a/package.json +++ b/package.json @@ -141,7 +141,7 @@ "pnpm": "^10.0.0" }, "volta": { - "node": "22.14.0", + "node": "22.15.0", "pnpm": "10.8.1" }, "dependencies": { @@ -348,7 +348,7 @@ "typescript": "5.8.3", "typescript-eslint": "8.30.1", "unified": "9.2.2", - "vite": "6.2.6", + "vite": "6.3.0", "vite-tsconfig-paths": "5.1.4", "vitest": "3.1.1", "vitest-mock-extended": "3.1.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3dc2a21981a..c8c1edfc287 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -606,11 +606,11 @@ importers: specifier: 9.2.2 version: 9.2.2 vite: - specifier: 6.2.6 - version: 6.2.6(@types/node@22.14.1)(tsx@4.19.3)(yaml@2.7.1) + specifier: 6.3.0 + version: 6.3.0(@types/node@22.14.1)(tsx@4.19.3)(yaml@2.7.1) vite-tsconfig-paths: specifier: 5.1.4 - version: 5.1.4(typescript@5.8.3)(vite@6.2.6(@types/node@22.14.1)(tsx@4.19.3)(yaml@2.7.1)) + version: 5.1.4(typescript@5.8.3)(vite@6.3.0(@types/node@22.14.1)(tsx@4.19.3)(yaml@2.7.1)) vitest: specifier: 3.1.1 version: 3.1.1(@types/debug@4.1.12)(@types/node@22.14.1)(jsdom@26.1.0)(tsx@4.19.3)(yaml@2.7.1) @@ -6293,8 +6293,8 @@ packages: vite: optional: true - vite@6.2.6: - resolution: {integrity: sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==} + vite@6.3.0: + resolution: {integrity: sha512-9aC0n4pr6hIbvi1YOpFjwQ+QOTGssvbJKoeYkuHHGWwlXfdxQlI8L2qNMo9awEEcCPSiS+5mJZk5jH1PAqoDeQ==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: @@ -9160,13 +9160,13 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@3.1.1(vite@6.2.6(@types/node@22.14.1)(tsx@4.19.3)(yaml@2.7.1))': + '@vitest/mocker@3.1.1(vite@6.3.0(@types/node@22.14.1)(tsx@4.19.3)(yaml@2.7.1))': dependencies: '@vitest/spy': 3.1.1 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 6.2.6(@types/node@22.14.1)(tsx@4.19.3)(yaml@2.7.1) + vite: 6.3.0(@types/node@22.14.1)(tsx@4.19.3)(yaml@2.7.1) '@vitest/pretty-format@3.1.1': dependencies: @@ -13505,7 +13505,7 @@ snapshots: debug: 4.4.0 es-module-lexer: 1.6.0 pathe: 2.0.3 - vite: 6.2.6(@types/node@22.14.1)(tsx@4.19.3)(yaml@2.7.1) + vite: 6.3.0(@types/node@22.14.1)(tsx@4.19.3)(yaml@2.7.1) transitivePeerDependencies: - '@types/node' - jiti @@ -13520,22 +13520,25 @@ snapshots: - tsx - yaml - vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@6.2.6(@types/node@22.14.1)(tsx@4.19.3)(yaml@2.7.1)): + vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@6.3.0(@types/node@22.14.1)(tsx@4.19.3)(yaml@2.7.1)): dependencies: debug: 4.4.0 globrex: 0.1.2 tsconfck: 3.1.5(typescript@5.8.3) optionalDependencies: - vite: 6.2.6(@types/node@22.14.1)(tsx@4.19.3)(yaml@2.7.1) + vite: 6.3.0(@types/node@22.14.1)(tsx@4.19.3)(yaml@2.7.1) transitivePeerDependencies: - supports-color - typescript - vite@6.2.6(@types/node@22.14.1)(tsx@4.19.3)(yaml@2.7.1): + vite@6.3.0(@types/node@22.14.1)(tsx@4.19.3)(yaml@2.7.1): dependencies: esbuild: 0.25.2 + fdir: 6.4.4(picomatch@4.0.2) + picomatch: 4.0.2 postcss: 8.5.3 rollup: 4.40.0 + tinyglobby: 0.2.13 optionalDependencies: '@types/node': 22.14.1 fsevents: 2.3.3 @@ -13551,7 +13554,7 @@ snapshots: vitest@3.1.1(@types/debug@4.1.12)(@types/node@22.14.1)(jsdom@26.1.0)(tsx@4.19.3)(yaml@2.7.1): dependencies: '@vitest/expect': 3.1.1 - '@vitest/mocker': 3.1.1(vite@6.2.6(@types/node@22.14.1)(tsx@4.19.3)(yaml@2.7.1)) + '@vitest/mocker': 3.1.1(vite@6.3.0(@types/node@22.14.1)(tsx@4.19.3)(yaml@2.7.1)) '@vitest/pretty-format': 3.1.1 '@vitest/runner': 3.1.1 '@vitest/snapshot': 3.1.1 @@ -13567,7 +13570,7 @@ snapshots: tinyexec: 0.3.2 tinypool: 1.0.2 tinyrainbow: 2.0.0 - vite: 6.2.6(@types/node@22.14.1)(tsx@4.19.3)(yaml@2.7.1) + vite: 6.3.0(@types/node@22.14.1)(tsx@4.19.3)(yaml@2.7.1) vite-node: 3.1.1(@types/node@22.14.1)(tsx@4.19.3)(yaml@2.7.1) why-is-node-running: 2.3.0 optionalDependencies: diff --git a/tools/docker/Dockerfile b/tools/docker/Dockerfile index e98ae30c8a4..623d1300916 100644 --- a/tools/docker/Dockerfile +++ b/tools/docker/Dockerfile @@ -5,23 +5,23 @@ ARG BASE_IMAGE_TYPE=slim # -------------------------------------- # slim image # -------------------------------------- -FROM ghcr.io/renovatebot/base-image:9.60.6@sha256:8e7b476e6b508ffd08d74632b3f6b64ca4118cc3b457871b00cc5f0069ca44fb AS slim-base +FROM ghcr.io/renovatebot/base-image:9.61.0@sha256:938341cddf2d8f8721524a1426aaf9dcce50d4e7763281788f71695b790fd230 AS slim-base # -------------------------------------- # full image # -------------------------------------- -FROM ghcr.io/renovatebot/base-image:9.60.6-full@sha256:b97f67869b56b614f015de8bb7adb587b8cbf10d86dcffa5dd04d0213441a682 AS full-base +FROM ghcr.io/renovatebot/base-image:9.61.0-full@sha256:6c0c8bb6f75e8c776baffb4d56e8bb7e6a4cf32d255396bcbd69a5f007c34b6c AS full-base ENV RENOVATE_BINARY_SOURCE=global # -------------------------------------- # build image # -------------------------------------- -FROM --platform=$BUILDPLATFORM ghcr.io/renovatebot/base-image:9.60.6@sha256:8e7b476e6b508ffd08d74632b3f6b64ca4118cc3b457871b00cc5f0069ca44fb AS build +FROM --platform=$BUILDPLATFORM ghcr.io/renovatebot/base-image:9.61.0@sha256:938341cddf2d8f8721524a1426aaf9dcce50d4e7763281788f71695b790fd230 AS build # We want a specific node version here # renovate: datasource=github-releases packageName=containerbase/node-prebuild versioning=node -RUN install-tool node 22.14.0 +RUN install-tool node 22.15.0 # corepack is too buggy 😞 # renovate: datasource=npm