diff --git a/lib/modules/manager/api.ts b/lib/modules/manager/api.ts index e450e35e3de13f213bb161d9dc11a042721a9664..e01c0fecf3fc10dee12af3f482383b433aa54846 100644 --- a/lib/modules/manager/api.ts +++ b/lib/modules/manager/api.ts @@ -40,6 +40,7 @@ import * as homebrew from './homebrew'; import * as html from './html'; import * as jenkins from './jenkins'; import * as jsonnetBundler from './jsonnet-bundler'; +import * as kotlinScript from './kotlin-script'; import * as kubernetes from './kubernetes'; import * as kustomize from './kustomize'; import * as leiningen from './leiningen'; @@ -117,6 +118,7 @@ api.set('homebrew', homebrew); api.set('html', html); api.set('jenkins', jenkins); api.set('jsonnet-bundler', jsonnetBundler); +api.set('kotlin-script', kotlinScript); api.set('kubernetes', kubernetes); api.set('kustomize', kustomize); api.set('leiningen', leiningen); diff --git a/lib/modules/manager/kotlin-script/__fixtures__/custom-repositories.main.kts b/lib/modules/manager/kotlin-script/__fixtures__/custom-repositories.main.kts new file mode 100644 index 0000000000000000000000000000000000000000..b945d7af11ff3f229c2f31eef7728f1b0c4cfccf --- /dev/null +++ b/lib/modules/manager/kotlin-script/__fixtures__/custom-repositories.main.kts @@ -0,0 +1,10 @@ +#!/usr/bin/env kotlin +@file:Repository("https://jitpack.io") +@file:DependsOn("it.krzeminski:github-actions-kotlin-dsl:0.22.0") +@file:DependsOn("org.eclipse.jgit:org.eclipse.jgit:4.6.0.201612231935-r") +@file:Repository("https://some.other.repo/foo/bar/baz") +@file:Repository("") + +// ... + +println("Hello world") diff --git a/lib/modules/manager/kotlin-script/__fixtures__/generic-case.main.kts b/lib/modules/manager/kotlin-script/__fixtures__/generic-case.main.kts new file mode 100644 index 0000000000000000000000000000000000000000..07671b56b683551bf9f59e59fac250043721a3c0 --- /dev/null +++ b/lib/modules/manager/kotlin-script/__fixtures__/generic-case.main.kts @@ -0,0 +1,15 @@ +#!/usr/bin/env kotlin +@file:DependsOn("it.krzeminski:github-actions-kotlin-dsl:0.22.0") +@file:DependsOn( + "org.eclipse.jgit:org.eclipse.jgit:4.6.0.201612231935-r" +) + +@file : DependsOn + ( + "org.jetbrains.lets-plot:lets-plot-kotlin-jvm:3.0.2" + +) + +// ... + +println("Hello world") diff --git a/lib/modules/manager/kotlin-script/__fixtures__/missing-parts.main.kts b/lib/modules/manager/kotlin-script/__fixtures__/missing-parts.main.kts new file mode 100644 index 0000000000000000000000000000000000000000..71a5f05fad7ad52c7c6c87b9bf4cab1e5cf8f5b9 --- /dev/null +++ b/lib/modules/manager/kotlin-script/__fixtures__/missing-parts.main.kts @@ -0,0 +1,9 @@ +#!/usr/bin/env kotlin +@file:DependsOn("it.krzeminski:github-actions-kotlin-dsl:0.22.0") +@file:DependsOn(":org.eclipse.jgit:4.6.0.201612231935-r") +@file:DependsOn("org.jetbrains.lets-plot::3.0.2") +@file:DependsOn("org.jetbrains.lets-plot:lets-plot-kotlin-jvm:") + +// ... + +println("Hello world") diff --git a/lib/modules/manager/kotlin-script/extract.spec.ts b/lib/modules/manager/kotlin-script/extract.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..9272c601065416684369886b29fe3b7e4ae49483 --- /dev/null +++ b/lib/modules/manager/kotlin-script/extract.spec.ts @@ -0,0 +1,98 @@ +import { Fixtures } from '../../../../test/fixtures'; +import { extractPackageFile } from '.'; + +const genericCaseFileContent = Fixtures.get('generic-case.main.kts'); +const customRepositoriesFileContent = Fixtures.get( + 'custom-repositories.main.kts' +); +const missingPartsFileContent = Fixtures.get('missing-parts.main.kts'); + +describe('modules/manager/kotlin-script/extract', () => { + describe('extractPackageFile()', () => { + it('extracts dependencies in a generic case', () => { + // when + const packageFile = extractPackageFile(genericCaseFileContent); + + // then + expect(packageFile).toEqual({ + deps: [ + { + depName: 'it.krzeminski:github-actions-kotlin-dsl', + currentValue: '0.22.0', + replaceString: '"it.krzeminski:github-actions-kotlin-dsl:0.22.0"', + datasource: 'maven', + }, + { + depName: 'org.eclipse.jgit:org.eclipse.jgit', + currentValue: '4.6.0.201612231935-r', + replaceString: + '"org.eclipse.jgit:org.eclipse.jgit:4.6.0.201612231935-r"', + datasource: 'maven', + }, + { + depName: 'org.jetbrains.lets-plot:lets-plot-kotlin-jvm', + currentValue: '3.0.2', + replaceString: + '"org.jetbrains.lets-plot:lets-plot-kotlin-jvm:3.0.2"', + datasource: 'maven', + }, + ], + }); + }); + + it('detects custom repository definitions', () => { + // when + const packageFile = extractPackageFile(customRepositoriesFileContent); + + // then + expect(packageFile).toEqual({ + deps: [ + { + depName: 'it.krzeminski:github-actions-kotlin-dsl', + currentValue: '0.22.0', + replaceString: '"it.krzeminski:github-actions-kotlin-dsl:0.22.0"', + datasource: 'maven', + }, + { + depName: 'org.eclipse.jgit:org.eclipse.jgit', + currentValue: '4.6.0.201612231935-r', + replaceString: + '"org.eclipse.jgit:org.eclipse.jgit:4.6.0.201612231935-r"', + datasource: 'maven', + }, + ], + registryUrls: [ + 'https://jitpack.io', + 'https://some.other.repo/foo/bar/baz', + ], + }); + }); + + it('no dependencies', () => { + // when + const packageFile = extractPackageFile(` + #!/usr/bin/env kotlin + println("Hello world")`); + + // then + expect(packageFile).toBeNull(); + }); + + it('skips dependencies with missing parts', () => { + // when + const packageFile = extractPackageFile(missingPartsFileContent); + + // then + expect(packageFile).toEqual({ + deps: [ + { + depName: 'it.krzeminski:github-actions-kotlin-dsl', + currentValue: '0.22.0', + replaceString: '"it.krzeminski:github-actions-kotlin-dsl:0.22.0"', + datasource: 'maven', + }, + ], + }); + }); + }); +}); diff --git a/lib/modules/manager/kotlin-script/extract.ts b/lib/modules/manager/kotlin-script/extract.ts new file mode 100644 index 0000000000000000000000000000000000000000..62125c3c3f8ccfa559484a6261c2c579403647a5 --- /dev/null +++ b/lib/modules/manager/kotlin-script/extract.ts @@ -0,0 +1,40 @@ +import is from '@sindresorhus/is'; +import { regEx } from '../../../util/regex'; +import { MavenDatasource } from '../../datasource/maven'; +import type { PackageDependency, PackageFile } from '../types'; + +const dependsOnRegex = regEx( + /@file\s*:\s*DependsOn\s*\(\s*(?<replaceString>"(?<groupId>.+):(?<artifactId>.+):(?<version>.+)")\s*\)/g +); +const repositoryRegex = regEx( + /@file\s*:\s*Repository\s*\(\s*"(?<repositoryName>.+)"\s*\)/g +); + +export function extractPackageFile(fileContent: string): PackageFile | null { + const registryUrls: string[] = [...fileContent.matchAll(repositoryRegex)] + .map((match) => match.groups?.repositoryName) + .filter(is.string); + + const matches = [...fileContent.matchAll(dependsOnRegex)] + .map((m) => m.groups) + .filter(is.truthy); + const deps: PackageDependency[] = []; + for (const match of matches) { + const dep: PackageDependency = { + currentValue: match.version, + depName: `${match.groupId}:${match.artifactId}`, + replaceString: match.replaceString, + datasource: MavenDatasource.id, + }; + deps.push(dep); + } + + if (deps.length === 0) { + return null; + } + + return { + deps, + ...(registryUrls.length && { registryUrls }), + }; +} diff --git a/lib/modules/manager/kotlin-script/index.spec.ts b/lib/modules/manager/kotlin-script/index.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..c2d04adaa30d3311357596824b3dfae24a1aefc1 --- /dev/null +++ b/lib/modules/manager/kotlin-script/index.spec.ts @@ -0,0 +1,11 @@ +import { regEx } from '../../../util/regex'; +import { defaultConfig } from '.'; + +describe('modules/manager/kotlin-script/index', () => { + it('fileMatch regex is correct', () => { + expect(defaultConfig.fileMatch).toHaveLength(1); + defaultConfig.fileMatch.forEach((pattern) => { + expect(() => regEx(pattern)).not.toThrow(); + }); + }); +}); diff --git a/lib/modules/manager/kotlin-script/index.ts b/lib/modules/manager/kotlin-script/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..dbded231b6b2969c835da3361f5d17252fc1b04a --- /dev/null +++ b/lib/modules/manager/kotlin-script/index.ts @@ -0,0 +1,9 @@ +import { MavenDatasource } from '../../datasource/maven'; + +export { extractPackageFile } from './extract'; + +export const defaultConfig = { + fileMatch: ['^.+\\.main\\.kts$'], +}; + +export const supportedDatasources = [MavenDatasource.id]; diff --git a/lib/modules/manager/kotlin-script/readme.md b/lib/modules/manager/kotlin-script/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..e227cd5a830346fcc599cd7a681761fd25666062 --- /dev/null +++ b/lib/modules/manager/kotlin-script/readme.md @@ -0,0 +1,32 @@ +--- +title: Kotlin Script dependency versions +description: Kotlin Script dependency versions support in Renovate +--- + +Renovate supports upgrading dependencies in [Kotlin Script](https://github.com/Kotlin/KEEP/blob/master/proposals/scripting-support.md) files. +These are self-contained scripts where one can write Kotlin code with JVM backend, and compilation happens when the +scripts are ran. For example: + +```kotlin +#!/usr/bin/env kotlin +@file:Repository("https://jitpack.io") +@file:DependsOn("com.github.krzema12:github-actions-kotlin-dsl:main-SNAPSHOT") +@file:DependsOn("org.eclipse.jgit:org.eclipse.jgit:4.6.0.201612231935-r") +@file:DependsOn("org.jetbrains.lets-plot:lets-plot-kotlin-jvm:3.0.2") + +println("Hello world!") + +// ... +``` + +By default, Renovate scans files only with `.main.kts` extension and not `.kts`, to avoid ambiguity with Gradle config +files that have `.gradle.kts` extension. As there are cases where just `.kts` extension or no extension is used, +Renovate can be [configured](https://docs.renovatebot.com/configuration-options/) to scan also these: + +```json +{ + "kotlin-script": { + "fileMatch": ["^.*\\.kts$"] + } +} +```