Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
280f81a
[#614] Update Chucker version to 4.2.0 and 4.3.0 in libs.versions.tom…
thawzintoe-ptut Feb 2, 2026
216c8c7
[618] Add AGENTS.md documentation and update project file filters to …
thawzintoe-ptut Feb 4, 2026
b907068
[#618] Update AGENTS.md to enhance project documentation and clarify …
thawzintoe-ptut Feb 5, 2026
cb0476b
Merge pull request #625 from nimblehq/bug/614-fix-chucker-http-report…
hoangnguyen92dn Feb 6, 2026
765a5f4
[#570] Add preRelease build type
hoangnguyen92dn Feb 9, 2026
0cb02f3
[#570] Update proguard rules
hoangnguyen92dn Feb 9, 2026
fa322e8
[#570] Minor code clean up
hoangnguyen92dn Feb 9, 2026
477a1e0
[#570] Add preRelease build type to sample-compose
hoangnguyen92dn Feb 9, 2026
e77bc6d
[#570] Update app deployment to support preRelease build type
hoangnguyen92dn Feb 9, 2026
001c336
[#570] Solve comments on code review
hoangnguyen92dn Feb 10, 2026
d3fe052
Merge pull request #627 from nimblehq/feature/570-add-pre-release-bui…
hoangnguyen92dn Feb 24, 2026
057bd23
[#463] Update workflows in .cicdtemplate to generate release notes fo…
hoangnguyen92dn Feb 24, 2026
f6227f1
[618] Update AGENTS.md to refine CI/CD workflow documentation and cla…
thawzintoe-ptut Feb 27, 2026
e502787
[618] Update AGENTS.md to correct build types count and standardize f…
thawzintoe-ptut Feb 27, 2026
2a72ba8
Merge pull request #626 from nimblehq/chore/618-add-agentsmd
hoangnguyen92dn Feb 27, 2026
9be7229
Merge pull request #629 from nimblehq/chore/463-generate-and-add-rele…
hoangnguyen92dn Feb 27, 2026
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: Deploy production to Firebase App Distribution

on:
push:
branches:
- main
workflow_dispatch:
inputs:
testerGroups:
description: "Tester groups (Use \"vars.TESTER_GROUPS\" as default)"
required: false
type: string

jobs:
deploy_production_to_firebase_app_distribution:
name: Deploy production to Firebase App Distribution
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'

- name: Set up timezone
uses: zcong1993/setup-timezone@master
with:
timezone: Asia/Bangkok

- name: Checkout source code
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Cache Gradle
uses: actions/cache@v4
with:
path: |
~/.gradle/caches/modules-*
~/.gradle/caches/jars-*
~/.gradle/caches/build-cache-*
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
restore-keys: |
${{ runner.os }}-gradle-

- name: Run Detekt
run: ./gradlew detekt

- name: Archive Detekt reports
uses: actions/upload-artifact@v4
with:
name: DetektReports
path: build/reports/detekt/

- name: Run unit tests with Kover
run: ./gradlew koverHtmlReportCustom

- name: Archive code coverage reports
uses: actions/upload-artifact@v4
with:
name: CodeCoverageReports
path: app/build/reports/kover/

- name: Generate release notes
id: generate-release-notes
run: |
echo 'RELEASE_NOTES<<EOF' >> $GITHUB_OUTPUT
echo "$(git log --merges --pretty=%B $(git describe --abbrev=0 --tags)..HEAD | grep "\[")" >> $GITHUB_OUTPUT
echo 'EOF' >> $GITHUB_OUTPUT

- name: Build production APK
run: ./gradlew assembleProductionPreRelease

- name: Deploy production to Firebase
uses: wzieba/Firebase-Distribution-Github-Action@v1
with:
appId: ${{secrets.FIREBASE_APP_ID_PRODUCTION}}
serviceCredentialsFileContent: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_CREDENTIAL_FILE_CONTENT }}
groups: ${{ github.event.inputs.testerGroups || vars.TESTER_GROUPS }}
file: app/build/outputs/apk/production/preRelease/app-production-preRelease.apk
releaseNotes: ${{ steps.generate-release-notes.outputs.RELEASE_NOTES }}
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
name: Deploy staging and production to Firebase App Distribution
name: Deploy staging to Firebase App Distribution

on:
# Trigger the workflow on push action in develop branch.
# So it will trigger when the PR of the feature branch was merged.
push:
branches:
- develop
workflow_dispatch:
inputs:
testerGroups:
description: "Tester groups (Use \"vars.TESTER_GROUPS\" as default)"
required: false
type: string

jobs:
deploy_staging_and_production_to_firebase_app_distribution:
name: Deploy staging and production to Firebase App Distribution
deploy_staging_to_firebase_app_distribution:
name: Deploy staging to Firebase App Distribution
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
Expand All @@ -26,6 +32,8 @@ jobs:

- name: Checkout source code
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Cache Gradle
uses: actions/cache@v4
Expand Down Expand Up @@ -57,23 +65,20 @@ jobs:
path: app/build/reports/kover/

- name: Build staging APK
run: ./gradlew assembleStagingDebug
run: ./gradlew assembleStagingPreRelease

- name: Generate release notes
id: generate-release-notes
run: |
echo 'RELEASE_NOTES<<EOF' >> $GITHUB_OUTPUT
echo "$((git log -1 --merges | grep "\[") | grep . && echo "" || echo $(git log -1 --merges --format=%B))" >> $GITHUB_OUTPUT
echo 'EOF' >> $GITHUB_OUTPUT

- name: Deploy staging to Firebase
uses: wzieba/Firebase-Distribution-Github-Action@v1
with:
appId: ${{secrets.FIREBASE_APP_ID_STAGING}}
serviceCredentialsFileContent: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_CREDENTIAL_FILE_CONTENT }}
groups: testers
file: app/build/outputs/apk/staging/debug/app-staging-debug.apk

- name: Build production APK
run: ./gradlew assembleProductionDebug

- name: Deploy production to Firebase
uses: wzieba/Firebase-Distribution-Github-Action@v1
with:
appId: ${{secrets.FIREBASE_APP_ID_PRODUCTION}}
serviceCredentialsFileContent: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_CREDENTIAL_FILE_CONTENT }}
groups: testers
file: app/build/outputs/apk/production/debug/app-production-debug.apk
groups: ${{ github.event.inputs.testerGroups || vars.TESTER_GROUPS }}
file: app/build/outputs/apk/staging/preRelease/app-staging-preRelease.apk
releaseNotes: ${{ steps.generate-release-notes.outputs.RELEASE_NOTES }}
154 changes: 154 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# AGENTS.md

You are an experienced Android developer working on a project template generator.

Android project template generator using [Nimble Compass](https://nimblehq.co/compass/) conventions.

## Technology Stack

| Category | Technology |
|----------|------------|
| **Language** | Kotlin |
| **UI Framework** | Jetpack Compose |
| **Architecture** | MVVM / Clean Architecture (app → domain ← data) |
| **Dependency Injection** | Hilt |
| **Generator** | Kscript |
| **Build System** | Gradle (Kotlin DSL) |
| **Static Analysis** | Detekt, Android Lint |
| **Coverage** | Kover |

## Project Structure

```
/scripts → Generator script (new_project.kts)
/template-compose → Source template (see its AGENTS.md for Android guidance)
/sample-compose → Example output (regenerate, don't edit directly)
/.github → CI workflows and PR templates
```

## Prerequisites

- Android Studio (Latest Stable)
- JDK 17+
- Kscript installed (`brew install holgerbrandl/tap/kscript`)

## Commands

### Build
```bash
cd template-compose && ./gradlew assembleDebug
```

### Static Analysis
```bash
cd template-compose && ./gradlew detekt lint
```

### Tests
```bash
cd template-compose && ./gradlew app:testStagingDebugUnitTest data:testDebugUnitTest domain:test
```

### Coverage
```bash
cd template-compose && ./gradlew koverXmlReportCustom
```

### Generate New Project
```bash
cd scripts && kscript new_project.kts package-name=com.example.app app-name="My App" template=compose
```

## Testing

**Before commit:**
```bash
cd template-compose && ./gradlew detekt lint assembleDebug
```

**Before PR:**
```bash
cd template-compose && ./gradlew detekt lint app:testStagingDebugUnitTest data:testDebugUnitTest domain:test koverXmlReportCustom
```

**After template changes:** Verify generator works:
```bash
cd scripts && kscript new_project.kts package-name=co.test.app app-name="Test App" template=compose
```

## Configuration Files

| File | Purpose |
|------|---------|
| `template-compose/detekt-config.yml` | Detekt rules |
| `.github/workflows/run_detekt_and_unit_tests.yml` | CI pipeline (Detekt + unit tests) |
| `.github/workflows/review_pull_request.yml` | PR automation and checks |
| `.github/workflows/verify_newproject_script.yml` | Validate generator script on PRs |

## CI/CD

**Pipeline:** Detekt → Lint → Tests → Coverage → Danger

Workflows defined in `.github/workflows/`:
- `run_detekt_and_unit_tests.yml` — Main CI (Detekt + unit tests)
- `review_pull_request.yml` — PR automation and checks
- `verify_newproject_script.yml` — Validate generator script on PRs

## Template Placeholders

The generator replaces these strings — don't modify them:

| Placeholder | Replaced With |
|-------------|---------------|
| `co.nimblehq.template.compose` | Package name |
| `co/nimblehq/template/compose` | Package path |
| `Template Compose` | App name |
| `TemplateCompose` | App name (no spaces) |

## Git Workflow

**Branches:**
- `main` — Production (protected, no direct commits)
- `develop` — Staging (protected, no direct commits)
- Feature branches from `develop` using `kebab-case`: `feature/add-login`, `bug/fix-crash`

**Commits:**
- Format: `[#123] Add feature` — capitalize first word, present tense
- One logical change per commit
- Include ticket ID for traceability

**Pull Requests:**
- Title: `[#ticket] Description`
- 2 approvals required (include Team Lead or senior for large squads)
- Target merge within 2-3 days
- Only Team Lead merges to protected branches

## Key Guidelines for Agents

1. **Template Awareness**: Never modify placeholder strings used by the generator
2. **Regenerate, Don't Edit**: Changes to `sample-compose/` are overwritten — modify `template-compose/` instead
3. **Static Analysis**: Always run `./gradlew detekt lint` before committing
4. **Test Generator**: After any template change, verify generation still works
5. **Module Boundaries**: Core modules must not depend on feature modules
6. **Compose Only**: All UI must use Jetpack Compose — never suggest XML layouts

## Boundaries

✅ **Required:**
- Run `detekt lint` before commits
- Test generator after template changes
- 2 PR approvals
- Follow Nimble Compass conventions

⚠️ **Ask before:**
- Adding modules to template
- Changing generator behavior
- Changing Detekt/coverage thresholds
- Adding CI workflows

🚫 **Don't:**
- Edit `sample-compose/` directly (regenerate it)
- Push to `main` or `develop`
- Skip CI checks
- Commit secrets or credentials
- Merge PRs without Team Lead approval
5 changes: 5 additions & 0 deletions sample-compose/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ android {
buildConfigField("String", "BASE_API_URL", "\"https://jsonplaceholder.typicode.com/\"")
}

create(BuildType.PRERELEASE) {
initWith(getByName(BuildType.RELEASE))
}

getByName(BuildType.DEBUG) {
// For quickly testing build with proguard, enable this
isMinifyEnabled = false
Expand Down Expand Up @@ -139,6 +143,7 @@ dependencies {
implementation(libs.kotlinx.collections.immutable)

debugImplementation(libs.chucker)
preReleaseImplementation(libs.chucker)
releaseImplementation(libs.chucker.noOp)

// Unit test
Expand Down
4 changes: 0 additions & 4 deletions sample-compose/app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,3 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

# Data class
-keepclassmembers class co.nimblehq.sample.compose.data.remote.models.requests.** { *; }
-keepclassmembers class co.nimblehq.sample.compose.data.remote.models.responses.** { *; }
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import okhttp3.logging.HttpLoggingInterceptor
import java.util.concurrent.*

private const val READ_TIME_OUT = 30L
private const val BUILD_TYPE_RELEASE = "release"

@Module
@InstallIn(SingletonComponent::class)
Expand All @@ -22,7 +23,7 @@ class OkHttpClientModule {
fun provideOkHttpClient(
chuckerInterceptor: ChuckerInterceptor
) = OkHttpClient.Builder().apply {
if (BuildConfig.DEBUG) {
if (!BuildConfig.BUILD_TYPE.equals(BUILD_TYPE_RELEASE, ignoreCase = true)) {
addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
Expand Down
1 change: 1 addition & 0 deletions sample-compose/buildSrc/src/main/java/Configurations.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ object Flavor {

object BuildType {
const val DEBUG = "debug"
const val PRERELEASE = "preRelease"
const val RELEASE = "release"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.dsl.DependencyHandler

fun DependencyHandler.preReleaseImplementation(dependencyNotation: Any): Dependency? =
add("preReleaseImplementation", dependencyNotation)
14 changes: 10 additions & 4 deletions sample-compose/data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ android {

buildTypes {
getByName(BuildType.RELEASE) {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
)
isMinifyEnabled = false
consumerProguardFiles("consumer-rules.pro")
}

create(BuildType.PRERELEASE) {
initWith(getByName(BuildType.RELEASE))
}

getByName(BuildType.DEBUG) {
Expand Down Expand Up @@ -59,6 +61,10 @@ dependencies {
api(libs.bundles.moshi)
implementation(libs.moshi)

debugImplementation(libs.chucker)
preReleaseImplementation(libs.chucker)
releaseImplementation(libs.chucker.noOp)

// Testing
testImplementation(libs.bundles.unitTest)
testImplementation(libs.test.core.ktx)
Expand Down
Loading