diff --git a/lib/config/presets/index.spec.ts b/lib/config/presets/index.spec.ts index ccfd5dd8169102bdf42970c61dd73bc4409cde92..2cc78c86dc5ebb31f0ba04e8db0c4b9ae24d68fc 100644 --- a/lib/config/presets/index.spec.ts +++ b/lib/config/presets/index.spec.ts @@ -513,485 +513,6 @@ describe('config/presets/index', () => { }); }); - describe('parsePreset', () => { - // default namespace - it('returns default package name', () => { - expect(presets.parsePreset(':base')).toEqual({ - repo: 'default', - params: undefined, - presetName: 'base', - presetPath: undefined, - presetSource: 'internal', - }); - }); - - it('parses github', () => { - expect(presets.parsePreset('github>some/repo')).toEqual({ - repo: 'some/repo', - params: undefined, - presetName: 'default', - presetPath: undefined, - presetSource: 'github', - }); - }); - - it('handles special chars', () => { - expect(presets.parsePreset('github>some/repo:foo+bar')).toEqual({ - repo: 'some/repo', - params: undefined, - presetName: 'foo+bar', - presetPath: undefined, - presetSource: 'github', - }); - }); - - it('parses github subfiles', () => { - expect(presets.parsePreset('github>some/repo:somefile')).toEqual({ - repo: 'some/repo', - params: undefined, - presetName: 'somefile', - presetPath: undefined, - presetSource: 'github', - }); - }); - - it('parses github subfiles with preset name', () => { - expect( - presets.parsePreset('github>some/repo:somefile/somepreset'), - ).toEqual({ - repo: 'some/repo', - params: undefined, - presetName: 'somefile/somepreset', - presetPath: undefined, - presetSource: 'github', - }); - }); - - it('parses github file with preset name with .json extension', () => { - expect(presets.parsePreset('github>some/repo:somefile.json')).toEqual({ - repo: 'some/repo', - params: undefined, - presetName: 'somefile.json', - presetPath: undefined, - presetSource: 'github', - tag: undefined, - }); - }); - - it('parses github file with preset name with .json5 extension', () => { - expect(presets.parsePreset('github>some/repo:somefile.json5')).toEqual({ - repo: 'some/repo', - params: undefined, - presetName: 'somefile.json5', - presetPath: undefined, - presetSource: 'github', - tag: undefined, - }); - }); - - it('parses github subfiles with preset name with .json extension', () => { - expect( - presets.parsePreset('github>some/repo:somefile.json/somepreset'), - ).toEqual({ - repo: 'some/repo', - params: undefined, - presetName: 'somefile.json/somepreset', - presetPath: undefined, - presetSource: 'github', - tag: undefined, - }); - }); - - it('parses github subfiles with preset name with .json5 extension', () => { - expect( - presets.parsePreset('github>some/repo:somefile.json5/somepreset'), - ).toEqual({ - repo: 'some/repo', - params: undefined, - presetName: 'somefile.json5/somepreset', - presetPath: undefined, - presetSource: 'github', - tag: undefined, - }); - }); - - it('parses github subfiles with preset and sub-preset name', () => { - expect( - presets.parsePreset( - 'github>some/repo:somefile/somepreset/somesubpreset', - ), - ).toEqual({ - repo: 'some/repo', - params: undefined, - presetName: 'somefile/somepreset/somesubpreset', - presetPath: undefined, - presetSource: 'github', - }); - }); - - it('parses github subdirectories', () => { - expect( - presets.parsePreset('github>some/repo//somepath/somesubpath/somefile'), - ).toEqual({ - repo: 'some/repo', - params: undefined, - presetName: 'somefile', - presetPath: 'somepath/somesubpath', - presetSource: 'github', - }); - }); - - it('parses github toplevel file using subdirectory syntax', () => { - expect(presets.parsePreset('github>some/repo//somefile')).toEqual({ - repo: 'some/repo', - params: undefined, - presetName: 'somefile', - presetPath: undefined, - presetSource: 'github', - }); - }); - - it('parses gitlab', () => { - expect(presets.parsePreset('gitlab>some/repo')).toEqual({ - repo: 'some/repo', - params: undefined, - presetName: 'default', - presetPath: undefined, - presetSource: 'gitlab', - }); - }); - - it('parses gitea', () => { - expect(presets.parsePreset('gitea>some/repo')).toEqual({ - repo: 'some/repo', - params: undefined, - presetName: 'default', - presetPath: undefined, - presetSource: 'gitea', - }); - }); - - it('parses local', () => { - expect(presets.parsePreset('local>some/repo')).toEqual({ - repo: 'some/repo', - params: undefined, - presetName: 'default', - presetPath: undefined, - presetSource: 'local', - }); - }); - - it('parses local with spaces', () => { - expect(presets.parsePreset('local>A2B CD/A2B_Renovate')).toEqual({ - repo: 'A2B CD/A2B_Renovate', - params: undefined, - presetName: 'default', - presetPath: undefined, - presetSource: 'local', - }); - }); - - it('parses local with subdirectory', () => { - expect( - presets.parsePreset('local>some-group/some-repo//some-dir/some-file'), - ).toEqual({ - repo: 'some-group/some-repo', - params: undefined, - presetName: 'some-file', - presetPath: 'some-dir', - presetSource: 'local', - }); - }); - - it('parses local with spaces and subdirectory', () => { - expect( - presets.parsePreset('local>A2B CD/A2B_Renovate//some-dir/some-file'), - ).toEqual({ - repo: 'A2B CD/A2B_Renovate', - params: undefined, - presetName: 'some-file', - presetPath: 'some-dir', - presetSource: 'local', - }); - }); - - it('parses local with sub preset and tag', () => { - expect( - presets.parsePreset( - 'local>some-group/some-repo:some-file/subpreset#1.2.3', - ), - ).toEqual({ - repo: 'some-group/some-repo', - params: undefined, - presetName: 'some-file/subpreset', - presetPath: undefined, - presetSource: 'local', - tag: '1.2.3', - }); - }); - - it('parses local with subdirectory and tag', () => { - expect( - presets.parsePreset( - 'local>some-group/some-repo//some-dir/some-file#1.2.3', - ), - ).toEqual({ - repo: 'some-group/some-repo', - params: undefined, - presetName: 'some-file', - presetPath: 'some-dir', - presetSource: 'local', - tag: '1.2.3', - }); - }); - - it('parses local with subdirectory and branch/tag with a slash', () => { - expect( - presets.parsePreset( - 'local>PROJECT/repository//path/to/preset#feature/branch', - ), - ).toEqual({ - repo: 'PROJECT/repository', - params: undefined, - presetName: 'preset', - presetPath: 'path/to', - presetSource: 'local', - tag: 'feature/branch', - }); - }); - - it('parses local with sub preset and branch/tag with a slash', () => { - expect( - presets.parsePreset( - 'local>PROJECT/repository:preset/subpreset#feature/branch', - ), - ).toEqual({ - repo: 'PROJECT/repository', - params: undefined, - presetName: 'preset/subpreset', - presetPath: undefined, - presetSource: 'local', - tag: 'feature/branch', - }); - }); - - it('parses no prefix as local', () => { - expect(presets.parsePreset('some/repo')).toEqual({ - repo: 'some/repo', - params: undefined, - presetName: 'default', - presetPath: undefined, - presetSource: 'local', - }); - }); - - it('parses local Bitbucket user repo with preset name', () => { - expect(presets.parsePreset('local>~john_doe/repo//somefile')).toEqual({ - repo: '~john_doe/repo', - params: undefined, - presetName: 'somefile', - presetPath: undefined, - presetSource: 'local', - }); - }); - - it('parses local Bitbucket user repo', () => { - expect(presets.parsePreset('local>~john_doe/renovate-config')).toEqual({ - repo: '~john_doe/renovate-config', - params: undefined, - presetName: 'default', - presetPath: undefined, - presetSource: 'local', - }); - }); - - it('returns default package name with params', () => { - expect(presets.parsePreset(':group(packages/eslint, eslint)')).toEqual({ - repo: 'default', - params: ['packages/eslint', 'eslint'], - presetName: 'group', - presetPath: undefined, - presetSource: 'internal', - }); - }); - - // scoped namespace - it('returns simple scope', () => { - expect(presets.parsePreset('@somescope')).toEqual({ - repo: '@somescope/renovate-config', - params: undefined, - presetName: 'default', - presetPath: undefined, - presetSource: 'npm', - }); - }); - - it('returns simple scope and params', () => { - expect(presets.parsePreset('@somescope(param1)')).toEqual({ - repo: '@somescope/renovate-config', - params: ['param1'], - presetName: 'default', - presetPath: undefined, - presetSource: 'npm', - }); - }); - - it('returns scope with repo and default', () => { - expect(presets.parsePreset('@somescope/somepackagename')).toEqual({ - repo: '@somescope/somepackagename', - params: undefined, - presetName: 'default', - presetPath: undefined, - presetSource: 'npm', - }); - }); - - it('returns scope with repo and params and default', () => { - expect( - presets.parsePreset( - '@somescope/somepackagename(param1, param2, param3)', - ), - ).toEqual({ - repo: '@somescope/somepackagename', - params: ['param1', 'param2', 'param3'], - presetName: 'default', - presetPath: undefined, - presetSource: 'npm', - }); - }); - - it('returns scope with presetName', () => { - expect(presets.parsePreset('@somescope:somePresetName')).toEqual({ - repo: '@somescope/renovate-config', - params: undefined, - presetName: 'somePresetName', - presetPath: undefined, - presetSource: 'npm', - }); - }); - - it('returns scope with presetName and params', () => { - expect(presets.parsePreset('@somescope:somePresetName(param1)')).toEqual({ - repo: '@somescope/renovate-config', - params: ['param1'], - presetName: 'somePresetName', - presetPath: undefined, - presetSource: 'npm', - }); - }); - - it('returns scope with repo and presetName', () => { - expect( - presets.parsePreset('@somescope/somepackagename:somePresetName'), - ).toEqual({ - repo: '@somescope/somepackagename', - params: undefined, - presetName: 'somePresetName', - presetPath: undefined, - presetSource: 'npm', - }); - }); - - it('returns scope with repo and presetName and params', () => { - expect( - presets.parsePreset( - '@somescope/somepackagename:somePresetName(param1, param2)', - ), - ).toEqual({ - repo: '@somescope/somepackagename', - params: ['param1', 'param2'], - presetName: 'somePresetName', - presetPath: undefined, - presetSource: 'npm', - }); - }); - - // non-scoped namespace - it('returns non-scoped default', () => { - expect(presets.parsePreset('somepackage')).toEqual({ - repo: 'renovate-config-somepackage', - params: undefined, - presetName: 'default', - presetPath: undefined, - presetSource: 'npm', - }); - }); - - it('returns non-scoped package name', () => { - expect(presets.parsePreset('somepackage:webapp')).toEqual({ - repo: 'renovate-config-somepackage', - params: undefined, - presetName: 'webapp', - presetPath: undefined, - presetSource: 'npm', - }); - }); - - it('returns non-scoped package name full', () => { - expect(presets.parsePreset('renovate-config-somepackage:webapp')).toEqual( - { - repo: 'renovate-config-somepackage', - params: undefined, - presetName: 'webapp', - presetPath: undefined, - presetSource: 'npm', - }, - ); - }); - - it('returns non-scoped package name with params', () => { - expect(presets.parsePreset('somepackage:webapp(param1)')).toEqual({ - repo: 'renovate-config-somepackage', - params: ['param1'], - presetName: 'webapp', - presetPath: undefined, - presetSource: 'npm', - }); - }); - - it('parses HTTPS URLs', () => { - expect( - presets.parsePreset( - 'https://my.server/gitea/renovate-config/raw/branch/main/default.json', - ), - ).toEqual({ - repo: 'https://my.server/gitea/renovate-config/raw/branch/main/default.json', - params: undefined, - presetName: '', - presetPath: undefined, - presetSource: 'http', - }); - }); - - it('parses HTTP URLs', () => { - expect( - presets.parsePreset( - 'http://my.server/users/me/repos/renovate-presets/raw/default.json?at=refs%2Fheads%2Fmain', - ), - ).toEqual({ - repo: 'http://my.server/users/me/repos/renovate-presets/raw/default.json?at=refs%2Fheads%2Fmain', - params: undefined, - presetName: '', - presetPath: undefined, - presetSource: 'http', - }); - }); - - it('parses HTTPS URLs with parameters', () => { - expect( - presets.parsePreset( - 'https://my.server/gitea/renovate-config/raw/branch/main/default.json(param1)', - ), - ).toEqual({ - repo: 'https://my.server/gitea/renovate-config/raw/branch/main/default.json', - params: ['param1'], - presetName: '', - presetPath: undefined, - presetSource: 'http', - }); - }); - }); - describe('getPreset', () => { it('handles removed presets with a migration', async () => { const res = await presets.getPreset(':base', {}); diff --git a/lib/config/presets/index.ts b/lib/config/presets/index.ts index 9a96ed1f6f5aab1b1d00ff7d14d7ca84fcd35a9f..349db47089fe75f27d18dada56b855f908727ae3 100644 --- a/lib/config/presets/index.ts +++ b/lib/config/presets/index.ts @@ -24,7 +24,8 @@ import * as http from './http'; import * as internal from './internal'; import * as local from './local'; import * as npm from './npm'; -import type { ParsedPreset, Preset, PresetApi } from './types'; +import { parsePreset } from './parse'; +import type { Preset, PresetApi } from './types'; import { PRESET_DEP_NOT_FOUND, PRESET_INVALID, @@ -46,13 +47,6 @@ const presetSources: Record<string, PresetApi> = { const presetCacheNamespace = 'preset'; -const nonScopedPresetWithSubdirRegex = regEx( - /^(?<repo>~?[\w\-. /]+?)\/\/(?:(?<presetPath>[\w\-./]+)\/)?(?<presetName>[\w\-.]+)(?:#(?<tag>[\w\-./]+?))?$/, -); -const gitPresetRegex = regEx( - /^(?<repo>~?[\w\-. /]+)(?::(?<presetName>[\w\-.+/]+))?(?:#(?<tag>[\w\-./]+?))?$/, -); - export function replaceArgs( obj: string, argMapping: Record<string, any>, @@ -105,120 +99,6 @@ export function replaceArgs( return obj; } -export function parsePreset(input: string): ParsedPreset { - let str = input; - let presetSource: string | undefined; - let presetPath: string | undefined; - let repo: string; - let presetName: string; - let tag: string | undefined; - let params: string[] | undefined; - if (str.startsWith('github>')) { - presetSource = 'github'; - str = str.substring('github>'.length); - } else if (str.startsWith('gitlab>')) { - presetSource = 'gitlab'; - str = str.substring('gitlab>'.length); - } else if (str.startsWith('gitea>')) { - presetSource = 'gitea'; - str = str.substring('gitea>'.length); - } else if (str.startsWith('local>')) { - presetSource = 'local'; - str = str.substring('local>'.length); - } else if (str.startsWith('http://') || str.startsWith('https://')) { - presetSource = 'http'; - } else if ( - !str.startsWith('@') && - !str.startsWith(':') && - str.includes('/') - ) { - presetSource = 'local'; - } - str = str.replace(regEx(/^npm>/), ''); - presetSource = presetSource ?? 'npm'; - if (str.includes('(')) { - params = str - .slice(str.indexOf('(') + 1, -1) - .split(',') - .map((elem) => elem.trim()); - str = str.slice(0, str.indexOf('(')); - } - if (presetSource === 'http') { - return { presetSource, repo: str, presetName: '', params }; - } - const presetsPackages = [ - 'compatibility', - 'config', - 'customManagers', - 'default', - 'docker', - 'group', - 'helpers', - 'mergeConfidence', - 'monorepo', - 'npm', - 'packages', - 'preview', - 'replacements', - 'schedule', - 'security', - 'workarounds', - ]; - if ( - presetsPackages.some((presetPackage) => str.startsWith(`${presetPackage}:`)) - ) { - presetSource = 'internal'; - [repo, presetName] = str.split(':'); - } else if (str.startsWith(':')) { - // default namespace - presetSource = 'internal'; - repo = 'default'; - presetName = str.slice(1); - } else if (str.startsWith('@')) { - // scoped namespace - [, repo] = regEx(/(@.*?)(:|$)/).exec(str)!; - str = str.slice(repo.length); - if (!repo.includes('/')) { - repo += '/renovate-config'; - } - if (str === '') { - presetName = 'default'; - } else { - presetName = str.slice(1); - } - } else if (str.includes('//')) { - // non-scoped namespace with a subdirectory preset - - // Validation - if (str.includes(':')) { - throw new Error(PRESET_PROHIBITED_SUBPRESET); - } - if (!nonScopedPresetWithSubdirRegex.test(str)) { - throw new Error(PRESET_INVALID); - } - ({ repo, presetPath, presetName, tag } = - nonScopedPresetWithSubdirRegex.exec(str)!.groups!); - } else { - ({ repo, presetName, tag } = gitPresetRegex.exec(str)!.groups!); - - if (presetSource === 'npm' && !repo.startsWith('renovate-config-')) { - repo = `renovate-config-${repo}`; - } - if (!is.nonEmptyString(presetName)) { - presetName = 'default'; - } - } - - return { - presetSource, - presetPath, - repo, - presetName, - tag, - params, - }; -} - export async function getPreset( preset: string, baseConfig?: RenovateConfig, diff --git a/lib/config/presets/parse.spec.ts b/lib/config/presets/parse.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..76cd8ddcee47c0254667b20178b77b30922bde82 --- /dev/null +++ b/lib/config/presets/parse.spec.ts @@ -0,0 +1,462 @@ +import { parsePreset } from './parse'; + +describe('config/presets/parse', () => { + describe('parsePreset', () => { + // default namespace + it('returns default package name', () => { + expect(parsePreset(':base')).toEqual({ + repo: 'default', + params: undefined, + presetName: 'base', + presetPath: undefined, + presetSource: 'internal', + }); + }); + + it('parses github', () => { + expect(parsePreset('github>some/repo')).toEqual({ + repo: 'some/repo', + params: undefined, + presetName: 'default', + presetPath: undefined, + presetSource: 'github', + }); + }); + + it('handles special chars', () => { + expect(parsePreset('github>some/repo:foo+bar')).toEqual({ + repo: 'some/repo', + params: undefined, + presetName: 'foo+bar', + presetPath: undefined, + presetSource: 'github', + }); + }); + + it('parses github subfiles', () => { + expect(parsePreset('github>some/repo:somefile')).toEqual({ + repo: 'some/repo', + params: undefined, + presetName: 'somefile', + presetPath: undefined, + presetSource: 'github', + }); + }); + + it('parses github subfiles with preset name', () => { + expect(parsePreset('github>some/repo:somefile/somepreset')).toEqual({ + repo: 'some/repo', + params: undefined, + presetName: 'somefile/somepreset', + presetPath: undefined, + presetSource: 'github', + }); + }); + + it('parses github file with preset name with .json extension', () => { + expect(parsePreset('github>some/repo:somefile.json')).toEqual({ + repo: 'some/repo', + params: undefined, + presetName: 'somefile.json', + presetPath: undefined, + presetSource: 'github', + tag: undefined, + }); + }); + + it('parses github file with preset name with .json5 extension', () => { + expect(parsePreset('github>some/repo:somefile.json5')).toEqual({ + repo: 'some/repo', + params: undefined, + presetName: 'somefile.json5', + presetPath: undefined, + presetSource: 'github', + tag: undefined, + }); + }); + + it('parses github subfiles with preset name with .json extension', () => { + expect(parsePreset('github>some/repo:somefile.json/somepreset')).toEqual({ + repo: 'some/repo', + params: undefined, + presetName: 'somefile.json/somepreset', + presetPath: undefined, + presetSource: 'github', + tag: undefined, + }); + }); + + it('parses github subfiles with preset name with .json5 extension', () => { + expect(parsePreset('github>some/repo:somefile.json5/somepreset')).toEqual( + { + repo: 'some/repo', + params: undefined, + presetName: 'somefile.json5/somepreset', + presetPath: undefined, + presetSource: 'github', + tag: undefined, + }, + ); + }); + + it('parses github subfiles with preset and sub-preset name', () => { + expect( + parsePreset('github>some/repo:somefile/somepreset/somesubpreset'), + ).toEqual({ + repo: 'some/repo', + params: undefined, + presetName: 'somefile/somepreset/somesubpreset', + presetPath: undefined, + presetSource: 'github', + }); + }); + + it('parses github subdirectories', () => { + expect( + parsePreset('github>some/repo//somepath/somesubpath/somefile'), + ).toEqual({ + repo: 'some/repo', + params: undefined, + presetName: 'somefile', + presetPath: 'somepath/somesubpath', + presetSource: 'github', + }); + }); + + it('parses github toplevel file using subdirectory syntax', () => { + expect(parsePreset('github>some/repo//somefile')).toEqual({ + repo: 'some/repo', + params: undefined, + presetName: 'somefile', + presetPath: undefined, + presetSource: 'github', + }); + }); + + it('parses gitlab', () => { + expect(parsePreset('gitlab>some/repo')).toEqual({ + repo: 'some/repo', + params: undefined, + presetName: 'default', + presetPath: undefined, + presetSource: 'gitlab', + }); + }); + + it('parses gitea', () => { + expect(parsePreset('gitea>some/repo')).toEqual({ + repo: 'some/repo', + params: undefined, + presetName: 'default', + presetPath: undefined, + presetSource: 'gitea', + }); + }); + + it('parses local', () => { + expect(parsePreset('local>some/repo')).toEqual({ + repo: 'some/repo', + params: undefined, + presetName: 'default', + presetPath: undefined, + presetSource: 'local', + }); + }); + + it('parses local with spaces', () => { + expect(parsePreset('local>A2B CD/A2B_Renovate')).toEqual({ + repo: 'A2B CD/A2B_Renovate', + params: undefined, + presetName: 'default', + presetPath: undefined, + presetSource: 'local', + }); + }); + + it('parses local with subdirectory', () => { + expect( + parsePreset('local>some-group/some-repo//some-dir/some-file'), + ).toEqual({ + repo: 'some-group/some-repo', + params: undefined, + presetName: 'some-file', + presetPath: 'some-dir', + presetSource: 'local', + }); + }); + + it('parses local with spaces and subdirectory', () => { + expect( + parsePreset('local>A2B CD/A2B_Renovate//some-dir/some-file'), + ).toEqual({ + repo: 'A2B CD/A2B_Renovate', + params: undefined, + presetName: 'some-file', + presetPath: 'some-dir', + presetSource: 'local', + }); + }); + + it('parses local with sub preset and tag', () => { + expect( + parsePreset('local>some-group/some-repo:some-file/subpreset#1.2.3'), + ).toEqual({ + repo: 'some-group/some-repo', + params: undefined, + presetName: 'some-file/subpreset', + presetPath: undefined, + presetSource: 'local', + tag: '1.2.3', + }); + }); + + it('parses local with subdirectory and tag', () => { + expect( + parsePreset('local>some-group/some-repo//some-dir/some-file#1.2.3'), + ).toEqual({ + repo: 'some-group/some-repo', + params: undefined, + presetName: 'some-file', + presetPath: 'some-dir', + presetSource: 'local', + tag: '1.2.3', + }); + }); + + it('parses local with subdirectory and branch/tag with a slash', () => { + expect( + parsePreset('local>PROJECT/repository//path/to/preset#feature/branch'), + ).toEqual({ + repo: 'PROJECT/repository', + params: undefined, + presetName: 'preset', + presetPath: 'path/to', + presetSource: 'local', + tag: 'feature/branch', + }); + }); + + it('parses local with sub preset and branch/tag with a slash', () => { + expect( + parsePreset('local>PROJECT/repository:preset/subpreset#feature/branch'), + ).toEqual({ + repo: 'PROJECT/repository', + params: undefined, + presetName: 'preset/subpreset', + presetPath: undefined, + presetSource: 'local', + tag: 'feature/branch', + }); + }); + + it('parses no prefix as local', () => { + expect(parsePreset('some/repo')).toEqual({ + repo: 'some/repo', + params: undefined, + presetName: 'default', + presetPath: undefined, + presetSource: 'local', + }); + }); + + it('parses local Bitbucket user repo with preset name', () => { + expect(parsePreset('local>~john_doe/repo//somefile')).toEqual({ + repo: '~john_doe/repo', + params: undefined, + presetName: 'somefile', + presetPath: undefined, + presetSource: 'local', + }); + }); + + it('parses local Bitbucket user repo', () => { + expect(parsePreset('local>~john_doe/renovate-config')).toEqual({ + repo: '~john_doe/renovate-config', + params: undefined, + presetName: 'default', + presetPath: undefined, + presetSource: 'local', + }); + }); + + it('returns default package name with params', () => { + expect(parsePreset(':group(packages/eslint, eslint)')).toEqual({ + repo: 'default', + params: ['packages/eslint', 'eslint'], + presetName: 'group', + presetPath: undefined, + presetSource: 'internal', + }); + }); + + // scoped namespace + it('returns simple scope', () => { + expect(parsePreset('@somescope')).toEqual({ + repo: '@somescope/renovate-config', + params: undefined, + presetName: 'default', + presetPath: undefined, + presetSource: 'npm', + }); + }); + + it('returns simple scope and params', () => { + expect(parsePreset('@somescope(param1)')).toEqual({ + repo: '@somescope/renovate-config', + params: ['param1'], + presetName: 'default', + presetPath: undefined, + presetSource: 'npm', + }); + }); + + it('returns scope with repo and default', () => { + expect(parsePreset('@somescope/somepackagename')).toEqual({ + repo: '@somescope/somepackagename', + params: undefined, + presetName: 'default', + presetPath: undefined, + presetSource: 'npm', + }); + }); + + it('returns scope with repo and params and default', () => { + expect( + parsePreset('@somescope/somepackagename(param1, param2, param3)'), + ).toEqual({ + repo: '@somescope/somepackagename', + params: ['param1', 'param2', 'param3'], + presetName: 'default', + presetPath: undefined, + presetSource: 'npm', + }); + }); + + it('returns scope with presetName', () => { + expect(parsePreset('@somescope:somePresetName')).toEqual({ + repo: '@somescope/renovate-config', + params: undefined, + presetName: 'somePresetName', + presetPath: undefined, + presetSource: 'npm', + }); + }); + + it('returns scope with presetName and params', () => { + expect(parsePreset('@somescope:somePresetName(param1)')).toEqual({ + repo: '@somescope/renovate-config', + params: ['param1'], + presetName: 'somePresetName', + presetPath: undefined, + presetSource: 'npm', + }); + }); + + it('returns scope with repo and presetName', () => { + expect(parsePreset('@somescope/somepackagename:somePresetName')).toEqual({ + repo: '@somescope/somepackagename', + params: undefined, + presetName: 'somePresetName', + presetPath: undefined, + presetSource: 'npm', + }); + }); + + it('returns scope with repo and presetName and params', () => { + expect( + parsePreset( + '@somescope/somepackagename:somePresetName(param1, param2)', + ), + ).toEqual({ + repo: '@somescope/somepackagename', + params: ['param1', 'param2'], + presetName: 'somePresetName', + presetPath: undefined, + presetSource: 'npm', + }); + }); + + // non-scoped namespace + it('returns non-scoped default', () => { + expect(parsePreset('somepackage')).toEqual({ + repo: 'renovate-config-somepackage', + params: undefined, + presetName: 'default', + presetPath: undefined, + presetSource: 'npm', + }); + }); + + it('returns non-scoped package name', () => { + expect(parsePreset('somepackage:webapp')).toEqual({ + repo: 'renovate-config-somepackage', + params: undefined, + presetName: 'webapp', + presetPath: undefined, + presetSource: 'npm', + }); + }); + + it('returns non-scoped package name full', () => { + expect(parsePreset('renovate-config-somepackage:webapp')).toEqual({ + repo: 'renovate-config-somepackage', + params: undefined, + presetName: 'webapp', + presetPath: undefined, + presetSource: 'npm', + }); + }); + + it('returns non-scoped package name with params', () => { + expect(parsePreset('somepackage:webapp(param1)')).toEqual({ + repo: 'renovate-config-somepackage', + params: ['param1'], + presetName: 'webapp', + presetPath: undefined, + presetSource: 'npm', + }); + }); + + it('parses HTTPS URLs', () => { + expect( + parsePreset( + 'https://my.server/gitea/renovate-config/raw/branch/main/default.json', + ), + ).toEqual({ + repo: 'https://my.server/gitea/renovate-config/raw/branch/main/default.json', + params: undefined, + presetName: '', + presetPath: undefined, + presetSource: 'http', + }); + }); + + it('parses HTTP URLs', () => { + expect( + parsePreset( + 'http://my.server/users/me/repos/renovate-presets/raw/default.json?at=refs%2Fheads%2Fmain', + ), + ).toEqual({ + repo: 'http://my.server/users/me/repos/renovate-presets/raw/default.json?at=refs%2Fheads%2Fmain', + params: undefined, + presetName: '', + presetPath: undefined, + presetSource: 'http', + }); + }); + + it('parses HTTPS URLs with parameters', () => { + expect( + parsePreset( + 'https://my.server/gitea/renovate-config/raw/branch/main/default.json(param1)', + ), + ).toEqual({ + repo: 'https://my.server/gitea/renovate-config/raw/branch/main/default.json', + params: ['param1'], + presetName: '', + presetPath: undefined, + presetSource: 'http', + }); + }); + }); +}); diff --git a/lib/config/presets/parse.ts b/lib/config/presets/parse.ts new file mode 100644 index 0000000000000000000000000000000000000000..09ac031b26ce9ed183ee23f5e93b4c1d7f00a5a5 --- /dev/null +++ b/lib/config/presets/parse.ts @@ -0,0 +1,125 @@ +import is from '@sindresorhus/is'; +import { regEx } from '../../util/regex'; +import type { ParsedPreset } from './types'; +import { PRESET_INVALID, PRESET_PROHIBITED_SUBPRESET } from './util'; + +const nonScopedPresetWithSubdirRegex = regEx( + /^(?<repo>~?[\w\-. /]+?)\/\/(?:(?<presetPath>[\w\-./]+)\/)?(?<presetName>[\w\-.]+)(?:#(?<tag>[\w\-./]+?))?$/, +); +const gitPresetRegex = regEx( + /^(?<repo>~?[\w\-. /]+)(?::(?<presetName>[\w\-.+/]+))?(?:#(?<tag>[\w\-./]+?))?$/, +); + +export function parsePreset(input: string): ParsedPreset { + let str = input; + let presetSource: string | undefined; + let presetPath: string | undefined; + let repo: string; + let presetName: string; + let tag: string | undefined; + let params: string[] | undefined; + if (str.startsWith('github>')) { + presetSource = 'github'; + str = str.substring('github>'.length); + } else if (str.startsWith('gitlab>')) { + presetSource = 'gitlab'; + str = str.substring('gitlab>'.length); + } else if (str.startsWith('gitea>')) { + presetSource = 'gitea'; + str = str.substring('gitea>'.length); + } else if (str.startsWith('local>')) { + presetSource = 'local'; + str = str.substring('local>'.length); + } else if (str.startsWith('http://') || str.startsWith('https://')) { + presetSource = 'http'; + } else if ( + !str.startsWith('@') && + !str.startsWith(':') && + str.includes('/') + ) { + presetSource = 'local'; + } + str = str.replace(regEx(/^npm>/), ''); + presetSource = presetSource ?? 'npm'; + if (str.includes('(')) { + params = str + .slice(str.indexOf('(') + 1, -1) + .split(',') + .map((elem) => elem.trim()); + str = str.slice(0, str.indexOf('(')); + } + if (presetSource === 'http') { + return { presetSource, repo: str, presetName: '', params }; + } + const presetsPackages = [ + 'compatibility', + 'config', + 'customManagers', + 'default', + 'docker', + 'group', + 'helpers', + 'mergeConfidence', + 'monorepo', + 'npm', + 'packages', + 'preview', + 'replacements', + 'schedule', + 'security', + 'workarounds', + ]; + if ( + presetsPackages.some((presetPackage) => str.startsWith(`${presetPackage}:`)) + ) { + presetSource = 'internal'; + [repo, presetName] = str.split(':'); + } else if (str.startsWith(':')) { + // default namespace + presetSource = 'internal'; + repo = 'default'; + presetName = str.slice(1); + } else if (str.startsWith('@')) { + // scoped namespace + [, repo] = regEx(/(@.*?)(:|$)/).exec(str)!; + str = str.slice(repo.length); + if (!repo.includes('/')) { + repo += '/renovate-config'; + } + if (str === '') { + presetName = 'default'; + } else { + presetName = str.slice(1); + } + } else if (str.includes('//')) { + // non-scoped namespace with a subdirectory preset + + // Validation + if (str.includes(':')) { + throw new Error(PRESET_PROHIBITED_SUBPRESET); + } + if (!nonScopedPresetWithSubdirRegex.test(str)) { + throw new Error(PRESET_INVALID); + } + ({ repo, presetPath, presetName, tag } = + nonScopedPresetWithSubdirRegex.exec(str)!.groups!); + } else { + ({ repo, presetName, tag } = gitPresetRegex.exec(str)!.groups!); + + if (presetSource === 'npm' && !repo.startsWith('renovate-config-')) { + repo = `renovate-config-${repo}`; + } + if (!is.nonEmptyString(presetName)) { + presetName = 'default'; + } + } + + return { + presetSource, + presetPath, + repo, + presetName, + tag, + params, + }; +}