diff --git a/lib/modules/manager/poetry/extract.spec.ts b/lib/modules/manager/poetry/extract.spec.ts index a1a459499491b6..56fca72715c412 100644 --- a/lib/modules/manager/poetry/extract.spec.ts +++ b/lib/modules/manager/poetry/extract.spec.ts @@ -1,5 +1,7 @@ +import { codeBlock } from 'common-tags'; import { Fixtures } from '../../../../test/fixtures'; import { fs } from '../../../../test/util'; +import { GithubTagsDatasource } from '../../datasource/github-tags'; import { extractPackageFile } from '.'; jest.mock('../../../util/fs'); @@ -141,8 +143,17 @@ describe('modules/manager/poetry/extract', () => { }); it('extracts dependencies from dependency groups', async () => { - const content = - '[tool.poetry.dependencies]\ndep = "^2.0"\n\n[tool.poetry.group.dev.dependencies]\ndev_dep = "^3.0"\n\n[tool.poetry.group.typing.dependencies]\ntyping_dep = "^4.0"'; + const content = codeBlock` + [tool.poetry.dependencies] + dep = "^2.0" + + + [tool.poetry.group.dev.dependencies] + dev_dep = "^3.0" + + [tool.poetry.group.typing.dependencies] + typing_dep = "^4.0" + `; const res = await extractPackageFile(content, filename); expect(res?.deps).toMatchObject([ { @@ -177,9 +188,42 @@ describe('modules/manager/poetry/extract', () => { }); }); + it('parses github dependencies tags on ssh urls', async () => { + const content = codeBlock` + [tool.poetry.dependencies] + fastapi = {git = "git@github.com:tiangolo/fastapi.git", tag="1.2.3"} + werkzeug = ">=0.14" + `; + const res = (await extractPackageFile(content, filename))!.deps; + expect(res[0].depName).toBe('fastapi'); + expect(res[0].packageName).toBe('tiangolo/fastapi'); + expect(res[0].currentValue).toBe('1.2.3'); + expect(res[0].skipReason).toBeUndefined(); + expect(res[0].datasource).toBe(GithubTagsDatasource.id); + expect(res).toHaveLength(2); + }); + + it('parses github dependencies tags on http urls', async () => { + const content = codeBlock` + [tool.poetry.dependencies] + fastapi = {git = "https://github.com/tiangolo/fastapi.git", tag="1.2.3"} + werkzeug = ">=0.14" + `; + const res = (await extractPackageFile(content, filename))!.deps; + expect(res[0].depName).toBe('fastapi'); + expect(res[0].packageName).toBe('tiangolo/fastapi'); + expect(res[0].currentValue).toBe('1.2.3'); + expect(res[0].skipReason).toBeUndefined(); + expect(res[0].datasource).toBe(GithubTagsDatasource.id); + expect(res).toHaveLength(2); + }); + it('skips git dependencies', async () => { - const content = - '[tool.poetry.dependencies]\r\nflask = {git = "https://github.com/pallets/flask.git"}\r\nwerkzeug = ">=0.14"'; + const content = codeBlock` + [tool.poetry.dependencies] + flask = {git = "https://github.com/pallets/flask.git"} + werkzeug = ">=0.14" + `; const res = (await extractPackageFile(content, filename))!.deps; expect(res[0].depName).toBe('flask'); expect(res[0].currentValue).toBeEmptyString(); @@ -188,8 +232,11 @@ describe('modules/manager/poetry/extract', () => { }); it('skips git dependencies with version', async () => { - const content = - '[tool.poetry.dependencies]\r\nflask = {git = "https://github.com/pallets/flask.git", version="1.2.3"}\r\nwerkzeug = ">=0.14"'; + const content = codeBlock` + [tool.poetry.dependencies] + flask = {git = "https://github.com/pallets/flask.git", version="1.2.3"} + werkzeug = ">=0.14" + `; const res = (await extractPackageFile(content, filename))!.deps; expect(res[0].depName).toBe('flask'); expect(res[0].currentValue).toBe('1.2.3'); @@ -197,9 +244,24 @@ describe('modules/manager/poetry/extract', () => { expect(res).toHaveLength(2); }); + it('skips git dependencies on tags that are not in github', async () => { + const content = codeBlock` + [tool.poetry.dependencies] + aws-sam = {git = "https://gitlab.com/gitlab-examples/aws-sam.git", tag="1.2.3"} + `; + const res = (await extractPackageFile(content, filename))!.deps; + expect(res[0].depName).toBe('aws-sam'); + expect(res[0].currentValue).toBe('1.2.3'); + expect(res[0].skipReason).toBe('git-dependency'); + expect(res).toHaveLength(1); + }); + it('skips path dependencies', async () => { - const content = - '[tool.poetry.dependencies]\r\nflask = {path = "/some/path/"}\r\nwerkzeug = ">=0.14"'; + const content = codeBlock` + [tool.poetry.dependencies] + flask = {path = "/some/path/"} + werkzeug = ">=0.14" + `; const res = (await extractPackageFile(content, filename))!.deps; expect(res[0].depName).toBe('flask'); expect(res[0].currentValue).toBe(''); @@ -208,8 +270,11 @@ describe('modules/manager/poetry/extract', () => { }); it('skips path dependencies with version', async () => { - const content = - '[tool.poetry.dependencies]\r\nflask = {path = "/some/path/", version = "1.2.3"}\r\nwerkzeug = ">=0.14"'; + const content = codeBlock` + [tool.poetry.dependencies] + flask = {path = "/some/path/", version = "1.2.3"} + werkzeug = ">=0.14" + `; const res = (await extractPackageFile(content, filename))!.deps; expect(res[0].depName).toBe('flask'); expect(res[0].currentValue).toBe('1.2.3'); diff --git a/lib/modules/manager/poetry/extract.ts b/lib/modules/manager/poetry/extract.ts index ae9810dbc0e2fd..13daf4ace31a5f 100644 --- a/lib/modules/manager/poetry/extract.ts +++ b/lib/modules/manager/poetry/extract.ts @@ -7,7 +7,9 @@ import { localPathExists, readLocalFile, } from '../../../util/fs'; +import { parseGitUrl } from '../../../util/git/url'; import { regEx } from '../../../util/regex'; +import { GithubTagsDatasource } from '../../datasource/github-tags'; import { PypiDatasource } from '../../datasource/pypi'; import * as pep440Versioning from '../../versioning/pep440'; import * as poetryVersioning from '../../versioning/poetry'; @@ -56,12 +58,15 @@ function extractFromSection( } const pep503NormalizeRegex = regEx(/[-_.]+/g); - const packageName = depName - .toLowerCase() - .replace(pep503NormalizeRegex, '-'); + let packageName = depName.toLowerCase().replace(pep503NormalizeRegex, '-'); let skipReason: SkipReason | null = null; let currentValue = sectionContent[depName]; let nestedVersion = false; + let datasource = PypiDatasource.id; + let lockedVersion: string | null = null; + if (packageName in poetryLockfile) { + lockedVersion = poetryLockfile[packageName]; + } if (!is.string(currentValue)) { const version = currentValue.version; const path = currentValue.path; @@ -76,8 +81,19 @@ function extractFromSection( currentValue = ''; skipReason = 'path-dependency'; } else if (git) { - currentValue = ''; - skipReason = 'git-dependency'; + if (currentValue.tag) { + currentValue = currentValue.tag; + datasource = GithubTagsDatasource.id; + const githubPackageName = extractGithubPackageName(git); + if (githubPackageName) { + packageName = githubPackageName; + } else { + skipReason = 'git-dependency'; + } + } else { + currentValue = ''; + skipReason = 'git-dependency'; + } } else { currentValue = ''; skipReason = 'multiple-constraint-dep'; @@ -88,10 +104,10 @@ function extractFromSection( depType, currentValue, managerData: { nestedVersion }, - datasource: PypiDatasource.id, + datasource, }; - if (packageName in poetryLockfile) { - dep.lockedVersion = poetryLockfile[packageName]; + if (lockedVersion) { + dep.lockedVersion = lockedVersion; } if (depName !== packageName) { dep.packageName = packageName; @@ -199,3 +215,11 @@ export async function extractPackageFile( } return res; } + +function extractGithubPackageName(url: string): string | null { + const parsedUrl = parseGitUrl(url); + if (parsedUrl.source !== 'github.com') { + return null; + } + return `${parsedUrl.owner}/${parsedUrl.name}`; +} diff --git a/lib/modules/manager/poetry/index.ts b/lib/modules/manager/poetry/index.ts index 122ebb1d37a823..3655fea38af505 100644 --- a/lib/modules/manager/poetry/index.ts +++ b/lib/modules/manager/poetry/index.ts @@ -1,11 +1,15 @@ import type { ProgrammingLanguage } from '../../../constants'; +import { GithubTagsDatasource } from '../../datasource/github-tags'; import { PypiDatasource } from '../../datasource/pypi'; export { extractPackageFile } from './extract'; export { updateArtifacts } from './artifacts'; export { updateLockedDependency } from './update-locked'; -export const supportedDatasources = [PypiDatasource.id]; +export const supportedDatasources = [ + PypiDatasource.id, + GithubTagsDatasource.id, +]; export const language: ProgrammingLanguage = 'python'; export const supportsLockFileMaintenance = true; diff --git a/lib/modules/manager/poetry/types.ts b/lib/modules/manager/poetry/types.ts index 935cd37436d9f9..6fa510ad515f9c 100644 --- a/lib/modules/manager/poetry/types.ts +++ b/lib/modules/manager/poetry/types.ts @@ -20,6 +20,7 @@ export interface PoetryFile { export interface PoetryDependency { path?: string; git?: string; + tag?: string; version?: string; }