-
Notifications
You must be signed in to change notification settings - Fork 0
Scaffold android-cli Gradle plugin #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 |
| 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" | ||
| - 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 | ||
| 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" | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| dependencyResolutionManagement { | ||
| repositories { | ||
| mavenCentral() | ||
| gradlePluginPortal() | ||
| } | ||
| } | ||
| rootProject.name = "build-logic" |
| 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") | ||
| } |
| 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) | ||
| } | ||
| } | ||
| } | ||
| } |
| 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 | ||
| } |
| 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) | ||
| } |
| 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") | ||
| } |
| 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())) | ||
| } |
| 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>()) | ||
| } |
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing Unlike Proposed fix plugins {
id("clkx-module")
`java-gradle-plugin`
- id("com.vanniktech.maven.publish")
+ id("clkx-publish")
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
// 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.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 🐰 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
✏️ Learnings added
|
||||||||||||||||||||||
| 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())) | ||
| } |
| 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()) | ||
| } |
| 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() | ||
| } | ||
| } | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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_nameis 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
🤖 Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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_namecomes 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. 🐰
There was a problem hiding this comment.
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
🧠 Learnings used