diff --git a/.github/workflows/build-tauri.yml b/.github/workflows/build-tauri.yml deleted file mode 100644 index 7fa0ae7e8..000000000 --- a/.github/workflows/build-tauri.yml +++ /dev/null @@ -1,288 +0,0 @@ -name: Build Tauri - -on: - push: - branches: [master] - tags: - - v* - pull_request: - branches: [master] - -jobs: - build: - name: ${{ matrix.os }}, py-${{ matrix.python_version }}, node-${{ matrix.node_version }} - runs-on: ${{ matrix.os }} - continue-on-error: ${{ matrix.experimental }} - env: - # Whether to build and include extras (like aw-notify and aw-watcher-input) - AW_EXTRAS: true - TAURI_BUILD: true - # sets the macOS version target, see: https://users.rust-lang.org/t/compile-rust-binary-for-older-versions-of-mac-osx/38695 - MACOSX_DEPLOYMENT_TARGET: 10.9 - defaults: - run: - shell: bash - strategy: - fail-fast: false - max-parallel: 5 - matrix: - os: - [ - ubuntu-24.04, - ubuntu-24.04-arm, - windows-latest, - macos-latest, - ] - python_version: [3.9] - node_version: [22] - skip_rust: [false] - skip_webui: [false] - experimental: [false] - - steps: - - uses: actions/checkout@v6 - with: - submodules: "recursive" - fetch-depth: 0 - - - name: Set environment variables - run: | - echo "RELEASE=${{ startsWith(github.ref_name, 'v') || github.ref_name == 'master' }}" >> $GITHUB_ENV - echo "TAURI_BUILD=true" >> $GITHUB_ENV - - - name: Set tag metadata - if: startsWith(github.ref, 'refs/tags/v') - run: | - echo "VERSION_TAG=${GITHUB_REF_NAME}" >> $GITHUB_ENV - - - name: Determine and output version - run: | - VERSION_WITH_V=$(bash scripts/package/getversion.sh) - VERSION_NO_V="${VERSION_WITH_V#v}" - echo "VERSION_WITH_V=${VERSION_WITH_V}" >> $GITHUB_ENV - echo "========================================" - echo "Build Version Information (Tauri)" - echo "========================================" - echo "GitHub ref: ${{ github.ref }}" - echo "GitHub ref_name: ${{ github.ref_name }}" - echo "Version (with v): ${VERSION_WITH_V}" - echo "Version (no v): ${VERSION_NO_V}" - echo "========================================" - - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: ${{ matrix.python_version }} - - - name: Set up Node - if: ${{ !matrix.skip_webui }} - uses: actions/setup-node@v6 - with: - node-version: ${{ matrix.node_version }} - - - name: Set up Rust - if: ${{ !matrix.skip_rust }} - uses: dtolnay/rust-toolchain@master - id: toolchain - with: - toolchain: stable - - - name: Cache node_modules - uses: actions/cache@v5 - if: ${{ !matrix.skip_webui }} - with: - path: | - aw-server-rust/aw-webui/node_modules - aw-tauri/node_modules - key: ${{ matrix.os }}-node_modules-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ matrix.os }}-node_modules- - - - name: Cache cargo build - uses: actions/cache@v5 - env: - cache-name: cargo-build-target - with: - path: | - aw-server-rust/target - aw-tauri/src-tauri/target - key: ${{ matrix.os }}-${{ env.cache-name }}-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('**/Cargo.lock') }} - restore-keys: | - ${{ matrix.os }}-${{ env.cache-name }}-${{ steps.toolchain.outputs.rustc_hash }}- - - - name: Install APT dependencies - if: runner.os == 'Linux' - run: | - sudo apt-get update - sudo apt-get install -y \ - libgtk-3-dev \ - libayatana-appindicator3-dev \ - librsvg2-dev \ - libsoup-3.0-dev \ - xdg-utils - # Pin WebKit to avoid blank-webview regression on newer ubuntu-24.04 packages - # See: ActivityWatch/aw-tauri#99 - sudo apt-get install -y \ - libwebkit2gtk-4.1-0=2.44.0-2 \ - libwebkit2gtk-4.1-dev=2.44.0-2 \ - libjavascriptcoregtk-4.1-0=2.44.0-2 \ - libjavascriptcoregtk-4.1-dev=2.44.0-2 \ - gir1.2-javascriptcoregtk-4.1=2.44.0-2 \ - gir1.2-webkit2-4.1=2.44.0-2 - - - name: Install dependencies - run: | - if [ "$RUNNER_OS" == "Windows" ]; then - choco install innosetup - fi - pip3 install poetry==1.4.2 - - - name: Build - uses: nick-fields/retry@v4 - with: - timeout_minutes: 60 - max_attempts: 3 - shell: bash - command: | - python3 -m venv venv - source venv/bin/activate || source venv/Scripts/activate - poetry install - make build SKIP_WEBUI=${{ matrix.skip_webui }} SKIP_SERVER_RUST=${{ matrix.skip_rust }} - pip freeze - - - name: Run tests - uses: nick-fields/retry@v4 - with: - timeout_minutes: 60 - max_attempts: 3 - shell: bash - command: | - source venv/bin/activate || source venv/Scripts/activate - make test SKIP_SERVER_RUST=${{ matrix.skip_rust }} - - - name: Package - run: | - source venv/bin/activate || source venv/Scripts/activate - poetry install - make package SKIP_SERVER_RUST=${{ matrix.skip_rust }} - - - name: Package Linux (Tauri bundles) - if: runner.os == 'Linux' - run: | - # aw-tauri's bundler produces .AppImage/.deb/.rpm under - # dist/activitywatch/aw-tauri/ via `make package`. Copy them out with - # versioned, arch-suffixed filenames so they match the - # `dist/activitywatch-*.*` upload pattern below. - ARCH=$(uname -m) - shopt -s nullglob - for ext in AppImage deb rpm; do - files=( dist/activitywatch/aw-tauri/*.${ext} ) - case ${#files[@]} in - 0) continue ;; - 1) cp -v "${files[0]}" "dist/activitywatch-tauri-${VERSION_WITH_V}-linux-${ARCH}.${ext}" ;; - *) echo "ERROR: expected at most 1 .${ext} bundle, found ${#files[@]}" >&2; exit 1 ;; - esac - done - - - name: Package dmg - if: runner.os == 'macOS' - run: | - if [ -n "$APPLE_EMAIL" ]; then - ./scripts/ci/import-macos-p12.sh - fi - - source venv/bin/activate - make dist/ActivityWatch.dmg - - if [ -n "$APPLE_EMAIL" ]; then - codesign --force --verbose --timestamp -s ${APPLE_PERSONALID} dist/ActivityWatch.dmg - - brew install akeru-inc/tap/xcnotary - xcnotary precheck dist/ActivityWatch.app - xcnotary precheck dist/ActivityWatch.dmg - - make dist/notarize - fi - mv dist/ActivityWatch.dmg dist/activitywatch-tauri-${VERSION_WITH_V}-macos-$(uname -m).dmg - env: - APPLE_EMAIL: ${{ secrets.APPLE_EMAIL }} - APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} - APPLE_PERSONALID: ${{ secrets.APPLE_TEAMID }} - APPLE_TEAMID: ${{ secrets.APPLE_TEAMID }} - CERTIFICATE_MACOS_P12_BASE64: ${{ secrets.CERTIFICATE_MACOS_P12_BASE64 }} - CERTIFICATE_MACOS_P12_PASSWORD: ${{ secrets.CERTIFICATE_MACOS_P12_PASSWORD }} - - - name: Upload packages - uses: actions/upload-artifact@v7 - with: - name: builds-tauri-${{ matrix.os }}-py${{ matrix.python_version }} - path: dist/activitywatch-*.* - - release-notes: - runs-on: ubuntu-latest - if: startsWith(github.ref, 'refs/tags/v') - steps: - - uses: actions/checkout@v6 - with: - submodules: "recursive" - fetch-depth: 0 - - - uses: ActivityWatch/check-version-format-action@v2 - id: version - with: - prefix: "v" - - - name: Echo version - run: | - echo "${{ steps.version.outputs.full }} (stable: ${{ steps.version.outputs.is_stable }})" - - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: "3.12" - - - name: Install deps - run: | - pip install requests - - - name: Generate release notes - run: | - LAST_RELEASE=`STABLE_ONLY=${{ steps.version.output.is_stable }} ./scripts/get_latest_release.sh` - ./scripts/build_changelog.py --range "$LAST_RELEASE...${{ steps.version.outputs.full }}" - - - name: Rename - run: | - mv changelog.md release_notes.md - - - name: Upload release notes - uses: actions/upload-artifact@v7 - with: - name: release_notes_tauri - path: release_notes.md - - release: - needs: [build, release-notes] - if: startsWith(github.ref, 'refs/tags/v') - runs-on: ubuntu-latest - steps: - - name: Download build artifacts - uses: actions/download-artifact@v8 - with: - path: dist - - - name: Display structure of downloaded files - run: ls -R - working-directory: dist - - - uses: ActivityWatch/check-version-format-action@v2 - id: version - with: - prefix: "v" - - - name: Release - uses: softprops/action-gh-release@v3 - with: - draft: true - files: dist/*/activitywatch-*.* - body_path: dist/release_notes_tauri/release_notes.md - prerelease: ${{ !(steps.version.outputs.is_stable == 'true') }} # must compare to true, since boolean outputs are actually just strings, and "false" is truthy since it's not empty: https://github.com/actions/runner/issues/1483#issuecomment-994986996 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 1a8d7df16..000000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,292 +0,0 @@ -name: Build - -on: - push: - branches: [ master ] - tags: - - v* - pull_request: - branches: [ master ] - #release: - # types: [published] - -jobs: - build: - name: ${{ matrix.os }}, py-${{ matrix.python_version }}, node-${{ matrix.node_version }} - runs-on: ${{ matrix.os }} - continue-on-error: ${{ matrix.experimental }} - env: - # Whether to build and include extras (like aw-notify and aw-watcher-input) - AW_EXTRAS: true - # sets the macOS version target, see: https://users.rust-lang.org/t/compile-rust-binary-for-older-versions-of-mac-osx/38695 - MACOSX_DEPLOYMENT_TARGET: 10.9 - defaults: - run: - shell: bash - strategy: - fail-fast: false - matrix: - os: [ubuntu-24.04, windows-latest, macos-latest] - python_version: [3.9] - node_version: [22] - skip_rust: [false] - skip_webui: [false] - experimental: [false] - - #include: - # - os: ubuntu-latest - # python_version: 3.9 - # node_version: 20 - # experimental: true - - steps: - - uses: actions/checkout@v6 - with: - submodules: 'recursive' - fetch-depth: 0 # fetch all branches and tags - - # Build in release mode if: (longer build times) - # - on a tag (release) - # - on the master branch (nightly) - - name: Set RELEASE - run: | - echo "RELEASE=${{ startsWith(github.ref_name, 'v') || github.ref_name == 'master' }}" >> $GITHUB_ENV - - - name: Set tag metadata - if: startsWith(github.ref, 'refs/tags/v') - run: | - echo "VERSION_TAG=${GITHUB_REF_NAME}" >> $GITHUB_ENV - - - name: Determine and output version - run: | - VERSION_WITH_V=$(bash scripts/package/getversion.sh) - VERSION_NO_V="${VERSION_WITH_V#v}" - echo "VERSION_WITH_V=${VERSION_WITH_V}" >> $GITHUB_ENV - echo "========================================" - echo "Build Version Information" - echo "========================================" - echo "GitHub ref: ${{ github.ref }}" - echo "GitHub ref_name: ${{ github.ref_name }}" - echo "Version (with v): ${VERSION_WITH_V}" - echo "Version (no v): ${VERSION_NO_V}" - echo "========================================" - - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: ${{ matrix.python_version }} - - - name: Set up Node - if: ${{ !matrix.skip_webui }} - uses: actions/setup-node@v6 - with: - node-version: ${{ matrix.node_version }} - - - name: Set up Rust - if: ${{ !matrix.skip_rust }} - uses: dtolnay/rust-toolchain@master - id: toolchain - with: - toolchain: stable - - - name: Cache node_modules - uses: actions/cache@v5 - if: ${{ !matrix.skip_webui }} - with: - path: aw-server-rust/aw-webui/node_modules - key: ${{ matrix.os }}-node_modules-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ matrix.os }}-node_modules- - - - name: Cache cargo build - uses: actions/cache@v5 - # if: ${{ !matrix.skip_rust && (runner.os != 'macOS') }} # cache doesn't seem to behave nicely on macOS, see: https://github.com/ActivityWatch/aw-server-rust/issues/180 - env: - cache-name: cargo-build-target - with: - path: aw-server-rust/target - # key needs to contain rustc_hash due to https://github.com/ActivityWatch/aw-server-rust/issues/180 - key: ${{ matrix.os }}-${{ env.cache-name }}-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('**/Cargo.lock') }} - restore-keys: | - ${{ matrix.os }}-${{ env.cache-name }}-${{ steps.toolchain.outputs.rustc_hash }}- - - - name: Install APT dependencies - if: runner.os == 'Linux' - run: | - sudo apt-get update - # Unsure which of these are actually necessary... - sudo apt-get install -y \ - appstream \ - qt5-qmake \ - qtbase5-dev \ - qtwayland5 \ - libqt5x11extras5 \ - libfontconfig1 \ - libxcb1 \ - libfontconfig1-dev \ - libfreetype6-dev \ - libx11-dev \ - libxcursor-dev \ - libxext-dev \ - libxfixes-dev \ - libxft-dev \ - libxi-dev \ - libxrandr-dev \ - libxrender-dev - - - name: Install dependencies - run: | - if [ "$RUNNER_OS" == "Windows" ]; then - choco install innosetup - fi - pip3 install poetry==1.4.2 - - - name: Build - run: | - python3 -m venv venv - source venv/bin/activate || source venv/Scripts/activate - poetry install - make build SKIP_WEBUI=${{ matrix.skip_webui }} SKIP_SERVER_RUST=${{ matrix.skip_rust }} - pip freeze # output Python packages, useful for debugging dependency versions - - - name: Run tests - run: | - source venv/bin/activate || source venv/Scripts/activate - make test SKIP_SERVER_RUST=${{ matrix.skip_rust }} - - # Don't run integration tests on Windows, doesn't work for some reason - - name: Run integration tests - if: runner.os != 'Windows' - run: | - source venv/bin/activate || source venv/Scripts/activate - make test-integration - - - name: Package - run: | - source venv/bin/activate || source venv/Scripts/activate - poetry install # run again to ensure we have the correct version of PyInstaller - make package SKIP_SERVER_RUST=${{ matrix.skip_rust }} - - - name: Package dmg - if: runner.os == 'macOS' - run: | - # Load certificates - # Only load key & sign if env vars for signing exists - if [ -n "$APPLE_EMAIL" ]; then - ./scripts/ci/import-macos-p12.sh - fi - - # Build .app and .dmg - source venv/bin/activate - make dist/ActivityWatch.dmg - - # codesign and notarize - if [ -n "$APPLE_EMAIL" ]; then - codesign --force --verbose --timestamp -s ${APPLE_PERSONALID} dist/ActivityWatch.dmg - - # Run prechecks - brew install akeru-inc/tap/xcnotary - xcnotary precheck dist/ActivityWatch.app - xcnotary precheck dist/ActivityWatch.dmg - - # Notarize - make dist/notarize - fi - mv dist/ActivityWatch.dmg dist/activitywatch-${VERSION_WITH_V}-macos-$(uname -m).dmg - env: - APPLE_EMAIL: ${{ secrets.APPLE_EMAIL }} - APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} - APPLE_PERSONALID: ${{ secrets.APPLE_TEAMID }} # APPLE_PERSONAL_ID == APPLE_TEAM_ID for personal accounts - APPLE_TEAMID: ${{ secrets.APPLE_TEAMID }} - CERTIFICATE_MACOS_P12_BASE64: ${{ secrets.CERTIFICATE_MACOS_P12_BASE64 }} - CERTIFICATE_MACOS_P12_PASSWORD: ${{ secrets.CERTIFICATE_MACOS_P12_PASSWORD }} - - - name: Package AppImage - if: startsWith(runner.os, 'linux') - run: | - ./scripts/package/package-appimage.sh - - - name: Package deb - if: startsWith(runner.os, 'linux') - run: | - # The entire process is deferred to a shell file for consistency. - ./scripts/package/package-deb.sh - - - name: Upload packages - uses: actions/upload-artifact@v7 - with: - name: builds-${{ matrix.os }}-py${{ matrix.python_version }} - path: dist/activitywatch-*.* - - release-notes: - runs-on: ubuntu-latest - if: startsWith(github.ref, 'refs/tags/v') # only on runs triggered from tag - steps: - - uses: actions/checkout@v6 - with: - submodules: 'recursive' - fetch-depth: 0 # fetch all branches and tags - - - uses: ActivityWatch/check-version-format-action@v2 - id: version - with: - prefix: 'v' - - - name: Echo version - run: | - echo "${{ steps.version.outputs.full }} (stable: ${{ steps.version.outputs.is_stable }})" - - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: "3.12" - - - name: Install deps - run: | - pip install requests - - - name: Generate release notes - run: | - LAST_RELEASE=`STABLE_ONLY=${{ steps.version.output.is_stable }} ./scripts/get_latest_release.sh` - ./scripts/build_changelog.py --range "$LAST_RELEASE...${{ steps.version.outputs.full }}" - - # TODO: Move rename build_changelog and move into there - - name: Rename - run: | - mv changelog.md release_notes.md - - - name: Upload release notes - uses: actions/upload-artifact@v7 - with: - name: release_notes - path: release_notes.md - - release: - needs: [build, release-notes] - if: startsWith(github.ref, 'refs/tags/v') # only run on tag - runs-on: ubuntu-latest - steps: - # Will download all artifacts to path - - name: Download build artifacts - uses: actions/download-artifact@v8 - with: - path: dist - - - name: Display structure of downloaded files - run: ls -R - working-directory: dist - - # detect if version tag is stable/beta - - uses: ActivityWatch/check-version-format-action@v2 - id: version - with: - prefix: 'v' - - # create a release - - name: Release - uses: softprops/action-gh-release@v3 - with: - draft: true - files: dist/*/activitywatch-*.* - body_path: dist/release_notes/release_notes.md - prerelease: ${{ !(steps.version.outputs.is_stable == 'true') }} # must compare to true, since boolean outputs are actually just strings, and "false" is truthy since it's not empty: https://github.com/actions/runner/issues/1483#issuecomment-994986996 diff --git a/.github/workflows/dev-release.yml b/.github/workflows/dev-release.yml deleted file mode 100644 index bb130d016..000000000 --- a/.github/workflows/dev-release.yml +++ /dev/null @@ -1,218 +0,0 @@ -name: Create dev release - -# Create prerelease tags on a schedule (every other Thursday) or manually. -# The existing build workflows already know how to package tag builds and create -# draft GitHub prereleases, so this workflow only needs to decide whether a new -# prerelease is warranted and push the next prerelease tag. - -on: - schedule: - - cron: '0 12 * * 4' - workflow_dispatch: - inputs: - release_line: - description: 'Release line to prerelease from' - required: true - default: patch - type: choice - options: - - patch - - minor - -permissions: - contents: write - actions: read # needed for /actions/runs/{run_id} (check_suite_id lookup) - checks: read # needed for /commits/{sha}/check-runs - -concurrency: - group: dev-release - cancel-in-progress: false - -jobs: - preflight: - name: Pre-flight checks - runs-on: ubuntu-latest - outputs: - should_release: ${{ steps.preflight.outputs.should_release }} - next_tag: ${{ steps.preflight.outputs.next_tag }} - since_ref: ${{ steps.preflight.outputs.since_ref }} - commits_since_ref: ${{ steps.preflight.outputs.commits_since_ref }} - head_sha: ${{ steps.preflight.outputs.head_sha }} - steps: - - uses: actions/checkout@v6 - with: - ref: master # explicit: prevent workflow_dispatch from a non-master branch tagging the wrong commit - fetch-depth: 0 - - - name: Decide whether to create a dev release - id: preflight - env: - GH_TOKEN: ${{ github.token }} - RELEASE_LINE: ${{ github.event.inputs.release_line || 'patch' }} - run: | - set -euo pipefail - - if [ "${GITHUB_EVENT_NAME}" = "schedule" ]; then - # Use a fixed reference Thursday to compute biweekly parity, avoiding - # ISO week resets at year boundaries (which cause a 3-week gap in Dec/Jan). - ref_epoch=$(date -d "2024-01-04" +%s) # a known even-week Thursday - now_epoch=$(date -u +%s) - weeks_since=$(( (now_epoch - ref_epoch) / 604800 )) - if [ $((weeks_since % 2)) -eq 1 ]; then - echo "Skipping this week to keep the cadence biweekly." - echo "should_release=false" >> "$GITHUB_OUTPUT" - exit 0 - fi - fi - - bump_version() { - local version="$1" release_line="$2" - IFS='.' read -r major minor patch <<< "$version" - if [ "$release_line" = "minor" ]; then - minor=$((minor + 1)) - patch=0 - else - patch=$((patch + 1)) - fi - echo "${major}.${minor}.${patch}" - } - - latest_stable=$(git tag --sort=-version:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1 || true) - if [ -z "$latest_stable" ]; then - echo "No stable tag found, refusing to create prerelease tags." - echo "should_release=false" >> "$GITHUB_OUTPUT" - exit 0 - fi - - next_base=$(bump_version "${latest_stable#v}" "$RELEASE_LINE") - - # If a prerelease for a version ahead of next_base already exists, continue - # that release line instead of starting a new one from the stable bump. - # e.g. latest_stable=v0.13.2 + patch → v0.13.3, but if v0.14.0b1 already - # exists we should produce v0.14.0b2, not v0.13.3b1. - highest_prerelease=$(git tag --sort=-version:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+b[0-9]+$' | head -1 || true) - if [ -n "$highest_prerelease" ]; then - hp_base="${highest_prerelease%%b*}" - hp_base="${hp_base#v}" - if [ "$(printf '%s\n%s\n' "$hp_base" "$next_base" | sort -V | tail -1)" = "$hp_base" ] && \ - [ "$hp_base" != "$next_base" ]; then - echo "Existing prerelease $highest_prerelease is ahead of computed v${next_base}; continuing that release line." - next_base="$hp_base" - fi - fi - - prerelease_pattern="^v${next_base//./\\.}b[0-9]+$" - last_prerelease=$(git tag --sort=-version:refname | grep -E "$prerelease_pattern" | head -1 || true) - - if [ -n "$last_prerelease" ]; then - since_ref="$last_prerelease" - last_prerelease_num=${last_prerelease##*b} - next_tag="v${next_base}b$((last_prerelease_num + 1))" - else - since_ref="$latest_stable" - next_tag="v${next_base}b1" - fi - - commits_since_ref=$(git rev-list "${since_ref}..HEAD" --count) - echo "latest_stable=$latest_stable" - echo "last_prerelease=${last_prerelease:-}" - echo "since_ref=$since_ref" - echo "next_tag=$next_tag" - echo "commits_since_ref=$commits_since_ref" - - if [ "$commits_since_ref" -eq 0 ]; then - echo "No new commits since $since_ref, skipping dev release." - echo "should_release=false" >> "$GITHUB_OUTPUT" - exit 0 - fi - - head_sha=$(git rev-parse HEAD) - - # Get the current workflow run's check suite ID so we can exclude - # our own check runs without relying on fragile job name strings - current_suite_id=$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \ - --jq '.check_suite_id' 2>/dev/null || echo "0") - - # gh api's built-in --jq does not support jq variables like --arg, so - # use jq directly for the suite-id filter. - # Auxiliary workflow jobs (Dependabot Auto-merge, greeting, this - # workflow) are not CI signals — exclude them so their skipped/failed - # conclusions don't block the release. - conclusions=$(gh api "repos/${GITHUB_REPOSITORY}/commits/${head_sha}/check-runs" \ - --paginate \ - --slurp 2>/dev/null | jq -r --arg suite "$current_suite_id" ' - [.[].check_runs[]? - | select( - .app.slug == "github-actions" and - ((.check_suite.id | tostring) != $suite) and - (.name | test("^(Dependabot|Auto-merge|greeting|Pre-flight checks|Create dev release tag)$") | not) - ) - | .conclusion] - | unique - | .[] - ' 2>/dev/null || echo unknown) - - echo "CI conclusions: $conclusions" - - if echo "$conclusions" | grep -qE 'failure|action_required|timed_out|cancelled|startup_failure'; then - echo "CI has failures on HEAD, skipping dev release." - echo "should_release=false" >> "$GITHUB_OUTPUT" - exit 0 - fi - - if echo "$conclusions" | grep -qE 'null|pending'; then - echo "CI is still running on HEAD, skipping dev release." - echo "should_release=false" >> "$GITHUB_OUTPUT" - exit 0 - fi - - if [ -z "$conclusions" ] || [ "$conclusions" = "unknown" ]; then - echo "CI status unavailable on HEAD, skipping dev release." - echo "should_release=false" >> "$GITHUB_OUTPUT" - exit 0 - fi - - if ! echo "$conclusions" | grep -q 'success'; then - echo "No successful CI checks found on HEAD, skipping dev release." - echo "should_release=false" >> "$GITHUB_OUTPUT" - exit 0 - fi - - echo "should_release=true" >> "$GITHUB_OUTPUT" - echo "next_tag=$next_tag" >> "$GITHUB_OUTPUT" - echo "since_ref=$since_ref" >> "$GITHUB_OUTPUT" - echo "commits_since_ref=$commits_since_ref" >> "$GITHUB_OUTPUT" - echo "head_sha=$head_sha" >> "$GITHUB_OUTPUT" - - create-tag: - name: Create dev release tag - needs: preflight - if: needs.preflight.outputs.should_release == 'true' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - with: - ref: ${{ needs.preflight.outputs.head_sha }} - fetch-depth: 1 - token: ${{ secrets.AWBOT_GH_TOKEN }} # PAT required — GITHUB_TOKEN cannot trigger downstream tag-based workflows - - - name: Configure git - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - - - name: Create and push prerelease tag - run: | - set -euo pipefail - tag="${{ needs.preflight.outputs.next_tag }}" - git tag -a "$tag" -m "Development prerelease $tag" - git push origin "$tag" - { - echo "## Dev release created" - echo "" - echo "- Tag: \`$tag\`" - echo "- Changes since: \`${{ needs.preflight.outputs.since_ref }}\`" - echo "- Commits: \`${{ needs.preflight.outputs.commits_since_ref }}\`" - echo "" - echo "The existing tag-triggered build workflows will now build artifacts and create/update the draft prerelease." - } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..fac02470f --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,687 @@ +name: Release + +# Unified release/build pipeline for ActivityWatch. +# +# Triggers: +# - schedule / workflow_dispatch: decide whether to create a new dev prerelease tag +# - push / pull_request: run the normal build matrices +# - tag push: run both build matrices, generate release notes, and publish one draft release + +on: + schedule: + - cron: '0 12 * * 4' + workflow_dispatch: + inputs: + release_line: + description: 'Release line to prerelease from' + required: true + default: patch + type: choice + options: + - patch + - minor + push: + branches: [master] + tags: + - v* + pull_request: + branches: [master] + +permissions: + contents: write + actions: read + checks: read + +jobs: + preflight: + name: Pre-flight checks + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + concurrency: + group: dev-release + cancel-in-progress: false + outputs: + should_release: ${{ steps.preflight.outputs.should_release }} + next_tag: ${{ steps.preflight.outputs.next_tag }} + since_ref: ${{ steps.preflight.outputs.since_ref }} + commits_since_ref: ${{ steps.preflight.outputs.commits_since_ref }} + head_sha: ${{ steps.preflight.outputs.head_sha }} + steps: + - uses: actions/checkout@v6 + with: + ref: master + fetch-depth: 0 + + - name: Decide whether to create a dev release + id: preflight + env: + GH_TOKEN: ${{ github.token }} + RELEASE_LINE: ${{ github.event.inputs.release_line || 'patch' }} + run: | + set -euo pipefail + + if [ "${GITHUB_EVENT_NAME}" = "schedule" ]; then + ref_epoch=$(date -d "2024-01-04" +%s) + now_epoch=$(date -u +%s) + weeks_since=$(( (now_epoch - ref_epoch) / 604800 )) + if [ $((weeks_since % 2)) -eq 1 ]; then + echo "Skipping this week to keep the cadence biweekly." + echo "should_release=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + fi + + bump_version() { + local version="$1" release_line="$2" + IFS='.' read -r major minor patch <<< "$version" + if [ "$release_line" = "minor" ]; then + minor=$((minor + 1)) + patch=0 + else + patch=$((patch + 1)) + fi + echo "${major}.${minor}.${patch}" + } + + latest_stable=$(git tag --sort=-version:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1 || true) + if [ -z "$latest_stable" ]; then + echo "No stable tag found, refusing to create prerelease tags." + echo "should_release=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + next_base=$(bump_version "${latest_stable#v}" "$RELEASE_LINE") + + # If a prerelease for a version ahead of next_base already exists, + # continue that release line instead of starting a new one from the + # stable bump. Example: latest_stable=v0.13.2 + patch -> v0.13.3, + # but if v0.14.0b1 exists, produce v0.14.0b2 instead. + highest_prerelease=$(git tag --sort=-version:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+b[0-9]+$' | head -1 || true) + if [ -n "$highest_prerelease" ]; then + hp_base="${highest_prerelease%%b*}" + hp_base="${hp_base#v}" + if [ "$(printf '%s\n%s\n' "$hp_base" "$next_base" | sort -V | tail -1)" = "$hp_base" ] && \ + [ "$hp_base" != "$next_base" ]; then + echo "Existing prerelease $highest_prerelease is ahead of computed v${next_base}; continuing that release line." + next_base="$hp_base" + fi + fi + + prerelease_pattern="^v${next_base//./\\.}b[0-9]+$" + last_prerelease=$(git tag --sort=-version:refname | grep -E "$prerelease_pattern" | head -1 || true) + + if [ -n "$last_prerelease" ]; then + since_ref="$last_prerelease" + last_prerelease_num=${last_prerelease##*b} + next_tag="v${next_base}b$((last_prerelease_num + 1))" + else + since_ref="$latest_stable" + next_tag="v${next_base}b1" + fi + + commits_since_ref=$(git rev-list "${since_ref}..HEAD" --count) + echo "latest_stable=$latest_stable" + echo "last_prerelease=${last_prerelease:-}" + echo "since_ref=$since_ref" + echo "next_tag=$next_tag" + echo "commits_since_ref=$commits_since_ref" + + if [ "$commits_since_ref" -eq 0 ]; then + echo "No new commits since $since_ref, skipping dev release." + echo "should_release=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + head_sha=$(git rev-parse HEAD) + + current_suite_id=$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \ + --jq '.check_suite_id' 2>/dev/null || echo "0") + + # Auxiliary workflow jobs are not CI signals; exclude them so their + # skipped or failed conclusions do not block the release. + conclusions=$(gh api "repos/${GITHUB_REPOSITORY}/commits/${head_sha}/check-runs" \ + --paginate \ + --slurp 2>/dev/null | jq -r --arg suite "$current_suite_id" ' + [.[].check_runs[]? + | select( + .app.slug == "github-actions" and + ((.check_suite.id | tostring) != $suite) and + (.name | test("^(Dependabot|Auto-merge|greeting|Pre-flight checks|Create dev release tag)$") | not) + ) + | .conclusion] + | unique + | .[] + ' 2>/dev/null || echo unknown) + + echo "CI conclusions: $conclusions" + + if echo "$conclusions" | grep -qE 'failure|action_required|timed_out|cancelled|startup_failure'; then + echo "CI has failures on HEAD, skipping dev release." + echo "should_release=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + if echo "$conclusions" | grep -qE 'null|pending'; then + echo "CI is still running on HEAD, skipping dev release." + echo "should_release=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + if [ -z "$conclusions" ] || [ "$conclusions" = "unknown" ]; then + echo "CI status unavailable on HEAD, skipping dev release." + echo "should_release=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + if ! echo "$conclusions" | grep -q 'success'; then + echo "No successful CI checks found on HEAD, skipping dev release." + echo "should_release=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "should_release=true" >> "$GITHUB_OUTPUT" + echo "next_tag=$next_tag" >> "$GITHUB_OUTPUT" + echo "since_ref=$since_ref" >> "$GITHUB_OUTPUT" + echo "commits_since_ref=$commits_since_ref" >> "$GITHUB_OUTPUT" + echo "head_sha=$head_sha" >> "$GITHUB_OUTPUT" + + create-tag: + name: Create dev release tag + needs: preflight + if: needs.preflight.outputs.should_release == 'true' + runs-on: ubuntu-latest + concurrency: + group: dev-release + cancel-in-progress: false + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ needs.preflight.outputs.head_sha }} + fetch-depth: 1 + token: ${{ secrets.AWBOT_GH_TOKEN }} + + - name: Configure git + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Create and push prerelease tag + run: | + set -euo pipefail + tag="${{ needs.preflight.outputs.next_tag }}" + git tag -a "$tag" -m "Development prerelease $tag" + git push origin "$tag" + { + echo "## Dev release created" + echo "" + echo "- Tag: \`$tag\`" + echo "- Changes since: \`${{ needs.preflight.outputs.since_ref }}\`" + echo "- Commits: \`${{ needs.preflight.outputs.commits_since_ref }}\`" + echo "" + echo "The tag-triggered build jobs in this workflow will now build artifacts and create/update the draft prerelease." + } >> "$GITHUB_STEP_SUMMARY" + + build-qt: + name: Build Qt artifacts + if: github.event_name == 'push' || github.event_name == 'pull_request' + runs-on: ${{ matrix.os }} + continue-on-error: ${{ matrix.experimental }} + env: + AW_EXTRAS: true + MACOSX_DEPLOYMENT_TARGET: 10.9 + defaults: + run: + shell: bash + strategy: + fail-fast: false + matrix: + os: [ubuntu-24.04, windows-latest, macos-latest] + python_version: [3.9] + node_version: [22] + skip_rust: [false] + skip_webui: [false] + experimental: [false] + + steps: + - uses: actions/checkout@v6 + with: + submodules: 'recursive' + fetch-depth: 0 + + - name: Set RELEASE + run: | + echo "RELEASE=${{ startsWith(github.ref_name, 'v') || github.ref_name == 'master' }}" >> "$GITHUB_ENV" + + - name: Set tag metadata + if: startsWith(github.ref, 'refs/tags/v') + run: | + echo "VERSION_TAG=${GITHUB_REF_NAME}" >> "$GITHUB_ENV" + + - name: Determine and output version + run: | + VERSION_WITH_V=$(bash scripts/package/getversion.sh) + VERSION_NO_V="${VERSION_WITH_V#v}" + echo "VERSION_WITH_V=${VERSION_WITH_V}" >> "$GITHUB_ENV" + echo "========================================" + echo "Build Version Information" + echo "========================================" + echo "GitHub ref: ${{ github.ref }}" + echo "GitHub ref_name: ${{ github.ref_name }}" + echo "Version (with v): ${VERSION_WITH_V}" + echo "Version (no v): ${VERSION_NO_V}" + echo "========================================" + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python_version }} + + - name: Set up Node + if: ${{ !matrix.skip_webui }} + uses: actions/setup-node@v6 + with: + node-version: ${{ matrix.node_version }} + + - name: Set up Rust + if: ${{ !matrix.skip_rust }} + uses: dtolnay/rust-toolchain@master + id: toolchain + with: + toolchain: stable + + - name: Cache node_modules + uses: actions/cache@v5 + if: ${{ !matrix.skip_webui }} + with: + path: aw-server-rust/aw-webui/node_modules + key: ${{ matrix.os }}-node_modules-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ matrix.os }}-node_modules- + + - name: Cache cargo build + uses: actions/cache@v5 + env: + cache-name: cargo-build-target + with: + path: aw-server-rust/target + key: ${{ matrix.os }}-${{ env.cache-name }}-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ matrix.os }}-${{ env.cache-name }}-${{ steps.toolchain.outputs.rustc_hash }}- + + - name: Install APT dependencies + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y \ + appstream \ + qt5-qmake \ + qtbase5-dev \ + qtwayland5 \ + libqt5x11extras5 \ + libfontconfig1 \ + libxcb1 \ + libfontconfig1-dev \ + libfreetype6-dev \ + libx11-dev \ + libxcursor-dev \ + libxext-dev \ + libxfixes-dev \ + libxft-dev \ + libxi-dev \ + libxrandr-dev \ + libxrender-dev + + - name: Install dependencies + run: | + if [ "$RUNNER_OS" == "Windows" ]; then + choco install innosetup + fi + pip3 install poetry==1.4.2 + + - name: Build + run: | + python3 -m venv venv + source venv/bin/activate || source venv/Scripts/activate + poetry install + make build SKIP_WEBUI=${{ matrix.skip_webui }} SKIP_SERVER_RUST=${{ matrix.skip_rust }} + pip freeze + + - name: Run tests + run: | + source venv/bin/activate || source venv/Scripts/activate + make test SKIP_SERVER_RUST=${{ matrix.skip_rust }} + + - name: Run integration tests + if: runner.os != 'Windows' + run: | + source venv/bin/activate || source venv/Scripts/activate + make test-integration + + - name: Package + run: | + source venv/bin/activate || source venv/Scripts/activate + poetry install + make package SKIP_SERVER_RUST=${{ matrix.skip_rust }} + + - name: Package dmg + if: runner.os == 'macOS' + run: | + if [ -n "$APPLE_EMAIL" ]; then + ./scripts/ci/import-macos-p12.sh + fi + + source venv/bin/activate + make dist/ActivityWatch.dmg + + if [ -n "$APPLE_EMAIL" ]; then + codesign --force --verbose --timestamp -s ${APPLE_PERSONALID} dist/ActivityWatch.dmg + + brew install akeru-inc/tap/xcnotary + xcnotary precheck dist/ActivityWatch.app + xcnotary precheck dist/ActivityWatch.dmg + + make dist/notarize + fi + mv dist/ActivityWatch.dmg dist/activitywatch-${VERSION_WITH_V}-macos-$(uname -m).dmg + env: + APPLE_EMAIL: ${{ secrets.APPLE_EMAIL }} + APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} + APPLE_PERSONALID: ${{ secrets.APPLE_TEAMID }} + APPLE_TEAMID: ${{ secrets.APPLE_TEAMID }} + CERTIFICATE_MACOS_P12_BASE64: ${{ secrets.CERTIFICATE_MACOS_P12_BASE64 }} + CERTIFICATE_MACOS_P12_PASSWORD: ${{ secrets.CERTIFICATE_MACOS_P12_PASSWORD }} + + - name: Package AppImage + if: startsWith(runner.os, 'linux') + run: | + ./scripts/package/package-appimage.sh + + - name: Package deb + if: startsWith(runner.os, 'linux') + run: | + ./scripts/package/package-deb.sh + + - name: Upload packages + uses: actions/upload-artifact@v7 + with: + name: builds-${{ matrix.os }}-py${{ matrix.python_version }} + path: dist/activitywatch-*.* + + build-tauri: + name: Build Tauri artifacts + if: github.event_name == 'push' || github.event_name == 'pull_request' + runs-on: ${{ matrix.os }} + continue-on-error: ${{ matrix.experimental }} + env: + AW_EXTRAS: true + TAURI_BUILD: true + MACOSX_DEPLOYMENT_TARGET: 10.9 + defaults: + run: + shell: bash + strategy: + fail-fast: false + max-parallel: 5 + matrix: + os: + [ + ubuntu-24.04, + ubuntu-24.04-arm, + windows-latest, + macos-latest, + ] + python_version: [3.9] + node_version: [22] + skip_rust: [false] + skip_webui: [false] + experimental: [false] + + steps: + - uses: actions/checkout@v6 + with: + submodules: "recursive" + fetch-depth: 0 + + - name: Set environment variables + run: | + echo "RELEASE=${{ startsWith(github.ref_name, 'v') || github.ref_name == 'master' }}" >> "$GITHUB_ENV" + echo "TAURI_BUILD=true" >> "$GITHUB_ENV" + + - name: Set tag metadata + if: startsWith(github.ref, 'refs/tags/v') + run: | + echo "VERSION_TAG=${GITHUB_REF_NAME}" >> "$GITHUB_ENV" + + - name: Determine and output version + run: | + VERSION_WITH_V=$(bash scripts/package/getversion.sh) + VERSION_NO_V="${VERSION_WITH_V#v}" + echo "VERSION_WITH_V=${VERSION_WITH_V}" >> "$GITHUB_ENV" + echo "========================================" + echo "Build Version Information (Tauri)" + echo "========================================" + echo "GitHub ref: ${{ github.ref }}" + echo "GitHub ref_name: ${{ github.ref_name }}" + echo "Version (with v): ${VERSION_WITH_V}" + echo "Version (no v): ${VERSION_NO_V}" + echo "========================================" + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python_version }} + + - name: Set up Node + if: ${{ !matrix.skip_webui }} + uses: actions/setup-node@v6 + with: + node-version: ${{ matrix.node_version }} + + - name: Set up Rust + if: ${{ !matrix.skip_rust }} + uses: dtolnay/rust-toolchain@master + id: toolchain + with: + toolchain: stable + + - name: Cache node_modules + uses: actions/cache@v5 + if: ${{ !matrix.skip_webui }} + with: + path: | + aw-server-rust/aw-webui/node_modules + aw-tauri/node_modules + key: ${{ matrix.os }}-node_modules-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ matrix.os }}-node_modules- + + - name: Cache cargo build + uses: actions/cache@v5 + env: + cache-name: cargo-build-target + with: + path: | + aw-server-rust/target + aw-tauri/src-tauri/target + key: ${{ matrix.os }}-${{ env.cache-name }}-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ matrix.os }}-${{ env.cache-name }}-${{ steps.toolchain.outputs.rustc_hash }}- + + - name: Install APT dependencies + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y \ + libgtk-3-dev \ + libayatana-appindicator3-dev \ + librsvg2-dev \ + libsoup-3.0-dev \ + xdg-utils + # Pin WebKit to avoid blank-webview regression on newer ubuntu-24.04 packages. + # See: ActivityWatch/aw-tauri#99 + sudo apt-get install -y \ + libwebkit2gtk-4.1-0=2.44.0-2 \ + libwebkit2gtk-4.1-dev=2.44.0-2 \ + libjavascriptcoregtk-4.1-0=2.44.0-2 \ + libjavascriptcoregtk-4.1-dev=2.44.0-2 \ + gir1.2-javascriptcoregtk-4.1=2.44.0-2 \ + gir1.2-webkit2-4.1=2.44.0-2 + + - name: Install dependencies + run: | + if [ "$RUNNER_OS" == "Windows" ]; then + choco install innosetup + fi + pip3 install poetry==1.4.2 + + - name: Build + uses: nick-fields/retry@v4 + with: + timeout_minutes: 60 + max_attempts: 3 + shell: bash + command: | + python3 -m venv venv + source venv/bin/activate || source venv/Scripts/activate + poetry install + make build SKIP_WEBUI=${{ matrix.skip_webui }} SKIP_SERVER_RUST=${{ matrix.skip_rust }} + pip freeze + + - name: Run tests + uses: nick-fields/retry@v4 + with: + timeout_minutes: 60 + max_attempts: 3 + shell: bash + command: | + source venv/bin/activate || source venv/Scripts/activate + make test SKIP_SERVER_RUST=${{ matrix.skip_rust }} + + - name: Package + run: | + source venv/bin/activate || source venv/Scripts/activate + poetry install + make package SKIP_SERVER_RUST=${{ matrix.skip_rust }} + + - name: Package Linux (Tauri bundles) + if: runner.os == 'Linux' + run: | + # aw-tauri's bundler produces .AppImage/.deb/.rpm under + # dist/activitywatch/aw-tauri/ via `make package`. Copy them out with + # versioned, arch-suffixed filenames so they match the + # `dist/activitywatch-*.*` upload pattern below. + ARCH=$(uname -m) + shopt -s nullglob + for ext in AppImage deb rpm; do + files=( dist/activitywatch/aw-tauri/*.${ext} ) + case ${#files[@]} in + 0) continue ;; + 1) cp -v "${files[0]}" "dist/activitywatch-tauri-${VERSION_WITH_V}-linux-${ARCH}.${ext}" ;; + *) echo "ERROR: expected at most 1 .${ext} bundle, found ${#files[@]}" >&2; exit 1 ;; + esac + done + + - name: Package dmg + if: runner.os == 'macOS' + run: | + if [ -n "$APPLE_EMAIL" ]; then + ./scripts/ci/import-macos-p12.sh + fi + + source venv/bin/activate + make dist/ActivityWatch.dmg + + if [ -n "$APPLE_EMAIL" ]; then + codesign --force --verbose --timestamp -s ${APPLE_PERSONALID} dist/ActivityWatch.dmg + + brew install akeru-inc/tap/xcnotary + xcnotary precheck dist/ActivityWatch.app + xcnotary precheck dist/ActivityWatch.dmg + + make dist/notarize + fi + mv dist/ActivityWatch.dmg dist/activitywatch-tauri-${VERSION_WITH_V}-macos-$(uname -m).dmg + env: + APPLE_EMAIL: ${{ secrets.APPLE_EMAIL }} + APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} + APPLE_PERSONALID: ${{ secrets.APPLE_TEAMID }} + APPLE_TEAMID: ${{ secrets.APPLE_TEAMID }} + CERTIFICATE_MACOS_P12_BASE64: ${{ secrets.CERTIFICATE_MACOS_P12_BASE64 }} + CERTIFICATE_MACOS_P12_PASSWORD: ${{ secrets.CERTIFICATE_MACOS_P12_PASSWORD }} + + - name: Upload packages + uses: actions/upload-artifact@v7 + with: + name: builds-tauri-${{ matrix.os }}-py${{ matrix.python_version }} + path: dist/activitywatch-*.* + + release-notes: + name: Generate release notes + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + submodules: 'recursive' + fetch-depth: 0 + + - uses: ActivityWatch/check-version-format-action@v2 + id: version + with: + prefix: 'v' + + - name: Echo version + run: | + echo "${{ steps.version.outputs.full }} (stable: ${{ steps.version.outputs.is_stable }})" + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Install deps + run: | + pip install requests + + - name: Generate release notes + run: | + LAST_RELEASE=`STABLE_ONLY=${{ steps.version.outputs.is_stable }} ./scripts/get_latest_release.sh` + ./scripts/build_changelog.py --range "$LAST_RELEASE...${{ steps.version.outputs.full }}" + + - name: Rename + run: | + mv changelog.md release_notes.md + + - name: Upload release notes + uses: actions/upload-artifact@v7 + with: + name: release_notes + path: release_notes.md + + release: + name: Publish draft release + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + needs: [build-qt, build-tauri, release-notes] + runs-on: ubuntu-latest + steps: + - name: Download build artifacts + uses: actions/download-artifact@v8 + with: + path: dist + + - name: Display structure of downloaded files + run: ls -R + working-directory: dist + + - uses: ActivityWatch/check-version-format-action@v2 + id: version + with: + prefix: 'v' + + - name: Release + uses: softprops/action-gh-release@v3 + with: + draft: true + files: dist/*/activitywatch-*.* + body_path: dist/release_notes/release_notes.md + prerelease: ${{ !(steps.version.outputs.is_stable == 'true') }}