Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Build

on:
push:
branches: [main]
pull_request:
branches: [main]

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure git
run: |
git config --global user.name "CI"
git config --global user.email "ci@clanker.zone"
git config --global init.defaultBranch main
- uses: actions/setup-java@v4
with:
distribution: jetbrains
java-version: 17
- uses: gradle/actions/setup-gradle@v4
- run: ./gradlew build
41 changes: 41 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Release

on:
release:
types: [published]

permissions:
contents: write

jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure git
run: |
git config --global user.name "CI"
git config --global user.email "ci@clanker.zone"
git config --global init.defaultBranch main
- name: Extract version from tag
id: version
run: |
VERSION="${{ github.event.release.tag_name }}"
VERSION="${VERSION#v}"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
Comment on lines +20 to +25
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

GitHub Actions script injection hardening github.event context run step

💡 Result:

Script injection in GitHub Actions occurs when untrusted inputs from the github.event context, such as github.event.pull_request.title or github.event.issue.body, are interpolated into 'run' steps, allowing attackers to execute arbitrary shell commands. This is particularly risky in workflows triggered by pull_request_target, workflow_run, or similar high-privilege events where attackers control parts of github.event. Vulnerable example: - name: Check PR title run: | title="${{ github.event.pull_request.title }}" if [[ $title =~ ^octocat ]]; then echo "Valid" fi An attacker sets PR title to 'a"; ls $GITHUB_WORKSPACE"' , breaking out of the quotes and executing 'ls'. Hardening practices from official GitHub docs: 1. Use actions instead of inline scripts (recommended): Pass untrusted input as 'with' parameter to a JS action, which stringifies it safely. uses: myaction/check@v1 with: title: ${{ github.event.pull_request.title }} 2. Use intermediate environment variables in inline scripts: - name: Check PR title env: TITLE: ${{ github.event.pull_request.title }} run: | if [[ "$TITLE" =~ ^octocat ]]; then echo "Valid" fi The env var prevents shell interpretation. Untrusted github.event fields (treat as user-controlled): those ending in body, default_branch, email, head_ref, label, message, name, page_name, ref, title (e.g., github.event.issue.title, github.event.pull_request.body). Also branch names, emails. Additional mitigations: - Set workflow permissions to read-only: permissions: { contents: read, pull-requests: read } - Pin actions to commit SHAs. - Use tools like step-security/harden-runner for runtime monitoring. - Avoid pull_request_target unless necessary; prefer pull_request. No major changes in 2025-2026; core advice remains from docs.github.com/en/actions/concepts/security/script-injections and /en/actions/security-guides/security-hardening-for-github-actions.

Citations:


🌐 Web query:

GitHub Actions github.event.release.tag_name untrusted field security

💡 Result:

github.event.release.tag_name in GitHub Actions is generally considered a trusted field and not listed among the explicitly untrusted inputs by official GitHub documentation or security advisories. Official GitHub docs on script injections and untrusted inputs (e.g., issue titles, PR bodies, comments) do not include github.event.release.tag_name or other release event fields in their lists of potentially attacker-controlled data. The tag_name corresponds to a Git tag ref created by users with write access to the repository, making it controlled by trusted actors rather than external contributors like in pull_request events. However, general best practices for GitHub Actions security recommend treating many github.event fields cautiously to prevent script injection when passed directly into shell commands or expressions. For tag_name specifically: - It can contain special characters allowed in Git refs (e.g., zzz";echo${IFS}"hello";#), which could lead to command injection if unsanitized in run: steps. - Release events are triggered by repository collaborators, so the risk is lower than for fork PRs or issues, but still present if workflows use elevated permissions. Mitigation: - Use environment variables: env: TAG_NAME: ${{ github.event.release.tag_name }} then run: echo $TAG_NAME - Validate/sanitize before use (e.g., regex check for expected semver format). - Prefer github.ref_name for tag-triggered workflows, as it's equivalent and sometimes more reliable. No known CVEs or breaches specifically exploiting github.event.release.tag_name as of 2026-04-17. Primary risks in release workflows stem from other factors like untrusted actions or improper GITHUB_TOKEN permissions.

Citations:


🌐 Web query:

GitHub Actions release event context security sensitive fields

💡 Result:

In GitHub Actions workflows triggered by the 'release' event (e.g., on: release: types: [published]), the github.event.release context provides the full release object from the webhook payload, which matches the GitHub REST API Release schema. This includes fields like id, tag_name, target_commitish, name, body, draft, prerelease, author (user details), assets array (with download URLs, sizes), and timestamps (created_at, published_at). No fields contain secrets or tokens by default. The only known sensitive field in the broader github context is github.token, but it is not part of github.event.release. Security considerations: github.event data, including release fields like body or asset names, should be treated as potentially untrusted if the release creator has write access, as it could enable script injection if interpolated into commands without sanitization. Use safe handling practices like passing to JS actions or env vars with quoting. GitHub redacts secrets from logs but does not mask event data. Release workflows typically run with full GITHUB_TOKEN access, so minimize permissions and avoid untrusted code execution.

Citations:


Best practice: Use environment variables for release tag names instead of direct interpolation.

While github.event.release.tag_name is controlled by repository maintainers and is not listed in GitHub's official untrusted inputs, passing it through an environment variable before use in shell commands aligns with GitHub's security hardening guidance and prevents potential issues if special characters in the tag name are mishandled.

Recommended fix
       - name: Extract version from tag
         id: version
+        env:
+          RAW_TAG: ${{ github.event.release.tag_name }}
         run: |
-          VERSION="${{ github.event.release.tag_name }}"
-          VERSION="${VERSION#v}"
+          VERSION="${RAW_TAG#v}"
           echo "version=$VERSION" >> "$GITHUB_OUTPUT"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Extract version from tag
id: version
run: |
VERSION="${{ github.event.release.tag_name }}"
VERSION="${VERSION#v}"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Extract version from tag
id: version
env:
RAW_TAG: ${{ github.event.release.tag_name }}
run: |
VERSION="${RAW_TAG#v}"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml around lines 20 - 25, The step "Extract
version from tag" currently interpolates github.event.release.tag_name directly
in the shell; instead set an environment variable (e.g., RELEASE_TAG) to ${{
github.event.release.tag_name }} for that step and reference "$RELEASE_TAG"
inside the run block, then perform the strip operation
(VERSION="${RELEASE_TAG#v}") and echo to GITHUB_OUTPUT; ensure the variable is
double-quoted when used to safely handle special characters.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    __
   (-.-)
   /   \   matching siblings, not
  (  /\  )  changing just this repo
   \_/\_/

Sensible in isolation, but this release.yml is a copy of our sibling Clanker repos (clkx-agents, wrkx, srcx, opsx) — they all use the same ${{ github.event.release.tag_name }} interpolation. tag_name comes from an event that already requires write access to trigger, so the blast radius is low. If we tighten this we'll do it in a sweep across all repos at once, not one-off here.

Parking as known-and-intentional for now. 🐰

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ClankerGuru — Understood, that's a reasonable call. The event already requires write access, so the risk is bounded, and keeping the pattern consistent across the sibling repos makes a coordinated sweep much cleaner. I'll note this for future reviews. 🐰


✏️ Learnings added
Learnt from: ClankerGuru
Repo: ClankerGuru/android-cli PR: 1
File: .github/workflows/release.yml:20-25
Timestamp: 2026-04-17T20:11:42.419Z
Learning: In the ClankerGuru family of repos (android-cli, clkx-agents, wrkx, srcx, opsx), direct interpolation of `${{ github.event.release.tag_name }}` in `release.yml` run steps is intentional and consistent across all sibling repos. The pattern is considered acceptable because the `release: published` event requires repository write access to trigger (low blast radius). Any hardening (e.g., moving to an env var) will be done as a coordinated sweep across all repos, not one-off. Do not flag this pattern in individual PR reviews for these repos.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: ClankerGuru
Repo: ClankerGuru/android-cli PR: 1
File: build-logic/src/main/kotlin/clkx-plugin.gradle.kts:1-5
Timestamp: 2026-04-17T20:11:24.722Z
Learning: In the android-cli Gradle project (build-logic/src/main/kotlin/), the Vanniktech Maven publish configuration is intentionally split across two convention scripts:
- `clkx-plugin.gradle.kts` applies only the `java-gradle-plugin` and `com.vanniktech.maven.publish` plugin IDs.
- `clkx-publish.gradle.kts` owns the `mavenPublishing { configure(GradlePlugin(javadocJar = JavadocJar.Empty())) }` block.
Both are composed together by `clkx-conventions.gradle.kts` (which applies `clkx-plugin` and `clkx-publish`), so every plugin module gets the full publishing config. The split is intentional: `clkx-publish` is reused for non-plugin library artifacts as well (via `clkx-library.gradle.kts`, which uses `JavaLibrary` instead of `GradlePlugin`). Do not flag the absence of a `mavenPublishing` block in `clkx-plugin.gradle.kts` as a missing configuration.

- uses: actions/setup-java@v4
with:
distribution: jetbrains
java-version: 17
- uses: gradle/actions/setup-gradle@v4
- name: Build and test
run: ./gradlew build -PVERSION_NAME=${{ steps.version.outputs.version }}
- name: Publish to Maven Central
run: ./gradlew publishAndReleaseToMavenCentral -PVERSION_NAME=${{ steps.version.outputs.version }}
env:
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_PASSWORD }}
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.GPG_SIGNING_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.GPG_KEY_ID }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.GPG_PASSPHRASE }}
ORG_GRADLE_PROJECT_RELEASE_SIGNING_ENABLED: true
26 changes: 26 additions & 0 deletions build-logic/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
plugins {
`kotlin-dsl`
}

repositories {
mavenCentral()
gradlePluginPortal()
}

dependencies {
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:${embeddedKotlinVersion}")
implementation("com.vanniktech:gradle-maven-publish-plugin:0.32.0")
implementation("org.gradle.toolchains:foojay-resolver:1.0.0")
implementation("org.jetbrains.kotlinx:kover-gradle-plugin:0.9.1")
implementation("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.23.8")
implementation("org.jlleitschuh.gradle:ktlint-gradle:12.2.0")
}

gradlePlugin {
plugins {
register("clkx-settings") {
id = "clkx-settings"
implementationClass = "zone.clanker.gradle.conventions.ClkxSettingsPlugin"
}
}
}
7 changes: 7 additions & 0 deletions build-logic/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
dependencyResolutionManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
}
rootProject.name = "build-logic"
11 changes: 11 additions & 0 deletions build-logic/src/main/kotlin/clkx-conventions.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
plugins {
id("clkx-module")
id("clkx-toolchain")
id("clkx-plugin")
id("clkx-publish")
id("clkx-testing")
id("clkx-cover")
id("clkx-konsist")
id("clkx-detekt")
id("clkx-ktlint")
}
21 changes: 21 additions & 0 deletions build-logic/src/main/kotlin/clkx-cover.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
plugins {
id("org.jetbrains.kotlinx.kover")
}

kover {
reports {
filters {
excludes {
// @TaskAction run() methods delegate to Cli.execAndPrint — untestable without real CLIs
annotatedBy("org.gradle.api.tasks.TaskAction")
// Settings plugin apply() requires real Gradle Settings — tested via registerTasks
classes("*\$SettingsPlugin", "*\$SettingsPlugin\$*")
}
}
verify {
rule {
minBound(90)
}
}
}
}
9 changes: 9 additions & 0 deletions build-logic/src/main/kotlin/clkx-detekt.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
plugins {
id("io.gitlab.arturbosch.detekt")
}

detekt {
config.setFrom(rootProject.file("config/detekt.yml"))
buildUponDefaultConfig = true
parallel = true
}
31 changes: 31 additions & 0 deletions build-logic/src/main/kotlin/clkx-konsist.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
plugins {
`java-base`
}

val sourceSets = the<SourceSetContainer>()

val slopTest by sourceSets.creating {
compileClasspath += sourceSets["main"].output
runtimeClasspath += sourceSets["main"].output
}

configurations[slopTest.implementationConfigurationName].extendsFrom(configurations["testImplementation"])
configurations[slopTest.runtimeOnlyConfigurationName].extendsFrom(configurations["testRuntimeOnly"])

dependencies {
"slopTestImplementation"("com.lemonappdev:konsist:0.17.3")
"slopTestImplementation"("io.kotest:kotest-runner-junit5:5.9.1")
"slopTestImplementation"("io.kotest:kotest-assertions-core:5.9.1")
}

val slopTask = tasks.register<Test>("slopTest") {
description = "Run slop taste tests — architecture, naming, boundaries, style"
group = "verification"
testClassesDirs = slopTest.output.classesDirs
classpath = slopTest.runtimeClasspath
useJUnitPlatform()
}

tasks.named("check") {
dependsOn(slopTask)
}
7 changes: 7 additions & 0 deletions build-logic/src/main/kotlin/clkx-ktlint.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
plugins {
id("org.jlleitschuh.gradle.ktlint")
}

ktlint {
version.set("1.5.0")
}
17 changes: 17 additions & 0 deletions build-logic/src/main/kotlin/clkx-library.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import com.vanniktech.maven.publish.JavaLibrary
import com.vanniktech.maven.publish.JavadocJar

plugins {
id("clkx-module")
id("clkx-toolchain")
id("clkx-testing")
id("clkx-cover")
id("clkx-konsist")
id("clkx-detekt")
id("clkx-ktlint")
id("com.vanniktech.maven.publish")
}

mavenPublishing {
configure(JavaLibrary(javadocJar = JavadocJar.Empty()))
}
18 changes: 18 additions & 0 deletions build-logic/src/main/kotlin/clkx-module.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
plugins {
`java-library`
kotlin("jvm")
}

group = "zone.clanker"

dependencies {
"implementation"(gradleApi())
}

tasks.withType<Test> {
useJUnitPlatform()
}

tasks.withType<PublishToMavenRepository>().configureEach {
dependsOn(tasks.withType<Sign>())
}
5 changes: 5 additions & 0 deletions build-logic/src/main/kotlin/clkx-plugin.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
plugins {
id("clkx-module")
`java-gradle-plugin`
id("com.vanniktech.maven.publish")
}
Comment on lines +1 to +5
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing mavenPublishing configuration for the Gradle plugin artifact.

Unlike clkx-library.gradle.kts (which configures JavaLibrary(javadocJar = JavadocJar.Empty())), this script applies com.vanniktech.maven.publish without any mavenPublishing.configure(GradlePlugin(...)) block. The sibling clkx-publish.gradle.kts has that configuration but does not appear to be applied here. As-is, the published plugin artifact won't have the intended empty-Javadoc behavior and POM configuration may be inconsistent with the library convention.

Proposed fix
 plugins {
     id("clkx-module")
     `java-gradle-plugin`
-    id("com.vanniktech.maven.publish")
+    id("clkx-publish")
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
plugins {
id("clkx-module")
`java-gradle-plugin`
id("com.vanniktech.maven.publish")
}
plugins {
id("clkx-module")
`java-gradle-plugin`
id("clkx-publish")
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@build-logic/src/main/kotlin/clkx-plugin.gradle.kts` around lines 1 - 5, Add a
mavenPublishing configuration that registers this Gradle plugin artifact the
same way the library convention does: call
mavenPublishing.configure(GradlePlugin(...)) for the plugin artifact and set its
JavaLibrary publishing options to JavaLibrary(javadocJar = JavadocJar.Empty())
so the published plugin gets the empty Javadoc jar and consistent POM metadata;
locate the plugins block in clkx-plugin.gradle.kts and add a
mavenPublishing.configure block referencing GradlePlugin and
JavaLibrary/JavadocJar.Empty to mirror the sibling
clkx-library.gradle.kts/clkx-publish.gradle.kts setup.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

   __
  /  \
 (  o.o )  Actually — we DO use Vanniktech.
  >  ^  <      You caught one half of a split config.

clkx-plugin.gradle.kts only applies the java-gradle-plugin + com.vanniktech.maven.publish plugin IDs. The mavenPublishing { configure(GradlePlugin(javadocJar = JavadocJar.Empty())) } block lives in the sibling clkx-publish.gradle.kts:

// clkx-publish.gradle.kts
import com.vanniktech.maven.publish.GradlePlugin
import com.vanniktech.maven.publish.JavadocJar

plugins {
    id("com.vanniktech.maven.publish")
}

mavenPublishing {
    configure(GradlePlugin(javadocJar = JavadocJar.Empty()))
}

Both are applied together by clkx-conventions, so every plugin module gets the full publishing config:

// clkx-conventions.gradle.kts
plugins {
    id("clkx-module")
    id("clkx-toolchain")
    id("clkx-plugin")    // java-gradle-plugin + vanniktech ID
    id("clkx-publish")   // vanniktech config block
    ...
}

Same result as your diff — just split across two convention plugins on purpose, so we can reuse clkx-publish for non-plugin library artifacts too (see clkx-library.gradle.kts which uses JavaLibrary instead of GradlePlugin).

🐰

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ClankerGuru, thanks for the clear walkthrough — you're absolutely right, and I apologize for the noise. The split is a clean design: clkx-plugin registers the plugin IDs, clkx-publish owns the mavenPublishing configuration block, and clkx-conventions composes both so every plugin module gets the full setup. The reuse of clkx-publish with JavaLibrary vs GradlePlugin depending on context makes the intent clear. I'll remember this pattern for future reviews.


✏️ Learnings added
Learnt from: ClankerGuru
Repo: ClankerGuru/android-cli PR: 1
File: build-logic/src/main/kotlin/clkx-plugin.gradle.kts:1-5
Timestamp: 2026-04-17T20:11:24.722Z
Learning: In the android-cli Gradle project (build-logic/src/main/kotlin/), the Vanniktech Maven publish configuration is intentionally split across two convention scripts:
- `clkx-plugin.gradle.kts` applies only the `java-gradle-plugin` and `com.vanniktech.maven.publish` plugin IDs.
- `clkx-publish.gradle.kts` owns the `mavenPublishing { configure(GradlePlugin(javadocJar = JavadocJar.Empty())) }` block.
Both are composed together by `clkx-conventions.gradle.kts` (which applies `clkx-plugin` and `clkx-publish`), so every plugin module gets the full publishing config. The split is intentional: `clkx-publish` is reused for non-plugin library artifacts as well (via `clkx-library.gradle.kts`, which uses `JavaLibrary` instead of `GradlePlugin`). Do not flag the absence of a `mavenPublishing` block in `clkx-plugin.gradle.kts` as a missing configuration.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

10 changes: 10 additions & 0 deletions build-logic/src/main/kotlin/clkx-publish.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import com.vanniktech.maven.publish.GradlePlugin
import com.vanniktech.maven.publish.JavadocJar

plugins {
id("com.vanniktech.maven.publish")
}

mavenPublishing {
configure(GradlePlugin(javadocJar = JavadocJar.Empty()))
}
10 changes: 10 additions & 0 deletions build-logic/src/main/kotlin/clkx-testing.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
plugins {
`java-base`
}

dependencies {
"testImplementation"("io.kotest:kotest-runner-junit5:5.9.1")
"testImplementation"("io.kotest:kotest-assertions-core:5.9.1")
"testImplementation"("io.kotest:kotest-framework-datatest:5.9.1")
"testImplementation"(gradleTestKit())
}
10 changes: 10 additions & 0 deletions build-logic/src/main/kotlin/clkx-toolchain.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
plugins {
`java-base`
}

java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
vendor.set(JvmVendorSpec.JETBRAINS)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package zone.clanker.gradle.conventions

import org.gradle.api.Plugin
import org.gradle.api.initialization.Settings
import org.gradle.api.initialization.resolve.RepositoriesMode

class ClkxSettingsPlugin : Plugin<Settings> {
override fun apply(settings: Settings) {
settings.pluginManager.apply("org.gradle.toolchains.foojay-resolver-convention")

@Suppress("UnstableApiUsage")
settings.dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
gradlePluginPortal()
}
}
}
}
Loading
Loading