diff --git a/.github/actions/android/action.yml b/.github/actions/android/action.yml new file mode 100644 index 00000000..abe0e23b --- /dev/null +++ b/.github/actions/android/action.yml @@ -0,0 +1,15 @@ +name: 'Common - Android' +description: 'Common setup for Android workflows' + +runs: + using: "composite" + steps: + - name: Set up JDK + uses: actions/setup-java@v5 + with: + java-version-file: '.java-version' + distribution: 'adopt' + cache: 'gradle' + cache-dependency-path: ./gradle/libs.versions.toml + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 diff --git a/.github/actions/python/action.yml b/.github/actions/python/action.yml new file mode 100644 index 00000000..b4817b8e --- /dev/null +++ b/.github/actions/python/action.yml @@ -0,0 +1,26 @@ +name: 'Common - Python' +description: 'Common setup for Python workflows' + +inputs: + workspace: + description: 'The path to the Python project workspace' + required: false + default: '' + +runs: + using: "composite" + steps: + - name: Install poetry + shell: bash + run: pipx install poetry + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version-file: '${{ inputs.workspace }}/.python-version' + cache: 'poetry' + - name: Configure Poetry + shell: bash + run: poetry config virtualenvs.create true + - name: Install dependencies + shell: bash + run: cd ./scripts/emoji_generator && poetry install --no-interaction --no-ansi diff --git a/.github/workflows/android-ci.yml b/.github/workflows/android-ci.yml new file mode 100644 index 00000000..743edb64 --- /dev/null +++ b/.github/workflows/android-ci.yml @@ -0,0 +1,118 @@ +name: Android CI + +on: + push: + branches: + - develop + paths-ignore: + - ".github/actions/python/**" + - ".github/workflows/python-ci.yml" + - "app/src/main/**" + - "scripts/emoji_generator/**" + pull_request: + branches: + - develop + paths-ignore: + - ".github/actions/python/**" + - ".github/workflows/python-ci.yml" + - "app/src/main/**" + - "scripts/emoji_generator/**" + +env: + CI: true + +jobs: + wrapper-validation: + name: Wrapper Validation + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: gradle/actions/wrapper-validation@v4 + + spotless: + name: Spotless Check + needs: wrapper-validation + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v5 + - name: Run Setup + uses: ./.github/actions/android + - name: Run spotless check + run: ./gradlew spotlessCheck + + gradle-dokka: + name: Generate and deploy docs + needs: wrapper-validation + if: github.event_name == 'push' + runs-on: ubuntu-latest + steps: + - uses: actions/create-github-app-token@v2 + id: app-token + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + - name: Checkout repository + uses: actions/checkout@v5 + - name: Run Setup + uses: ./.github/actions/android + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Generate docs with dokka + run: ./gradlew dokkaHtmlMultiModule + - name: Deploy 🚀 + uses: JamesIves/github-pages-deploy-action@v4.7.3 + with: + branch: docs # The branch the action should deploy to. + folder: dokka-docs # The folder the action should deploy. + token: ${{ steps.app-token.outputs.token }} + + unit-test: + name: Unit Tests + needs: [wrapper-validation, spotless] + runs-on: ubuntu-latest + steps: + - uses: actions/create-github-app-token@v2 + id: app-token + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + - name: Checkout repository + uses: actions/checkout@v5 + - name: Run Setup + uses: ./.github/actions/android + - name: Run tests + run: | + ./gradlew emojify:preTest + ./gradlew emojify:test --stacktrace + ./gradlew emojify:postTest + - name: Publish Test Report + uses: mikepenz/action-junit-report@v5 + if: always() + with: + report_paths: '**/build/test-results/**/TEST-*.xml' + token: ${{ steps.app-token.outputs.token }} + + publish-artifact: + name: Publish Artifact + needs: unit-test + if: github.event_name == 'push' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v5 + - name: Run Setup + uses: ./.github/actions/android + - name: Run build + run: | + ./gradlew emojify:preTest + ./gradlew clean build --stacktrace + ./gradlew emojify:postTest + - name: Publish to Local Maven + run: | + ./gradlew publishMavenPublicationToMavenLocal + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: publications + path: ~/.m2/repository/io/wax911/emoji/ \ No newline at end of file diff --git a/.github/workflows/android-publish-artifact.yml b/.github/workflows/android-publish-artifact.yml deleted file mode 100644 index b3914c6a..00000000 --- a/.github/workflows/android-publish-artifact.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: android-publish-artifact - -on: - push: - branches: [ develop ] - paths-ignore: - - "app/src/main/**" - -permissions: - checks: write - -jobs: - publish: - runs-on: ubuntu-latest - steps: - - uses: actions/create-github-app-token@v2 - id: app-token - with: - app-id: ${{ secrets.APP_ID }} - private-key: ${{ secrets.APP_PRIVATE_KEY }} - - uses: actions/checkout@v5 - - uses: gradle/wrapper-validation-action@v3 - - name: set up JDK 17 - uses: actions/setup-java@v5 - with: - java-version: '17' - distribution: 'adopt' - - name: Wait for tests to succeed - uses: lewagon/wait-on-check-action@v1.4.1 - with: - ref: ${{ github.ref }} - running-workflow-name: android-publish-artifact - check-name: unit - repo-token: ${{ steps.app-token.outputs.token }} - wait-interval: 20 - - uses: gradle/actions/setup-gradle@v4 - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - name: Build artifacts - run: | - ./gradlew emojify:preTest - ./gradlew clean build - ./gradlew emojify:postTest - - name: Publish to local maven - run: | - ./gradlew publishMavenPublicationToMavenLocal - - name: Upload artifacts - uses: actions/upload-artifact@v4 - with: - name: publications - path: ~/.m2/repository/io/wax911/emoji/ diff --git a/.github/workflows/android-spotless.yml b/.github/workflows/android-spotless.yml deleted file mode 100644 index 4b59e9d3..00000000 --- a/.github/workflows/android-spotless.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: android-spotless - -on: - push: - branches: [ develop ] - pull_request: - branches: - - develop - -jobs: - spotless: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - name: set up JDK 17 - uses: actions/setup-java@v5 - with: - java-version: '17' - distribution: 'adopt' - - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - name: Run spotless check - run: ./gradlew spotlessCheck diff --git a/.github/workflows/android-unit-test.yml b/.github/workflows/android-unit-test.yml deleted file mode 100644 index 82e19828..00000000 --- a/.github/workflows/android-unit-test.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: android-unit-test - -on: - push: - branches: [ develop ] - pull_request: - branches: - - develop - paths-ignore: - - "app/src/main/**" - - "scripts/emoji_generator/**" - -permissions: - checks: write - -jobs: - unit: - runs-on: ubuntu-latest - steps: - - uses: actions/create-github-app-token@v2 - id: app-token - with: - app-id: ${{ secrets.APP_ID }} - private-key: ${{ secrets.APP_PRIVATE_KEY }} - - uses: actions/checkout@v5 - - uses: gradle/actions/setup-gradle@v4 - - name: set up JDK 21 - uses: actions/setup-java@v5 - with: - java-version: 21 - distribution: 'adopt' - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - name: Run tests - run: | - ./gradlew emojify:preTest - ./gradlew emojify:test --stacktrace - ./gradlew emojify:postTest - - name: Publish Test Report - uses: mikepenz/action-junit-report@v5 - if: always() # always run even if the previous step fails - with: - report_paths: '**/build/test-results/**/TEST-*.xml' - token: ${{ steps.app-token.outputs.token }} diff --git a/.github/workflows/auto-approve.yml b/.github/workflows/auto-approve.yml index 6af00c36..d45f1294 100644 --- a/.github/workflows/auto-approve.yml +++ b/.github/workflows/auto-approve.yml @@ -1,4 +1,4 @@ -name: auto-approve +name: Auto Approve on: pull_request_target: diff --git a/.github/workflows/emoji-generator-ci.yml b/.github/workflows/emoji-generator-ci.yml deleted file mode 100644 index 0d57867e..00000000 --- a/.github/workflows/emoji-generator-ci.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: emoji-generator-ci - -on: - push: - branches: [ develop ] - paths: - - 'scripts/emoji_generator/**' - - '.github/workflows/emoji-generator-ci.yml' - pull_request: - branches: [ develop ] - paths: - - 'scripts/emoji_generator/**' - - '.github/workflows/emoji-generator-ci.yml' - -jobs: - lint-and-test: - runs-on: ubuntu-latest - defaults: - run: - working-directory: ./scripts/emoji_generator - - strategy: - matrix: - python-version: ["3.13.2"] - - steps: - - uses: actions/checkout@v5 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v6 - with: - python-version: ${{ matrix.python-version }} - - - name: Install Poetry - uses: snok/install-poetry@v1 - with: - version: latest - virtualenvs-create: true - virtualenvs-in-project: true - - - name: Load cached venv - id: cached-poetry-dependencies - uses: actions/cache@v4 - with: - path: ./scripts/emoji_generator/.venv - key: venv-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }} - - - name: Install dependencies - if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' - run: poetry install --no-interaction --no-ansi - - - name: Run linter (Ruff) - run: poetry run ruff check . - - - name: Check code formatting (Ruff) - run: poetry run ruff format --check . - - - name: Run type checker (mypy) - run: poetry run poetry run mypy . - - - name: Run tests - run: poetry run pytest -v diff --git a/.github/workflows/first-time-contributer-greeting.yml b/.github/workflows/first-interaction.yml similarity index 93% rename from .github/workflows/first-time-contributer-greeting.yml rename to .github/workflows/first-interaction.yml index 4c9a7174..be9209b3 100644 --- a/.github/workflows/first-time-contributer-greeting.yml +++ b/.github/workflows/first-interaction.yml @@ -1,9 +1,10 @@ -name: greeting-first-time-contributor +name: Greeting on: [ pull_request_target, issues ] jobs: - greeting: + first-interaction: + name: Greet new contributors runs-on: ubuntu-latest permissions: issues: write diff --git a/.github/workflows/gradle-dokka.yml b/.github/workflows/gradle-dokka.yml deleted file mode 100644 index be3b32f9..00000000 --- a/.github/workflows/gradle-dokka.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: gradle-dokka - -on: - push: - branches: [ develop ] - -env: - CI: true - -jobs: - gradle-dokka: - runs-on: ubuntu-latest - steps: - - uses: actions/create-github-app-token@v2 - id: app-token - with: - app-id: ${{ secrets.APP_ID }} - private-key: ${{ secrets.APP_PRIVATE_KEY }} - - uses: actions/checkout@v5 - - name: set up JDK 17 - uses: actions/setup-java@v5 - with: - java-version: 17 - distribution: 'adopt' - - name: Setup Gradle - uses: gradle/gradle-build-action@v3 - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - name: Generate docs with dokka - run: ./gradlew dokkaHtmlMultiModule - - - name: Deploy 🚀 - uses: JamesIves/github-pages-deploy-action@v4.7.3 - with: - branch: docs # The branch the action should deploy to. - folder: dokka-docs # The folder the action should deploy. - token: ${{ steps.app-token.outputs.token }} diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml deleted file mode 100644 index 78b0e129..00000000 --- a/.github/workflows/gradle-wrapper-validation.yml +++ /dev/null @@ -1,10 +0,0 @@ -name: gradle-wrapper-validation -on: [ push, pull_request ] - -jobs: - validation: - name: "Validation" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - uses: gradle/actions/wrapper-validation@v4 diff --git a/.github/workflows/emoji-data-updater.yml b/.github/workflows/python-cd.yml similarity index 79% rename from .github/workflows/emoji-data-updater.yml rename to .github/workflows/python-cd.yml index cce584e7..be04a14a 100644 --- a/.github/workflows/emoji-data-updater.yml +++ b/.github/workflows/python-cd.yml @@ -1,12 +1,17 @@ -name: emoji-data-updater +name: Python CD on: workflow_dispatch: schedule: - cron: "0 2 * * 5" +env: + PYTHONUNBUFFERED: 1 + WORKSPACE: ./scripts/emoji_generator + jobs: update-emoji-json: + name: Update emoji.json runs-on: ubuntu-latest env: VERSION: "" @@ -20,8 +25,12 @@ jobs: with: app-id: ${{ secrets.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} - - name: Checkout Repository + - name: Checkout repository uses: actions/checkout@v5 + - name: Run Setup + uses: ./.github/actions/python + with: + workspace: ${{ env.WORKSPACE }} - name: Fetch Latest Emojibase Version id: fetch_version run: | @@ -31,27 +40,14 @@ jobs: echo "VERSION=$(echo \"$LATEST_RELEASE\" | grep -oP '(?<=emojibase-data@)\d+\.\d+\.\d+')" >> $GITHUB_ENV echo "RELEASE_URL=$RELEASE_URL" >> $GITHUB_ENV + # This step should preferably check if the version of emojibase-data exists on unicode.org + # and only run this step if it does exist. Otherwise we should stop the workflow here. - name: Download emoji-test data run: | curl -s -o ../../emojify/src/test/resources/io/wax911/emojify/core/emoji-test.txt https://unicode.org/Public/emoji/latest/emoji-test.txt - - name: Install poetry - run: pipx install poetry - - - name: Setup python - uses: actions/setup-python@v6 - with: - python-version-file: 'scripts/emoji_generator/pyproject.toml' - cache: 'poetry' - - - name: Install Dependencies - run: | - poetry config virtualenvs.create true - poetry install --no-root - - name: Run Emoji Update Script env: - PYTHONUNBUFFERED: 1 EMOJI_VERSION: ${{ env.VERSION }} run: poetry run emoji-generator @@ -68,6 +64,3 @@ jobs: author: GitHub delete-branch: true token: ${{ steps.app-token.outputs.token }} - labels: | - dependencies - feature diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml new file mode 100644 index 00000000..6ac234c8 --- /dev/null +++ b/.github/workflows/python-ci.yml @@ -0,0 +1,139 @@ +name: Python CI + +on: + push: + branches: [develop] + paths: + - ".github/actions/python/**" + - ".github/workflows/python-ci.yml" + - "scripts/emoji_generator/**" + pull_request: + branches: [develop] + paths: + - ".github/actions/python/**" + - ".github/workflows/python-ci.yml" + - "scripts/emoji_generator/**" + +env: + PYTHONUNBUFFERED: 1 + WORKSPACE: ./scripts/emoji_generator + +jobs: + dependency-review: + name: Dependency Review + if: >- + github.event_name == 'pull_request' || + github.event_name == 'pull_request_target' || + github.event_name == 'merge_group' + permissions: + contents: read + runs-on: ubuntu-latest + defaults: + run: + working-directory: ${{ env.WORKSPACE }} + steps: + - uses: actions/checkout@v5 + - name: "Dependency Review" + uses: actions/dependency-review-action@v4 + with: + comment-summary-in-pr: on-failure + fail-on-severity: moderate + + analyze: + name: CodeQL Analysis + needs: dependency-review + if: ${{ always() && needs.dependency-review.result != 'failure' }} + runs-on: ubuntu-latest + defaults: + run: + working-directory: ${{ env.WORKSPACE }} + + permissions: + actions: read + contents: read + security-events: write + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: python + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + + lint-check: + name: Lint Check + needs: dependency-review + if: ${{ always() && needs.dependency-review.result != 'failure' }} + runs-on: ubuntu-latest + defaults: + run: + working-directory: ${{ env.WORKSPACE }} + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + - name: Run Setup + uses: ./.github/actions/python + with: + workspace: ${{ env.WORKSPACE }} + - name: Run linter (Ruff) + run: poetry run ruff check . + + format-check: + name: Format Check + needs: dependency-review + if: ${{ always() && needs.dependency-review.result != 'failure' }} + runs-on: ubuntu-latest + defaults: + run: + working-directory: ${{ env.WORKSPACE }} + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + - name: Run Setup + uses: ./.github/actions/python + with: + workspace: ${{ env.WORKSPACE }} + - name: Check code formatting (Ruff) + run: poetry run ruff format --check . + + type-check: + name: Type Check + needs: dependency-review + if: ${{ always() && needs.dependency-review.result != 'failure' }} + runs-on: ubuntu-latest + defaults: + run: + working-directory: ${{ env.WORKSPACE }} + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + - name: Run Setup + uses: ./.github/actions/python + with: + workspace: ${{ env.WORKSPACE }} + - name: Run type checker (mypy) + run: poetry run mypy . + + test: + name: Python Tests + needs: [analyze, lint-check, format-check, type-check] + runs-on: ubuntu-latest + defaults: + run: + working-directory: ${{ env.WORKSPACE }} + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + - name: Run Setup + uses: ./.github/actions/python + with: + workspace: ${{ env.WORKSPACE }} + - name: Run tests + run: poetry run pytest -v diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index 0ec52435..59e30ced 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -1,4 +1,4 @@ -name: release-drafter +name: Release Drafter on: push: diff --git a/.github/workflows/version-updater.yml b/.github/workflows/version-updater.yml index 97036f2b..90228271 100644 --- a/.github/workflows/version-updater.yml +++ b/.github/workflows/version-updater.yml @@ -1,4 +1,4 @@ -name: version-update +name: Version Updater on: repository_dispatch: diff --git a/.java-version b/.java-version new file mode 100644 index 00000000..aabe6ec3 --- /dev/null +++ b/.java-version @@ -0,0 +1 @@ +21 diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..727f0cc2 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,33 @@ +# Repository Guidelines + +## Project Structure & Module Organization +- Multi-module Gradle project; library code lives in `emojify`, shared interfaces in `contract`, and serializers in `serializer/{kotlinx,gson,moshi}`. +- Sample app under `app` is included for local debugging only (not in CI); keep feature work in library modules first. +- Kotlin sources are in `src//kotlin`, tests in `src/test/kotlin`, and emoji assets in `emojify/src/main/assets/emoticons` (copied into tests via Gradle tasks). +- Common build logic is centralized in `buildSrc`, and formatting headers sit in `spotless/`. + +## Build, Test, and Development Commands +- `./gradlew :emojify:assemble` builds the core AAR; use `:contract:assemble` or serializer variants as needed. +- `./gradlew emojify:preTest emojify:test emojify:postTest` runs unit tests with the required emoji payloads; chain the tasks exactly when running locally or in CI. +- `./gradlew spotlessCheck` (or `spotlessApply`) enforces formatting and headers. +- `./gradlew :app:installDebug` deploys the sample client when the `app` module is included outside CI. + +## Coding Style & Naming Conventions +- Kotlin 2.1+ with JDK 21 target; follow standard Kotlin style defined in `.editorconfig`. +- Spotless + Ktlint guard formatting on library modules; do not edit generated headers under `spotless/`. +- Use PascalCase for classes, camelCase for functions/properties, and UPPER_SNAKE_CASE for constants; package names remain lowercase under `io.wax911.emojify`. + +## Testing Guidelines +- Unit tests use JUnit4 in `emojify/src/test/kotlin`; mirror the production package, and prefer descriptive names like `functionUnderTest_expectedResult`. +- Keep emoji fixture updates in `emojify/src/main/assets/emoticons/emoji.json`; ensure new cases run via `preTest` before committing. +- Add regression coverage for new serializers under their respective modules, using mock payloads where possible. + +## Commit & Pull Request Guidelines +- Follow Conventional Commits as seen in history (`fix(deps): …`, `chore(build): …`); scope with the touched module when it clarifies impact. +- Before raising a PR, run `./gradlew spotlessCheck` and the emojify test pipeline locally, and attach outputs for failures. +- Reference linked issues, describe the emoji scenarios impacted, and include sample app screenshots/GIFs when UI behavior changes. +- Small, focused PRs merge faster; prefer follow-up issues for unrelated refactors or bulk dependency bumps. + +## Emoji Data & Tooling Notes +- Use `scripts/emoji_generator` when updating bundled datasets; document source versions in the PR description. +- Verify serializer parity (`kotlinx`, `gson`, `moshi`) whenever schema changes land, and update README badges if release artifacts move.