diff --git a/.gitea/workflows/build-android-apk.yml b/.gitea/workflows/build-android-apk.yml index 5d76cd2..40460f4 100644 --- a/.gitea/workflows/build-android-apk.yml +++ b/.gitea/workflows/build-android-apk.yml @@ -1,14 +1,6 @@ name: Build Android APK on: - push: - branches: [main, master] - paths: - - 'toju-app/**' - - 'package.json' - - 'package-lock.json' - - 'tools/build-android-apk.sh' - - '.gitea/workflows/build-android-apk.yml' workflow_dispatch: jobs: @@ -69,12 +61,39 @@ jobs: NODE_ENV: development run: npm ci + - name: Resolve release version + id: version + run: node tools/resolve-release-version.js --write-output + + - name: Ensure draft release exists + id: release + env: + GITEA_RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} + run: > + node tools/gitea-release.js ensure-draft + --server-url "${{ github.server_url }}" + --repository "${{ github.repository }}" + --tag "${{ steps.version.outputs.release_tag }}" + --target "${{ github.sha }}" + --name "${{ steps.version.outputs.release_name }}" + --body "Automated draft release from ${{ github.ref_name }} @ ${{ github.sha }}" + --write-output + - name: Build debug APK run: bash tools/build-android-apk.sh - - name: Upload APK artifact - uses: https://github.com/actions/upload-artifact@v4 - with: - name: metoyou-android-debug-apk - path: toju-app/android/app/build/outputs/apk/debug/app-debug.apk - if-no-files-found: error + - name: Stage Android APK + run: | + mkdir -p dist-android + cp toju-app/android/app/build/outputs/apk/debug/app-debug.apk \ + "dist-android/Toju-${{ steps.version.outputs.release_version }}-android-debug.apk" + + - name: Upload Android APK to draft release + env: + GITEA_RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} + run: > + node tools/gitea-release.js upload-built-assets + --server-url "${{ github.server_url }}" + --repository "${{ github.repository }}" + --release-id "${{ steps.release.outputs.release_id }}" + --dist-android dist-android diff --git a/.gitea/workflows/release-draft.yml b/.gitea/workflows/release-draft.yml index 6954ba7..f81ad6b 100644 --- a/.gitea/workflows/release-draft.yml +++ b/.gitea/workflows/release-draft.yml @@ -110,6 +110,87 @@ jobs: --dist-electron dist-electron --dist-server dist-server + build-android: + needs: prepare + runs-on: ubuntu-latest + container: node:22 + + steps: + - name: Checkout + uses: https://github.com/actions/checkout@v4 + + - name: Restore npm cache + uses: https://github.com/actions/cache@v4 + with: + path: /root/.npm + key: npm-android-${{ hashFiles('package-lock.json') }} + restore-keys: npm-android- + + - name: Restore Gradle cache + uses: https://github.com/actions/cache@v4 + with: + path: | + /root/.gradle/caches + /root/.gradle/wrapper + key: gradle-android-${{ hashFiles('toju-app/android/**/*.gradle*', 'toju-app/android/gradle/wrapper/gradle-wrapper.properties') }} + restore-keys: gradle-android- + + - name: Install Android build toolchain + run: | + apt-get update + apt-get install -y --no-install-recommends wget unzip ca-certificates gnupg + + install -d /etc/apt/keyrings + wget -qO - https://packages.adoptium.net/artifactory/api/gpg/key/public | gpg --dearmor -o /etc/apt/keyrings/adoptium.gpg + echo "deb [signed-by=/etc/apt/keyrings/adoptium.gpg] https://packages.adoptium.net/artifactory/deb bookworm main" > /etc/apt/sources.list.d/adoptium.list + apt-get update + apt-get install -y --no-install-recommends temurin-21-jdk + + export ANDROID_SDK_ROOT=/opt/android-sdk + mkdir -p "$ANDROID_SDK_ROOT/cmdline-tools" + cd /tmp + wget -q https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip + unzip -q commandlinetools-linux-11076708_latest.zip + mv cmdline-tools "$ANDROID_SDK_ROOT/cmdline-tools/latest" + export PATH="$PATH:$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$ANDROID_SDK_ROOT/platform-tools" + + yes | sdkmanager --licenses >/dev/null + sdkmanager "platform-tools" "platforms;android-36" "build-tools;35.0.0" + + echo "ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT" >> "$GITHUB_ENV" + echo "ANDROID_HOME=$ANDROID_SDK_ROOT" >> "$GITHUB_ENV" + echo "JAVA_HOME=/usr/lib/jvm/temurin-21-jdk-amd64" >> "$GITHUB_ENV" + echo "PATH=$PATH:$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$ANDROID_SDK_ROOT/platform-tools" >> "$GITHUB_ENV" + + - name: Install dependencies + env: + NODE_ENV: development + run: npm ci + + - name: Set CI release version + run: > + node tools/set-release-version.js + --version "${{ needs.prepare.outputs.release_version }}" + + - name: Build debug APK + run: bash tools/build-android-apk.sh + + - name: Stage Android APK + run: | + mkdir -p dist-android + cp toju-app/android/app/build/outputs/apk/debug/app-debug.apk \ + "dist-android/Toju-${{ needs.prepare.outputs.release_version }}-android-debug.apk" + + - name: Upload Android APK + env: + GITEA_RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} + run: > + node tools/gitea-release.js upload-built-assets + --server-url "${{ github.server_url }}" + --repository "${{ github.repository }}" + --release-id "${{ needs.prepare.outputs.release_id }}" + --dist-android dist-android + build-windows: needs: prepare runs-on: windows diff --git a/.gitignore b/.gitignore index 677b615..e1962b3 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,7 @@ Thumbs.db .env .certs/ /server/data/variables.json +/server/data/metoyou.sqlite dist-server/* doc/** diff --git a/agents-docs/ENGINEERING.md b/agents-docs/ENGINEERING.md index 3ff240d..0d884a6 100644 --- a/agents-docs/ENGINEERING.md +++ b/agents-docs/ENGINEERING.md @@ -124,7 +124,8 @@ Behavioral changes to any of these qualify as a feature-doc update under the rul - `release-draft.yml` — queues release builds on push to `main` / `master` - `publish-draft-release.yml` — publishes draft releases - `deploy-web-apps.yml` — deploys the marketing site and Docusaurus docs - - `build-android-apk.yml` — builds a debug Capacitor Android APK on push (mobile-related paths) or manual dispatch; uploads `app-debug.apk` as a workflow artifact + - `build-android-apk.yml` — manual **workflow_dispatch** debug Capacitor Android APK build; uploads `Toju--android-debug.apk` to the draft release (same path as desktop assets) + - `release-draft.yml` job `build-android` — builds and uploads the debug APK to each queued draft release alongside desktop/server archives - All checks must pass before merging a PR - Workflow status is visible in the Gitea PR view; use the web UI or `tea` CLI to inspect runs diff --git a/agents-docs/features/mobile-capacitor.md b/agents-docs/features/mobile-capacitor.md index 992cd05..c95b247 100644 --- a/agents-docs/features/mobile-capacitor.md +++ b/agents-docs/features/mobile-capacitor.md @@ -48,9 +48,11 @@ Config: `toju-app/capacitor.config.ts` (`webDir: ../dist/client/browser`). ### CI (Gitea) -Workflow `.gitea/workflows/build-android-apk.yml` runs automatically on push to `main` / `master` when mobile client paths change (`toju-app/**`, root lockfile, or the workflow itself). Use **workflow_dispatch** to build on demand from any branch. +Release workflow `.gitea/workflows/release-draft.yml` builds a debug Android APK on every push to `main` / `master` (job `build-android`), stages it as `Toju--android-debug.apk`, and uploads it to the same draft Gitea release as the desktop `.exe` / `.deb` assets via `tools/gitea-release.js`. -The job installs JDK 21 and Android SDK platform 36 inside the `node:22` container, runs `tools/build-android-apk.sh`, and uploads `metoyou-android-debug-apk` (`app-debug.apk`) as a workflow artifact. No signing keystore is configured — output is a **debug** APK suitable for sideloading and QA. +Manual-only workflow `.gitea/workflows/build-android-apk.yml` (**workflow_dispatch**) repeats the same build and release upload on demand from any branch. + +Both jobs install JDK 21 and Android SDK platform 36 inside the `node:22` container and run `tools/build-android-apk.sh`. No signing keystore is configured — output is a **debug** APK suitable for sideloading and QA. Optional `google-services.json` is not injected in CI; push registration in artifact builds follows the same optional-Firebase behavior as local unsigned debug builds. diff --git a/server/data/metoyou.sqlite b/server/data/metoyou.sqlite deleted file mode 100644 index 19bc27d..0000000 Binary files a/server/data/metoyou.sqlite and /dev/null differ diff --git a/tools/gitea-release.js b/tools/gitea-release.js index c6823da..1d1299d 100644 --- a/tools/gitea-release.js +++ b/tools/gitea-release.js @@ -424,12 +424,20 @@ function isDesktopReleaseAsset(filePath) { || lowerFileName.endsWith('.zip'); } +function isAndroidReleaseAsset(filePath) { + return path.basename(filePath).toLowerCase().endsWith('.apk'); +} + function collectBuiltAssets(args) { const distElectronDir = path.resolve(process.cwd(), args['dist-electron'] || 'dist-electron'); const distServerDir = path.resolve(process.cwd(), args['dist-server'] || 'dist-server'); + const distAndroidDir = typeof args['dist-android'] === 'string' && args['dist-android'].trim().length > 0 + ? path.resolve(process.cwd(), args['dist-android']) + : null; const files = [ ...collectTopLevelFiles(distElectronDir).filter(isDesktopReleaseAsset), - ...collectTopLevelFiles(distServerDir) + ...collectTopLevelFiles(distServerDir), + ...(distAndroidDir ? collectTopLevelFiles(distAndroidDir).filter(isAndroidReleaseAsset) : []) ]; return [...new Set(files)].sort((left, right) => left.localeCompare(right)); @@ -646,9 +654,17 @@ async function main() { } } -main().catch((error) => { - const message = error instanceof Error ? error.message : String(error); +if (require.main === module) { + main().catch((error) => { + const message = error instanceof Error ? error.message : String(error); - console.error(`[gitea-release] ${message}`); - process.exitCode = 1; -}); + console.error(`[gitea-release] ${message}`); + process.exitCode = 1; + }); +} + +module.exports = { + collectBuiltAssets, + isAndroidReleaseAsset, + isDesktopReleaseAsset +}; diff --git a/tools/gitea-release.spec.js b/tools/gitea-release.spec.js new file mode 100644 index 0000000..a0f34e8 --- /dev/null +++ b/tools/gitea-release.spec.js @@ -0,0 +1,29 @@ +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const os = require('node:os'); +const path = require('node:path'); +const { test } = require('node:test'); + +const { collectBuiltAssets, isAndroidReleaseAsset } = require('./gitea-release'); + +test('isAndroidReleaseAsset matches apk files only', () => { + assert.equal(isAndroidReleaseAsset('/tmp/Toju-1.0.5-android-debug.apk'), true); + assert.equal(isAndroidReleaseAsset('/tmp/notes.txt'), false); +}); + +test('collectBuiltAssets includes apk files from dist-android', () => { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gitea-release-')); + const distAndroid = path.join(tempDir, 'dist-android'); + + fs.mkdirSync(distAndroid); + const apkPath = path.join(distAndroid, 'Toju-1.0.5-android-debug.apk'); + fs.writeFileSync(apkPath, 'apk'); + + const files = collectBuiltAssets({ + 'dist-android': distAndroid, + 'dist-electron': path.join(tempDir, 'missing-electron'), + 'dist-server': path.join(tempDir, 'missing-server') + }); + + assert.deepEqual(files, [apkPath]); +});