diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md index d225073659b490..40f9e3bcc821c4 100644 --- a/docs/usage/configuration-options.md +++ b/docs/usage/configuration-options.md @@ -410,7 +410,7 @@ This is an advance field and it's recommend you seek a config review before appl ## bumpVersion -Currently this setting supports `helmv3`, `npm`, `nuget`, `maven` and `sbt` only, so raise a feature request if you have a use for it with other package managers. +Currently this setting supports `helmv3`, `npm`, `nuget`, `maven`, `pep621` and `sbt` only, so raise a feature request if you have a use for it with other package managers. Its purpose is if you want Renovate to update the `version` field within your package file any time it updates dependencies within. Usually this is for automatic release purposes, so that you don't need to add another step after Renovate before you can release a new version. diff --git a/lib/modules/manager/pep621/extract.spec.ts b/lib/modules/manager/pep621/extract.spec.ts index 31b6edbb20d580..63aa8bcbdd11ff 100644 --- a/lib/modules/manager/pep621/extract.spec.ts +++ b/lib/modules/manager/pep621/extract.spec.ts @@ -311,5 +311,17 @@ describe('modules/manager/pep621/extract', () => { }, ]); }); + + it('should extract project version', () => { + const content = codeBlock` + [project] + name = "test" + version = "0.0.2" + dependencies = [ "requests==2.30.0" ] + `; + + const res = extractPackageFile(content, 'pyproject.toml'); + expect(res?.packageFileVersion).toBe('0.0.2'); + }); }); }); diff --git a/lib/modules/manager/pep621/extract.ts b/lib/modules/manager/pep621/extract.ts index f5c5a607f64574..d1f8e4accf7bf2 100644 --- a/lib/modules/manager/pep621/extract.ts +++ b/lib/modules/manager/pep621/extract.ts @@ -26,6 +26,8 @@ export function extractPackageFile( if (is.nullOrUndefined(def)) { return null; } + + const packageFileVersion = def.project?.version; const pythonConstraint = def.project?.['requires-python']; const extractedConstraints = is.nonEmptyString(pythonConstraint) ? { extractedConstraints: { python: pythonConstraint } } @@ -49,6 +51,6 @@ export function extractPackageFile( } return processedDeps.length - ? { ...extractedConstraints, deps: processedDeps } + ? { ...extractedConstraints, deps: processedDeps, packageFileVersion } : null; } diff --git a/lib/modules/manager/pep621/index.ts b/lib/modules/manager/pep621/index.ts index 1b4326edb1d24b..d71206781f7f95 100644 --- a/lib/modules/manager/pep621/index.ts +++ b/lib/modules/manager/pep621/index.ts @@ -1,5 +1,6 @@ import type { Category } from '../../../constants'; import { PypiDatasource } from '../../datasource/pypi'; +export { bumpPackageVersion } from './update'; export { extractPackageFile } from './extract'; export { updateArtifacts } from './artifacts'; diff --git a/lib/modules/manager/pep621/schema.ts b/lib/modules/manager/pep621/schema.ts index dbf9e1f4f2345b..880336adb38dc5 100644 --- a/lib/modules/manager/pep621/schema.ts +++ b/lib/modules/manager/pep621/schema.ts @@ -10,6 +10,7 @@ const DependencyRecordSchema = z export const PyProjectSchema = z.object({ project: z .object({ + version: z.string().optional().catch(undefined), 'requires-python': z.string().optional(), dependencies: DependencyListSchema, 'optional-dependencies': DependencyRecordSchema, diff --git a/lib/modules/manager/pep621/update.spec.ts b/lib/modules/manager/pep621/update.spec.ts new file mode 100644 index 00000000000000..5b33267b330dc6 --- /dev/null +++ b/lib/modules/manager/pep621/update.spec.ts @@ -0,0 +1,51 @@ +import { codeBlock } from 'common-tags'; +import * as projectUpdater from '.'; + +describe('modules/manager/pep621/update', () => { + describe('bumpPackageVersion()', () => { + const content = codeBlock` + [project] + name = "test" + version = "0.0.2" + description = "test" + `; + + it('increments', () => { + const { bumpedContent } = projectUpdater.bumpPackageVersion( + content, + '0.0.2', + 'patch', + ); + const expected = content.replace('0.0.2', '0.0.3'); + expect(bumpedContent).toEqual(expected); + }); + + it('no ops', () => { + const { bumpedContent } = projectUpdater.bumpPackageVersion( + content, + '0.0.1', + 'patch', + ); + expect(bumpedContent).toEqual(content); + }); + + it('updates', () => { + const { bumpedContent } = projectUpdater.bumpPackageVersion( + content, + '0.0.1', + 'minor', + ); + const expected = content.replace('0.0.2', '0.1.0'); + expect(bumpedContent).toEqual(expected); + }); + + it('returns content if bumping errors', () => { + const { bumpedContent } = projectUpdater.bumpPackageVersion( + content, + '0.0.2', + true as any, + ); + expect(bumpedContent).toEqual(content); + }); + }); +}); diff --git a/lib/modules/manager/pep621/update.ts b/lib/modules/manager/pep621/update.ts new file mode 100644 index 00000000000000..d19bb143ed717f --- /dev/null +++ b/lib/modules/manager/pep621/update.ts @@ -0,0 +1,48 @@ +import { inc } from '@renovatebot/pep440'; +import type { ReleaseType } from 'semver'; +import { logger } from '../../../logger'; +import { regEx } from '../../../util/regex'; +import type { BumpPackageVersionResult } from '../types'; + +export function bumpPackageVersion( + content: string, + currentValue: string, + bumpVersion: ReleaseType, +): BumpPackageVersionResult { + logger.debug( + { bumpVersion, currentValue }, + 'Checking if we should bump pyproject.toml version', + ); + + let bumpedContent = content; + try { + const newProjectVersion = inc(currentValue, bumpVersion); + if (!newProjectVersion) { + throw new Error('pep440 inc failed'); + } + + logger.debug(`newProjectVersion: ${newProjectVersion}`); + bumpedContent = content.replace( + regEx(`^(?version[ \\t]*=[ \\t]*['"])[^'"]*`, 'm'), + `$${newProjectVersion}`, + ); + + if (bumpedContent === content) { + logger.debug('Version was already bumped'); + } else { + logger.debug('Bumped pyproject.toml version'); + } + } catch (err) { + logger.warn( + { + content, + currentValue, + bumpVersion, + manager: 'pep621', + }, + 'Failed to bumpVersion', + ); + } + + return { bumpedContent }; +}