diff --git a/docs/development/adding-a-package-manager.md b/docs/development/adding-a-package-manager.md index 8519d90b5a6e01..1e49db4a614de2 100644 --- a/docs/development/adding-a-package-manager.md +++ b/docs/development/adding-a-package-manager.md @@ -22,6 +22,7 @@ The manager's `index.ts` file supports the following values or functions: | `extractAllPackageFiles` | yes | yes | | `getRangeStrategy` | yes | | | `categories` | yes | | +| `preflight` | yes | yes | | `supportsLockFileMaintenance` | yes | | | `updateArtifacts` | yes | yes | | `updateDependency` | yes | | @@ -82,6 +83,16 @@ The `npm` manager uses the `getRangeStrategy` function to pin `devDependencies` If left undefined, then a default `getRangeStrategy` will be used that always returns "replace". +### `categories` (optional) + +Use `categories` to define the categories of dependencies that the manager supports. +These usually are describing the purpose of the managed dependencies like `iac` for Infrastructure as Code or ecosystems like `js` for Javascript. + +### `preflight` (optional) + +Use `preflight` to modify Renovates config based on the repository content. +For example, if a repository contains files with `.tofu` ending, then change the `filematch` and change the registry. + ### `supportsLockFileMaintenance` (optional) Set to `true` if this package manager needs to update lock files in addition to package files. diff --git a/lib/modules/manager/types.ts b/lib/modules/manager/types.ts index 4cd752def355a6..b342b122913874 100644 --- a/lib/modules/manager/types.ts +++ b/lib/modules/manager/types.ts @@ -1,6 +1,7 @@ import type { ReleaseType } from 'semver'; import type { MatchStringsStrategy, + RenovateConfig, UpdateType, UserEnv, ValidationMessage, @@ -285,6 +286,8 @@ export interface ManagerApi extends ModuleApi { updateLockedDependency?( config: UpdateLockedConfig, ): Result; + + preflight?(config: RenovateConfig): Result; } // TODO: name and properties used by npm manager diff --git a/lib/workers/repository/extract/index.spec.ts b/lib/workers/repository/extract/index.spec.ts index f769a214198a76..77b58f1f3b4c32 100644 --- a/lib/workers/repository/extract/index.spec.ts +++ b/lib/workers/repository/extract/index.spec.ts @@ -2,9 +2,10 @@ import { mocked, partial, scm } from '../../../../test/util'; import { getConfig } from '../../../config/defaults'; import type { RenovateConfig } from '../../../config/types'; import { logger } from '../../../logger'; +import * as managers from '../../../modules/manager'; import type { PackageFile } from '../../../modules/manager/types'; import * as _managerFiles from './manager-files'; -import { extractAllDependencies } from '.'; +import { applyPreFlights, extractAllDependencies } from '.'; jest.mock('./manager-files'); jest.mock('../../../util/git'); @@ -68,4 +69,76 @@ describe('workers/repository/extract/index', () => { expect(Object.keys(res.packageFiles)).toContain('regex'); }); }); + + describe('applyPreFlights()', () => { + const getFunction = jest.spyOn(managers, 'get'); + const baseConfig: RenovateConfig = { + foo: 'bar', + }; + + it('should return same config for unknown manager', async () => { + await expect(applyPreFlights(baseConfig, ['test'])).resolves.toEqual({ + foo: 'bar', + }); + }); + + it('should return modified config for manager', async () => { + getFunction.mockReturnValueOnce((config: RenovateConfig) => { + return { + ...config, + foo: 'foo', + }; + }); + + await expect(applyPreFlights(baseConfig, ['test'])).resolves.toEqual({ + foo: 'foo', + }); + }); + + it('should return modified config for multiple managers', async () => { + getFunction.mockReturnValueOnce((config: RenovateConfig) => { + return { + ...config, + foo: 'foo', + }; + }); + getFunction.mockReturnValueOnce((config: RenovateConfig) => { + return { + ...config, + lip: 'sum', + foo: 'bar', + }; + }); + + await expect( + applyPreFlights(baseConfig, ['test', 'another-manager']), + ).resolves.toEqual({ + foo: 'bar', + lip: 'sum', + }); + }); + + it('should return modified config for async preflights managers', async () => { + getFunction.mockReturnValueOnce((config: RenovateConfig) => { + return { + ...config, + foo: 'foo', + }; + }); + getFunction.mockReturnValueOnce((config: RenovateConfig) => { + return Promise.resolve({ + ...config, + lip: 'sum', + foo: 'bar', + }); + }); + + await expect( + applyPreFlights(baseConfig, ['test', 'another-manager']), + ).resolves.toEqual({ + foo: 'bar', + lip: 'sum', + }); + }); + }); }); diff --git a/lib/workers/repository/extract/index.ts b/lib/workers/repository/extract/index.ts index 8e0a71f7757e6b..0c3c9bdd7d3825 100644 --- a/lib/workers/repository/extract/index.ts +++ b/lib/workers/repository/extract/index.ts @@ -2,7 +2,7 @@ import is from '@sindresorhus/is'; import { getManagerConfig, mergeChildConfig } from '../../../config'; import type { ManagerConfig, RenovateConfig } from '../../../config/types'; import { logger } from '../../../logger'; -import { getEnabledManagersList, hashMap } from '../../../modules/manager'; +import { get, getEnabledManagersList, hashMap } from '../../../modules/manager'; import { isCustomManager } from '../../../modules/manager/custom'; import { scm } from '../../../modules/platform/scm'; import type { ExtractResult, WorkerExtractConfig } from '../../types'; @@ -11,9 +11,13 @@ import { getManagerPackageFiles } from './manager-files'; import { processSupersedesManagers } from './supersedes'; export async function extractAllDependencies( - config: RenovateConfig, + initConfig: RenovateConfig, ): Promise { - const managerList = getEnabledManagersList(config.enabledManagers); + const managerList = getEnabledManagersList(initConfig.enabledManagers); + + // process all preflight checks + const config = await applyPreFlights(initConfig, managerList); + const extractList: WorkerExtractConfig[] = []; const fileList = await scm.getFileList(); @@ -97,3 +101,29 @@ export async function extractAllDependencies( return extractResult; } + +export async function applyPreFlights( + config: RenovateConfig, + managerList: string[], +): Promise { + let result = config; + for (const manager of managerList) { + const preflight = get(manager, 'preflight'); + if (!preflight) { + continue; + } + + logger.debug({ manager }, `Running preflight`); + logger.trace( + { manager, config: result }, + `Config before running preflight for manager: '${manager}'`, + ); + result = await preflight(result); + logger.trace( + { manager, config: result }, + `Config after running preflight for '${manager}'`, + ); + } + + return result; +}