Skip to content

fix(release): auto-release to Maven Central (automaticRelease = true) #19

fix(release): auto-release to Maven Central (automaticRelease = true)

fix(release): auto-release to Maven Central (automaticRelease = true) #19

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
# Cancel in-progress CI runs for the same ref when a new commit lands.
# Saves GitHub Actions minutes and keeps PR feedback focused on the
# latest commit.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
name: Lint (detekt + Android lint + import boundary)
runs-on: ubuntu-latest
steps:
- name: Check out
uses: actions/checkout@v4
- name: Set up JDK 17 (Temurin)
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
- name: Set up Gradle (with caching)
# setup-gradle handles ~/.gradle/caches/ and ~/.gradle/wrapper/ on its own —
# do NOT layer actions/cache on top or cache keys collide.
uses: gradle/actions/setup-gradle@v4
- name: Ensure gradlew is executable
# Defensive — actions/checkout@v4 should preserve exec bit but some self-hosted runners strip it.
run: chmod +x ./gradlew
- name: Enforce packages/core import boundary
# Runs before Gradle so a forbidden android.* import is flagged even if compilation fails.
run: |
set -euo pipefail
violations=$(grep -rn -E "^import (android|androidx)\." packages/core/src/ || true)
if [ -n "$violations" ]; then
echo "ERROR: packages/core is a pure Kotlin module — Android imports are forbidden. Move the code to packages/sdk or introduce a port interface."
echo "$violations"
exit 1
fi
echo "OK: packages/core has no Android imports."
- name: Enforce generated-file header (OpenAPI types)
# Catches hand-edits to files under packages/core/.../model/generated/.
# Every .kt file there must start with the "auto-generated by
# openapi-generator" marker (prepended by the backend's
# prependKotlinHeader.js step). Files that lack the header are
# either (a) hand-edited after regeneration, or (b) new files
# mistakenly added to the generated/ directory. Both cases are
# PR blockers — types must be regenerated in backend/apiDoc/serving
# and re-synced, never edited in place. Story 1.5, AC-6.
run: |
set -euo pipefail
GEN_DIR=packages/core/src/main/kotlin/com/convert/sdk/core/model/generated
if [ ! -d "$GEN_DIR" ]; then
echo "ERROR: generated directory not found: $GEN_DIR"
exit 1
fi
missing=0
while IFS= read -r -d '' file; do
if ! head -1 "$file" | grep -q "auto-generated by openapi-generator"; then
echo "ERROR: $file is missing the auto-generated header."
echo " Regenerate via 'yarn generateKotlinTypes' in backend/apiDoc/serving"
echo " and re-sync per the README in $GEN_DIR. Do not hand-edit."
missing=1
fi
done < <(find "$GEN_DIR" -name '*.kt' -print0)
if [ "$missing" -ne 0 ]; then
exit 1
fi
echo "OK: every .kt file under $GEN_DIR carries the auto-generated header."
- name: Run detekt
run: ./gradlew detekt --no-daemon
- name: Run Android Lint (Debug variant)
run: ./gradlew :packages:sdk:lintDebug --no-daemon
- name: Build Dokka HTML API reference
# Story 6.1 AC-7. Fails if Dokka can't build — catches a malformed
# KDoc reference, a broken `@sample` link, or a plugin mis-config.
# V2 task name is `dokkaGenerate` (aggregates every format); the
# legacy `dokkaHtml` alias was removed in Dokka 2.2.0. Outputs
# land under `packages/{core,sdk}/build/dokka/html/` and are
# consumed by vanniktech's JavadocJar.Dokka("dokkaGenerateHtml")
# hook at publish time.
run: ./gradlew dokkaGenerate --no-daemon
test-core:
name: Test :packages:core (JVM)
runs-on: ubuntu-latest
steps:
- name: Check out
uses: actions/checkout@v4
- name: Set up JDK 17 (Temurin)
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
- name: Set up Gradle (with caching)
uses: gradle/actions/setup-gradle@v4
- name: Ensure gradlew is executable
run: chmod +x ./gradlew
- name: Test + Kover XML report
run: ./gradlew :packages:core:test :packages:core:koverXmlReport --no-daemon
- name: Upload test report
if: always()
uses: actions/upload-artifact@v4
with:
name: test-core-report
path: packages/core/build/reports/tests/test/
retention-days: 7
test-sdk:
name: Test :packages:sdk (JVM / Robolectric-ready)
runs-on: ubuntu-latest
steps:
- name: Check out
uses: actions/checkout@v4
- name: Set up JDK 17 (Temurin)
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
- name: Set up Gradle (with caching)
uses: gradle/actions/setup-gradle@v4
- name: Ensure gradlew is executable
run: chmod +x ./gradlew
- name: Test + Kover XML report
run: ./gradlew :packages:sdk:testDebugUnitTest :packages:sdk:koverXmlReport --no-daemon
- name: Test :packages:sdk-lint (Story 6.3 custom lint rules)
# Exercises ConvertSdkInitializedBeforeUseDetector and
# ConvertSdkNotInApplicationOnCreateDetector against inline
# Kotlin fixtures via LintDetectorTest. Separate from the SDK
# unit test step so the report artefacts stay distinct.
run: ./gradlew :packages:sdk-lint:test --no-daemon
- name: Validate :packages:sdk lint output (Story 6.3 AC-3)
# Runs the published lint rules (lintPublish in sdk/build.gradle.kts
# bundles sdk-lint's JAR into the AAR) on the sdk module itself.
# A registry misconfiguration — wrong META-INF/services FQN,
# mis-matched lint-api ABI, malformed Issue constants — would
# crash this step. Green here = consumer apps will load the rules
# correctly.
run: ./gradlew :packages:sdk:lintDebug --no-daemon
- name: Upload test report
if: always()
uses: actions/upload-artifact@v4
with:
name: test-sdk-report
path: packages/sdk/build/reports/tests/testDebugUnitTest/
retention-days: 7
- name: Upload sdk-lint test report
if: always()
uses: actions/upload-artifact@v4
with:
name: test-sdk-lint-report
path: packages/sdk-lint/build/reports/tests/test/
retention-days: 7
coverage:
name: Enforce coverage thresholds
runs-on: ubuntu-latest
needs: [test-core, test-sdk]
steps:
- name: Check out
uses: actions/checkout@v4
- name: Set up JDK 17 (Temurin)
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
- name: Set up Gradle (with caching)
uses: gradle/actions/setup-gradle@v4
- name: Ensure gradlew is executable
run: chmod +x ./gradlew
- name: Generate Kover XML reports
# `koverVerify` does not emit report.xml — only `koverXmlReport` does.
# Generate the XML first so the reports exist for the upload step even
# when `koverVerify` trips a threshold below.
run: ./gradlew koverXmlReport --no-daemon
- name: Enforce Kover thresholds
# `koverVerify` runs each module's `minBound` rule — currently 85 for
# :packages:core and 50 for :packages:sdk (will ratchet to 70 per
# NFR19 when Robolectric-backed tests land in Story 2.2+).
run: ./gradlew koverVerify --no-daemon
- name: Ensure coverage XML reports exist
# Fail loudly if a path changes upstream — this catches silent drift
# in the Kover plugin's report layout before the artifact upload does.
run: |
ls -la packages/core/build/reports/kover/report.xml packages/sdk/build/reports/kover/report.xml
- name: Upload coverage reports
# Upload even on failure — when `koverVerify` trips a threshold, the
# reports are exactly what PR reviewers need to diagnose the gap.
if: always()
uses: actions/upload-artifact@v4
with:
name: coverage-reports
path: |
packages/core/build/reports/kover/report.xml
packages/sdk/build/reports/kover/report.xml
retention-days: 30
# The XML files may legitimately be absent if an earlier step failed
# *before* Kover produced them. Don't fail the upload on missing files.
if-no-files-found: warn