on: push: branches: - dev paths-ignore: - flake.nix jobs: build-web: runs-on: docker steps: - name: Check out repo uses: https://data.forgejo.org/actions/checkout@v4 - name: Install Packages run: apt-get update && apt-get install -y sudo curl jq fakeroot dpkg rpm - name: Install Nix uses: https://github.com/cachix/install-nix-action@v30 - name: Install Bun uses: oven-sh/setup-bun@v2 - name: Install dependencies run: bun install --frozen-lockfile - name: Copy licenses run: bun run copy-licenses - name: Build packages run: bun run build:packages - name: Build web run: bun run build:web - name: Install rsync run: apt-get update && apt-get install -y rsync - name: Deploy run: rsync -a --delete apps/web/dist/ /var/lib/www/tensamin-web-dev/ build-mobile: runs-on: docker steps: - name: Check out repo uses: https://data.forgejo.org/actions/checkout@v4 - name: Install Packages run: apt-get update && apt-get install -y sudo curl jq fakeroot dpkg rpm - name: Install Nix uses: https://github.com/cachix/install-nix-action@v30 - name: Install Bun uses: oven-sh/setup-bun@v2 - name: Install dependencies run: bun install --frozen-lockfile - name: Copy licenses run: bun run copy-licenses - name: Build packages run: bun run build:packages - name: Setup Android Keystore env: KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} KEYSTORE_PROPERTIES: ${{ secrets.ANDROID_KEYSTORE_PROPERTIES }} run: | set -eu ANDROID_DIR="$PWD/apps/tauri/src-tauri/gen/android" printf '%s' "$KEYSTORE_BASE64" \ | tr -d '[:space:]' \ | base64 -d > "$ANDROID_DIR/keystore.jks" printf '%s' "$KEYSTORE_PROPERTIES" \ | sed 's|storeFile=.*|storeFile=keystore.jks|' \ | sed 's/\\n/\n/g' \ | tr -d '\r' \ > "$ANDROID_DIR/keystore.properties" test -s "$ANDROID_DIR/keystore.jks" cat "$ANDROID_DIR/keystore.properties" | sed 's/password=.*/password=***REDACTED***/' - name: Build mobile run: bun run build:mobile - name: Upload mobile artifact uses: https://data.forgejo.org/actions/upload-artifact@v3 with: name: mobile-apk path: apps/tauri/src-tauri/gen/android/app/build/outputs/apk/universal/release/app-universal-release.apk build-desktop: runs-on: docker strategy: matrix: target: [linux] steps: - name: Check out repo uses: https://data.forgejo.org/actions/checkout@v4 - name: Install Packages run: apt-get update && apt-get install -y sudo curl jq fakeroot dpkg rpm xz-utils - name: Install Bun uses: oven-sh/setup-bun@v2 - name: Install dependencies run: bun install --frozen-lockfile - name: Copy licenses run: bun run copy-licenses - name: Build packages run: bun run build:packages - name: Set Electron dev version run: | VERSION="$(node -p "require('./package.json').version")" SHORT_SHA="$(git rev-parse --short HEAD)" DEV_VERSION="$VERSION-dev-$SHORT_SHA" export DEV_VERSION node -e ' const fs = require("fs"); const path = "apps/electron/package.json"; const pkg = JSON.parse(fs.readFileSync(path, "utf8")); pkg.version = process.env.DEV_VERSION; fs.writeFileSync(path, JSON.stringify(pkg, null, 2) + "\n"); ' - name: Build Electron desktop run: bun run build:desktop - name: Upload desktop artifacts uses: https://data.forgejo.org/actions/upload-artifact@v3 with: name: electron-desktop-${{ matrix.target }} path: apps/electron/release/ release: runs-on: docker needs: [build-web, build-mobile, build-desktop] steps: - name: Check out repo uses: https://data.forgejo.org/actions/checkout@v4 with: fetch-depth: 0 - name: Install Packages run: apt-get update && apt-get install -y sudo curl jq - name: Install Bun uses: oven-sh/setup-bun@v2 - name: Install dependencies run: bun install --frozen-lockfile - name: Download mobile artifact uses: https://data.forgejo.org/actions/download-artifact@v3 with: name: mobile-apk path: apps/tauri/src-tauri/gen/android/app/build/outputs/apk/universal/release/ - name: Download desktop artifacts uses: https://data.forgejo.org/actions/download-artifact@v3 with: name: electron-desktop-linux path: apps/electron/release/ - name: Read version and hash id: version run: | VERSION="$(node -p "require('./package.json').version")" SHORT_SHA="$(git rev-parse --short HEAD)" echo "version=$VERSION" >> "$FORGEJO_OUTPUT" echo "short_sha=$SHORT_SHA" >> "$FORGEJO_OUTPUT" echo "tag=${VERSION}-dev-${SHORT_SHA}" >> "$FORGEJO_OUTPUT" echo "title=${VERSION}-dev-${SHORT_SHA}" >> "$FORGEJO_OUTPUT" - name: Copy releases env: TENSAMIN_RELEASE_VERSION: ${{ steps.version.outputs.tag }} TENSAMIN_RELEASE_TAG: ${{ steps.version.outputs.tag }} run: | ASSET_BASE_URL="${{ forgejo.api_url }}" ASSET_BASE_URL="${ASSET_BASE_URL%/api/v1}/${{ forgejo.repository }}/releases/download/${{ steps.version.outputs.tag }}" FORGEJO_RELEASE_ASSET_BASE_URL="$ASSET_BASE_URL" bun --bun run copy-releases - name: Create pre-release and upload files env: TOKEN: ${{ forgejo.token }} API: ${{ forgejo.api_url }} REPO: ${{ forgejo.repository }} SHA: ${{ forgejo.sha }} TAG: ${{ steps.version.outputs.tag }} TITLE: ${{ steps.version.outputs.title }} run: | set -eu test -d releases find releases -type f | grep -q . LATEST_PROD_TAG="$(git for-each-ref refs/tags --sort=-creatordate --format='%(refname:short)' | awk '!/-/' | head -n 1 || true)" if [ -n "$LATEST_PROD_TAG" ]; then RAW_LOG="$(git log "$LATEST_PROD_TAG"..HEAD --pretty=format:'- %s')" else RAW_LOG="$(git log --pretty=format:'- %s')" fi export RAW_LOG LATEST_PROD_TAG API REPO TAG BODY="$(node -e ' const raw = process.env.RAW_LOG; const lines = raw.split("\n"); const commits = []; for (const line of lines) { const msg = line.replace(/^- /, ""); const parts = msg.split(/(?=\([^)]+\):)/).filter(Boolean).map(s => s.trim()).filter(s => s); for (const part of parts) { commits.push(part); } } const priority = { "(feat):": 1, "(fix):": 2, "(qol):": 3 }; commits.sort((a, b) => { const tagA = a.match(/^(\([^)]+\):)/)?.[1] || ""; const tagB = b.match(/^(\([^)]+\):)/)?.[1] || ""; return (priority[tagA] || 99) - (priority[tagB] || 99); }); const log = commits.map(c => "- " + c).join("\n"); const latestTag = process.env.LATEST_PROD_TAG; if (latestTag) { const serverUrl = process.env.API.replace(/\/api\/v1.*/, ""); const compareUrl = serverUrl + "/" + process.env.REPO + "/compare/" + latestTag + "..." + process.env.TAG; console.log(log + "\n\n[View changes](" + compareUrl + ")"); } else { console.log(log); } ')" HTTP_STATUS=$(curl -s -w "%{http_code}" -o release_out.json -H "Authorization: token $TOKEN" "$API/repos/$REPO/releases/tags/$TAG") if [ "$HTTP_STATUS" = "200" ]; then echo "Release $TAG already exists." RELEASE_ID="$(jq -r .id release_out.json)" else echo "Creating new pre-release for $TAG" RELEASE_JSON="$(curl -f -sS -X POST "$API/repos/$REPO/releases" \ -H "Authorization: token $TOKEN" \ -H "Content-Type: application/json" \ -d "$(jq -n \ --arg tag "$TAG" \ --arg name "$TITLE" \ --arg body "$BODY" \ --arg target "$SHA" \ '{ tag_name: $tag, name: $name, body: $body, target_commitish: $target, draft: false, prerelease: true }')")" RELEASE_ID="$(echo "$RELEASE_JSON" | jq -r .id)" fi ASSET_BASE_URL="${API%/api/v1}/$REPO/releases/download/$TAG" export ASSET_BASE_URL node -e ' const fs = require("fs"); const path = "releases/electron-release-metadata.json"; const metadata = JSON.parse(fs.readFileSync(path, "utf8")); metadata.version = process.env.TAG; metadata.tag = process.env.TAG; for (const artifact of metadata.artifacts || []) { artifact.url = `${process.env.ASSET_BASE_URL}/${encodeURIComponent(artifact.name)}`; } fs.writeFileSync(path, `${JSON.stringify(metadata, null, 2)}\n`); ' find releases -type f -print0 | while IFS= read -r -d '' file; do name="$(basename "$file")" curl -fsS -X POST "$API/repos/$REPO/releases/$RELEASE_ID/assets?name=$name" \ -H "Authorization: token $TOKEN" \ -F "attachment=@$file" done