fix(release): gate builds behind Play preflight #340
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| push: | |
| tags: | |
| - 'v*' | |
| - '!v*-rc.*' | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: 'Release tag (e.g., v0.2.3)' | |
| required: true | |
| type: string | |
| run_linux: | |
| description: 'Run Linux release job' | |
| required: false | |
| type: boolean | |
| default: false | |
| run_macos: | |
| description: 'Run macOS release job' | |
| required: false | |
| type: boolean | |
| default: false | |
| run_windows: | |
| description: 'Run Windows release job' | |
| required: false | |
| type: boolean | |
| default: false | |
| run_android: | |
| description: 'Run Android release job' | |
| required: false | |
| type: boolean | |
| default: false | |
| run_android_foss: | |
| description: 'Run Android FOSS release job' | |
| required: false | |
| type: boolean | |
| default: false | |
| run_ios_appstore: | |
| description: 'Run iOS App Store release job' | |
| required: false | |
| type: boolean | |
| default: false | |
| force_ios_appstore_upload: | |
| description: 'Fail instead of silently skipping iOS App Store upload when a corrected Apple build is required' | |
| required: false | |
| type: boolean | |
| default: false | |
| run_macos_appstore: | |
| description: 'Run macOS App Store release job' | |
| required: false | |
| type: boolean | |
| default: false | |
| run_update_packages: | |
| description: 'Run Scoop/Winget update job' | |
| required: false | |
| type: boolean | |
| default: false | |
| run_update_flathub: | |
| description: 'Run Flathub update job' | |
| required: false | |
| type: boolean | |
| default: false | |
| run_update_linux_repos: | |
| description: 'Run Linux repo publish job' | |
| required: false | |
| type: boolean | |
| default: false | |
| run_publish_chocolatey: | |
| description: 'Run Chocolatey publish job' | |
| required: false | |
| type: boolean | |
| default: false | |
| run_update_aur: | |
| description: 'Run AUR binary package update job' | |
| required: false | |
| type: boolean | |
| default: false | |
| run_update_aur_source: | |
| description: 'Run AUR source package update job' | |
| required: false | |
| type: boolean | |
| default: false | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ inputs.tag || github.ref_name }} | |
| cancel-in-progress: false | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" | |
| REQUIRE_STABLE_RELEASE_TAG: "1" | |
| jobs: | |
| # Stable releases validate the immutable tag, every committed version manifest, | |
| # and the CloudKit Production ledger before any platform build can start. | |
| validate: | |
| runs-on: ubuntu-latest | |
| name: Validate Stable Release | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read | |
| outputs: | |
| tag: ${{ steps.version.outputs.tag }} | |
| version: ${{ steps.version.outputs.version }} | |
| release_notes_path: ${{ steps.release_notes.outputs.body_path }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| with: | |
| ref: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref }} | |
| fetch-depth: 0 | |
| - name: Resolve version | |
| id: version | |
| env: | |
| INPUT_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }} | |
| GITHUB_EVENT_INPUTS_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || '' }} | |
| run: | | |
| set -euo pipefail | |
| scripts/ci/resolve-release-version.sh "$INPUT_TAG" >> "$GITHUB_OUTPUT" | |
| - name: Validate stable tag naming | |
| run: | | |
| set -euo pipefail | |
| TAG="${{ steps.version.outputs.tag }}" | |
| if ! [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then | |
| echo "Expected stable tag like v1.1.5, got: $TAG" >&2 | |
| exit 1 | |
| fi | |
| - name: Resolve and validate release notes | |
| id: release_notes | |
| env: | |
| TAG: ${{ steps.version.outputs.tag }} | |
| VERSION: ${{ steps.version.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| for path in \ | |
| "docs/release-notes/${TAG}.md" \ | |
| "docs/release-notes/${VERSION}.md"; do | |
| [ -f "$path" ] || continue | |
| heading="$(head -n 1 "$path")" | |
| if [[ "$heading" != "# Mindwtr ${VERSION}" && "$heading" != "# Mindwtr ${TAG}" ]]; then | |
| echo "Stable release notes heading must be '# Mindwtr ${VERSION}' or '# Mindwtr ${TAG}': $path" >&2 | |
| exit 1 | |
| fi | |
| echo "Using release notes at $path" | |
| echo "body_path=$path" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| done | |
| echo "Missing stable release notes. Expected one of:" >&2 | |
| echo " - docs/release-notes/${TAG}.md" >&2 | |
| echo " - docs/release-notes/${VERSION}.md" >&2 | |
| exit 1 | |
| - name: Verify app versions match the stable tag | |
| env: | |
| VERSION: ${{ steps.version.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| node - <<'NODE' | |
| const fs = require('fs'); | |
| const version = process.env.VERSION; | |
| const checks = [ | |
| ['package.json', json => json.version], | |
| ['apps/desktop/package.json', json => json.version], | |
| ['apps/mobile/package.json', json => json.version], | |
| ['apps/cloud/package.json', json => json.version], | |
| ['packages/core/package.json', json => json.version], | |
| ['apps/mobile/app.json', json => json.expo && json.expo.version], | |
| ['apps/desktop/src-tauri/tauri.conf.json', json => json.version], | |
| ]; | |
| const failures = []; | |
| for (const [file, getter] of checks) { | |
| const value = getter(JSON.parse(fs.readFileSync(file, 'utf8'))); | |
| if (value !== version) { | |
| failures.push(`${file}: expected ${version}, got ${value || '<missing>'}`); | |
| } | |
| } | |
| const cargoFile = 'apps/desktop/src-tauri/Cargo.toml'; | |
| const cargoVersion = /^version = "([^"]+)"/m.exec(fs.readFileSync(cargoFile, 'utf8'))?.[1]; | |
| if (cargoVersion !== version) { | |
| failures.push(`${cargoFile}: expected ${version}, got ${cargoVersion || '<missing>'}`); | |
| } | |
| if (failures.length) { | |
| console.error('Stable release tags and committed app/store versions must match.'); | |
| console.error(failures.join('\n')); | |
| process.exit(1); | |
| } | |
| console.log(`Verified app/store versions use ${version}.`); | |
| NODE | |
| - name: Verify committed FOSS release version matches the stable tag | |
| env: | |
| TAG: ${{ steps.version.outputs.tag }} | |
| run: | | |
| set -euo pipefail | |
| COMMITTED=$(node -p "require('./apps/mobile/release-version.json').releaseVersion") | |
| if [ "v$COMMITTED" != "$TAG" ] && [ "$COMMITTED" != "$TAG" ]; then | |
| echo "apps/mobile/release-version.json says '$COMMITTED' but the stable tag is '$TAG'." >&2 | |
| echo "Run ./scripts/bump-version.sh \"$TAG\" before creating the tag." >&2 | |
| exit 1 | |
| fi | |
| echo "Verified committed FOSS release version $COMMITTED." | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 | |
| with: | |
| bun-version-file: ".bun-version" | |
| - name: Verify CloudKit production schema is fully deployed | |
| run: bun run schema:check -- --release-gate | |
| - name: Verify stable tag points at this commit | |
| run: | | |
| set -euo pipefail | |
| TAG="${{ steps.version.outputs.tag }}" | |
| TAG_SHA="$(git rev-list -n 1 "$TAG")" | |
| HEAD_SHA="$(git rev-parse HEAD)" | |
| if [ "$TAG_SHA" != "$HEAD_SHA" ]; then | |
| echo "Stable release workflow must run from the commit pointed to by $TAG." >&2 | |
| echo "$TAG -> $TAG_SHA" >&2 | |
| echo "workflow HEAD -> $HEAD_SHA" >&2 | |
| exit 1 | |
| fi | |
| echo "Verified $TAG points at $HEAD_SHA." | |
| linux: | |
| needs: [validate, android-version-code] | |
| if: ${{ always() && (github.event_name != 'workflow_dispatch' || inputs.run_linux) && needs.validate.result == 'success' && (needs['android-version-code'].result == 'success' || (github.event_name == 'workflow_dispatch' && !inputs.run_android && !inputs.run_android_foss)) }} | |
| uses: ./.github/workflows/release-linux.yml | |
| with: | |
| tag: ${{ needs.validate.outputs.tag }} | |
| secrets: inherit | |
| macos: | |
| needs: [validate, android-version-code] | |
| if: ${{ always() && (github.event_name != 'workflow_dispatch' || inputs.run_macos) && needs.validate.result == 'success' && (needs['android-version-code'].result == 'success' || (github.event_name == 'workflow_dispatch' && !inputs.run_android && !inputs.run_android_foss)) }} | |
| uses: ./.github/workflows/release-macos.yml | |
| with: | |
| tag: ${{ needs.validate.outputs.tag }} | |
| secrets: inherit | |
| windows: | |
| needs: [validate, android-version-code] | |
| if: ${{ always() && (github.event_name != 'workflow_dispatch' || inputs.run_windows) && needs.validate.result == 'success' && (needs['android-version-code'].result == 'success' || (github.event_name == 'workflow_dispatch' && !inputs.run_android && !inputs.run_android_foss)) }} | |
| uses: ./.github/workflows/release-windows.yml | |
| permissions: | |
| contents: read | |
| # SignPath signing reads the unsigned-binaries artifact (#965). | |
| actions: read | |
| with: | |
| tag: ${{ needs.validate.outputs.tag }} | |
| secrets: inherit | |
| android-version-code: | |
| needs: validate | |
| if: ${{ always() && (github.event_name != 'workflow_dispatch' || inputs.run_android || inputs.run_android_foss) && needs.validate.result == 'success' }} | |
| runs-on: ubuntu-latest | |
| name: Android VersionCode Preflight | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read | |
| outputs: | |
| version_code: ${{ steps.version_code.outputs.resolved_code }} | |
| local_version_code: ${{ steps.version_code.outputs.local_code }} | |
| remote_max_version_code: ${{ steps.version_code.outputs.remote_max_code }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| with: | |
| ref: ${{ needs.validate.outputs.tag }} | |
| - name: Authenticate Google Play API | |
| if: ${{ github.event_name != 'workflow_dispatch' || inputs.run_android }} | |
| id: play_auth | |
| uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0 | |
| with: | |
| credentials_json: ${{ secrets.ANDROID_SERVICE_ACCOUNT_JSON }} | |
| token_format: access_token | |
| access_token_scopes: https://www.googleapis.com/auth/androidpublisher | |
| - name: Resolve Android versionCode | |
| id: version_code | |
| env: | |
| ACCESS_TOKEN: ${{ steps.play_auth.outputs.access_token }} | |
| USE_PLAY_TRACKS: ${{ github.event_name != 'workflow_dispatch' || inputs.run_android }} | |
| run: | | |
| set -euo pipefail | |
| PACKAGE="tech.dongdongbh.mindwtr" | |
| LOCAL_CODE=$(jq -r '.expo.android.versionCode' apps/mobile/app.json) | |
| if [ -z "$LOCAL_CODE" ] || [ "$LOCAL_CODE" = "null" ]; then | |
| echo "Missing expo.android.versionCode in apps/mobile/app.json" >&2 | |
| exit 1 | |
| fi | |
| MAX_CODE=0 | |
| if [ "$USE_PLAY_TRACKS" = "true" ]; then | |
| if [ -z "${ACCESS_TOKEN:-}" ]; then | |
| echo "Missing Google Play access token for Android versionCode preflight" >&2 | |
| exit 1 | |
| fi | |
| EDIT_ID=$(curl -sS -X POST \ | |
| -H "Authorization: Bearer $ACCESS_TOKEN" \ | |
| -H "Content-Type: application/json" \ | |
| "https://androidpublisher.googleapis.com/androidpublisher/v3/applications/$PACKAGE/edits" \ | |
| -d '{}' | jq -r '.id') | |
| if [ -z "$EDIT_ID" ] || [ "$EDIT_ID" = "null" ]; then | |
| echo "Failed to create Google Play edit" >&2 | |
| exit 1 | |
| fi | |
| TRACKS_FILE=$(mktemp) | |
| TRACKS_CODE=$(curl -sS -o "$TRACKS_FILE" -w "%{http_code}" \ | |
| -H "Authorization: Bearer $ACCESS_TOKEN" \ | |
| "https://androidpublisher.googleapis.com/androidpublisher/v3/applications/$PACKAGE/edits/$EDIT_ID/tracks") | |
| TRACKS_JSON=$(cat "$TRACKS_FILE") | |
| if [ "$TRACKS_CODE" != "200" ]; then | |
| echo "Failed to fetch Google Play tracks (HTTP $TRACKS_CODE): $TRACKS_JSON" >&2 | |
| exit 1 | |
| fi | |
| if echo "$TRACKS_JSON" | jq -e '.error' >/dev/null; then | |
| echo "Failed to fetch Google Play tracks: $TRACKS_JSON" >&2 | |
| exit 1 | |
| fi | |
| MAX_CODE=$(echo "$TRACKS_JSON" | jq -r '[(.tracks // [])[]? | (.releases // [])[]? | (.versionCodes // [])[]? | tonumber] | max // 0') | |
| if [ -z "$MAX_CODE" ] || [ "$MAX_CODE" = "null" ]; then | |
| MAX_CODE=0 | |
| fi | |
| curl -sS -X DELETE \ | |
| -H "Authorization: Bearer $ACCESS_TOKEN" \ | |
| "https://androidpublisher.googleapis.com/androidpublisher/v3/applications/$PACKAGE/edits/$EDIT_ID" >/dev/null | |
| fi | |
| REMOTE_MAX_VERSION_CODE="$MAX_CODE" \ | |
| ALLOW_UNTRACKED_VERSION_CODE=0 \ | |
| node scripts/ci/android-version-code-policy.js | |
| android: | |
| needs: [validate, android-version-code] | |
| if: ${{ always() && (github.event_name != 'workflow_dispatch' || inputs.run_android) && needs.validate.result == 'success' && needs['android-version-code'].result == 'success' }} | |
| permissions: | |
| actions: read | |
| contents: read | |
| uses: ./.github/workflows/release-android.yml | |
| with: | |
| tag: ${{ needs.validate.outputs.tag }} | |
| play_track: production | |
| version_code: ${{ needs['android-version-code'].outputs.version_code }} | |
| secrets: inherit | |
| android-foss: | |
| needs: [validate, android-version-code] | |
| if: ${{ always() && (github.event_name != 'workflow_dispatch' || inputs.run_android_foss) && needs.validate.result == 'success' && needs['android-version-code'].result == 'success' }} | |
| uses: ./.github/workflows/release-android-foss.yml | |
| with: | |
| tag: ${{ needs.validate.outputs.tag }} | |
| version_code: ${{ needs['android-version-code'].outputs.version_code }} | |
| secrets: inherit | |
| ios-appstore: | |
| needs: [validate, android-version-code] | |
| if: ${{ always() && (github.event_name != 'workflow_dispatch' || inputs.run_ios_appstore) && needs.validate.result == 'success' && (needs['android-version-code'].result == 'success' || (github.event_name == 'workflow_dispatch' && !inputs.run_android && !inputs.run_android_foss)) }} | |
| uses: ./.github/workflows/release-ios-appstore.yml | |
| with: | |
| tag: ${{ needs.validate.outputs.tag }} | |
| upload: true | |
| submit_for_review: true | |
| distribute_testflight: true | |
| force_appstore_upload: ${{ github.event_name == 'workflow_dispatch' && inputs.force_ios_appstore_upload || false }} | |
| testflight_group: 'external_testing' | |
| secrets: inherit | |
| macos-appstore: | |
| needs: [validate, android-version-code] | |
| if: ${{ always() && (github.event_name != 'workflow_dispatch' || inputs.run_macos_appstore) && needs.validate.result == 'success' && (needs['android-version-code'].result == 'success' || (github.event_name == 'workflow_dispatch' && !inputs.run_android && !inputs.run_android_foss)) }} | |
| uses: ./.github/workflows/release-macos-appstore.yml | |
| with: | |
| tag: ${{ needs.validate.outputs.tag }} | |
| upload: true | |
| submit_for_review: true | |
| distribute_testflight: true | |
| testflight_group: 'external_testing' | |
| secrets: inherit | |
| release: | |
| if: ${{ github.event_name != 'workflow_dispatch' || (inputs.run_linux && inputs.run_macos && inputs.run_windows && inputs.run_android && inputs.run_android_foss) }} | |
| needs: [validate, linux, macos, windows, android, android-foss] | |
| runs-on: ubuntu-latest | |
| name: Create Release | |
| timeout-minutes: 60 | |
| permissions: | |
| actions: read | |
| contents: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| with: | |
| ref: ${{ needs.validate.outputs.tag }} | |
| - name: Import Mindwtr release signing key | |
| uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0 | |
| with: | |
| gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} | |
| passphrase: ${{ secrets.GPG_PASSPHRASE }} | |
| - name: Download build artifacts | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| tmp_dir="$(mktemp -d)" | |
| mkdir -p ./release-assets | |
| cleanup() { | |
| rm -rf "$tmp_dir" | |
| } | |
| trap cleanup EXIT | |
| artifact_names="$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/artifacts" --paginate --jq '.artifacts[] | select(.expired == false and (.name | startswith("release-"))) | .name')" | |
| if [ -z "$artifact_names" ]; then | |
| echo "No release-* artifacts found for run ${GITHUB_RUN_ID}." >&2 | |
| exit 1 | |
| fi | |
| while IFS= read -r artifact_name; do | |
| [ -n "$artifact_name" ] || continue | |
| artifact_dir="${tmp_dir}/${artifact_name}" | |
| mkdir -p "$artifact_dir" | |
| gh run download "${GITHUB_RUN_ID}" --repo "${GITHUB_REPOSITORY}" -n "$artifact_name" -D "$artifact_dir" | |
| find "$artifact_dir" -type f -print0 | while IFS= read -r -d '' file; do | |
| cp "$file" ./release-assets/ | |
| done | |
| done <<< "$artifact_names" | |
| - name: List release assets | |
| run: ls -la ./release-assets/ || echo "No assets found" | |
| - name: Validate release assets | |
| run: | | |
| set -euo pipefail | |
| VERSION="${{ needs.validate.outputs.version }}" | |
| count="$(find ./release-assets -maxdepth 1 -type f | wc -l | tr -d ' ')" | |
| if [ "${count}" -eq 0 ]; then | |
| echo "No release artifacts were downloaded. Aborting release creation." | |
| exit 1 | |
| fi | |
| echo "Release asset count: ${count}" | |
| # Ensure all artifacts are non-empty and compute checksums. | |
| while IFS= read -r -d '' file; do | |
| if [ ! -s "$file" ]; then | |
| echo "Artifact has zero size: $file" >&2 | |
| exit 1 | |
| fi | |
| done < <(find ./release-assets -maxdepth 1 -type f -print0) | |
| has_match() { | |
| local pattern="$1" | |
| find ./release-assets -maxdepth 1 -type f -name "$pattern" -print -quit | grep -q . | |
| } | |
| missing=0 | |
| if ! has_match "*.apk"; then | |
| echo "Missing Android artifact (*.apk)." >&2 | |
| missing=1 | |
| fi | |
| if ! has_match "*-foss.apk"; then | |
| echo "Missing Android FOSS artifact (*-foss.apk)." >&2 | |
| missing=1 | |
| fi | |
| if ! has_match "*.dmg"; then | |
| echo "Missing macOS artifact (*.dmg)." >&2 | |
| missing=1 | |
| fi | |
| expected_windows_installer="./release-assets/mindwtr_${VERSION}_x64-setup.exe" | |
| mapfile -d '' -t windows_installers < <(find ./release-assets -maxdepth 1 -type f -name '*.exe' -print0) | |
| if [ "${#windows_installers[@]}" -ne 1 ]; then | |
| echo "Expected exactly one Windows installer for ${VERSION}; found ${#windows_installers[@]}." >&2 | |
| if [ "${#windows_installers[@]}" -gt 0 ]; then | |
| printf ' %s\n' "${windows_installers[@]}" >&2 | |
| fi | |
| missing=1 | |
| elif [ "${windows_installers[0]}" != "$expected_windows_installer" ]; then | |
| echo "Expected Windows installer ${expected_windows_installer}; found ${windows_installers[0]}." >&2 | |
| missing=1 | |
| fi | |
| if ! has_match "*.AppImage" && ! has_match "*.deb" && ! has_match "*.rpm" && ! has_match "*.snap"; then | |
| echo "Missing Linux artifact (*.AppImage, *.deb, *.rpm, or *.snap)." >&2 | |
| missing=1 | |
| fi | |
| if [ "$missing" -ne 0 ]; then | |
| exit 1 | |
| fi | |
| ( | |
| cd ./release-assets | |
| sha256sum * > SHA256SUMS | |
| gpg --batch --yes --armor --detach-sign --output SHA256SUMS.asc SHA256SUMS | |
| gpg --batch --verify SHA256SUMS.asc SHA256SUMS | |
| ) | |
| echo "Generated and signed checksum manifest at release-assets/SHA256SUMS" | |
| - name: Create Release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| NOTES_FILE: ${{ needs.validate.outputs.release_notes_path }} | |
| run: | | |
| set -euo pipefail | |
| TAG="${{ needs.validate.outputs.tag }}" | |
| if gh release view "$TAG" >/dev/null 2>&1; then | |
| echo "Release $TAG already exists; stable release assets are immutable. Publish fixes under a new tag." >&2 | |
| exit 1 | |
| else | |
| if [[ "$TAG" == *-* ]]; then | |
| gh release create "$TAG" ./release-assets/* \ | |
| --draft=false \ | |
| --prerelease \ | |
| --notes-file "$NOTES_FILE" | |
| else | |
| gh release create "$TAG" ./release-assets/* \ | |
| --draft=false \ | |
| --notes-file "$NOTES_FILE" | |
| fi | |
| fi | |
| update-packages: | |
| needs: [validate, release] | |
| runs-on: ubuntu-latest | |
| name: Update Scoop/Winget Packages | |
| timeout-minutes: 45 | |
| permissions: | |
| contents: read | |
| if: ${{ always() && needs.validate.result == 'success' && !contains(github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name, '-') && ((startsWith(github.ref, 'refs/tags/') && github.event_name == 'push' && needs.release.result == 'success') || (github.event_name == 'workflow_dispatch' && inputs.run_update_packages && (needs.release.result == 'success' || needs.release.result == 'skipped'))) }} | |
| steps: | |
| - name: Checkout release repo | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| with: | |
| path: source | |
| - name: Checkout packages repo | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| with: | |
| repository: dongdongbh/homebrew-mindwtr | |
| token: ${{ secrets.PACKAGES_REPO_TOKEN }} | |
| path: packages-repo | |
| - name: Resolve release version | |
| id: version | |
| env: | |
| INPUT_TAG: ${{ github.event.inputs.tag }} | |
| GITHUB_EVENT_INPUTS_TAG: ${{ github.event.inputs.tag }} | |
| run: | | |
| if [ -n "${INPUT_TAG:-}" ] && ! echo "${INPUT_TAG}" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$'; then | |
| echo "Invalid tag format" >&2 | |
| exit 1 | |
| fi | |
| source/scripts/ci/resolve-release-version.sh "$INPUT_TAG" >> "$GITHUB_OUTPUT" | |
| - name: Download release assets and compute hashes | |
| id: hashes | |
| run: | | |
| TAG="${{ steps.version.outputs.tag }}" | |
| VERSION="${{ steps.version.outputs.version }}" | |
| URL_EXE="https://github.com/${{ github.repository }}/releases/download/${TAG}/mindwtr_${VERSION}_x64-setup.exe" | |
| echo "Waiting for release assets to become available..." | |
| for attempt in $(seq 1 10); do | |
| if curl -fsI "$URL_EXE" >/dev/null; then | |
| echo "Release assets are available." | |
| break | |
| fi | |
| if [ "$attempt" -eq 10 ]; then | |
| echo "Release assets not available after waiting." | |
| exit 1 | |
| fi | |
| sleep 15 | |
| done | |
| curl -fL -o mindwtr.exe "$URL_EXE" | |
| SHA_EXE=$(sha256sum mindwtr.exe | awk '{print $1}') | |
| echo "sha_exe=$SHA_EXE" >> "$GITHUB_OUTPUT" | |
| - name: Update Scoop manifest | |
| run: | | |
| VERSION="${{ steps.version.outputs.version }}" | |
| SHA_EXE="${{ steps.hashes.outputs.sha_exe }}" | |
| URL_EXE="https://github.com/${{ github.repository }}/releases/download/v${VERSION}/mindwtr_${VERSION}_x64-setup.exe#/dl.7z" | |
| FILE="packages-repo/bucket/mindwtr.json" | |
| jq --arg v "$VERSION" \ | |
| --arg url "$URL_EXE" \ | |
| --arg hash "$SHA_EXE" \ | |
| '.version = $v | |
| | .architecture."64bit".url = $url | |
| | .architecture."64bit".hash = $hash | |
| | .autoupdate.architecture."64bit".url = "https://github.com/dongdongbh/Mindwtr/releases/download/v\($v)/mindwtr_\($v)_x64-setup.exe#/dl.7z"' \ | |
| "$FILE" > "$FILE.tmp" && mv "$FILE.tmp" "$FILE" | |
| - name: Check Winget package exists | |
| id: winget_check | |
| run: | | |
| PKG_ID="dongdongbh.Mindwtr" | |
| PKG_LOWER="${PKG_ID,,}" | |
| FIRST="${PKG_LOWER:0:1}" | |
| URL="https://github.com/microsoft/winget-pkgs/tree/master/manifests/${FIRST}/${PKG_ID//./\/}" | |
| STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$URL" || true) | |
| if [ "$STATUS" = "200" ]; then | |
| echo "exists=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "exists=false" >> "$GITHUB_OUTPUT" | |
| echo "Winget package not found yet; skipping submission." | |
| fi | |
| - name: Checkout winget-pkgs fork | |
| if: steps.winget_check.outputs.exists == 'true' | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| with: | |
| repository: dongdongbh/winget-pkgs | |
| token: ${{ secrets.PACKAGES_REPO_TOKEN }} | |
| path: winget-pkgs | |
| - name: Prepare Winget manifests | |
| if: steps.winget_check.outputs.exists == 'true' | |
| run: | | |
| VERSION="${{ steps.version.outputs.version }}" | |
| TAG="${{ steps.version.outputs.tag }}" | |
| REPO="${{ github.repository }}" | |
| SHA_EXE="${{ steps.hashes.outputs.sha_exe }}" | |
| RELEASE_DATE="$(date -u +%Y-%m-%d)" | |
| INSTALLER_URL="https://github.com/${REPO}/releases/download/${TAG}/mindwtr_${VERSION}_x64-setup.exe" | |
| MANIFEST_DIR="winget-pkgs/manifests/d/dongdongbh/Mindwtr/${VERSION}" | |
| mkdir -p "$MANIFEST_DIR" | |
| cat <<EOF | sed 's/^ //' > "$MANIFEST_DIR/dongdongbh.Mindwtr.installer.yaml" | |
| # yaml-language-server: \$schema=https://aka.ms/winget-manifest.installer.1.10.0.schema.json | |
| PackageIdentifier: dongdongbh.Mindwtr | |
| PackageVersion: ${VERSION} | |
| InstallerLocale: en-US | |
| InstallerType: nullsoft | |
| Scope: user | |
| InstallModes: | |
| - interactive | |
| - silent | |
| - silentWithProgress | |
| InstallerSwitches: | |
| Silent: /S /CURRENTUSER | |
| SilentWithProgress: /S /CURRENTUSER | |
| UpgradeBehavior: install | |
| Commands: | |
| - mindwtr | |
| ProductCode: Mindwtr | |
| ReleaseDate: ${RELEASE_DATE} | |
| AppsAndFeaturesEntries: | |
| - ProductCode: Mindwtr | |
| InstallationMetadata: | |
| DefaultInstallLocation: '%LOCALAPPDATA%\\Programs\\Mindwtr' | |
| Installers: | |
| - Architecture: x64 | |
| InstallerUrl: ${INSTALLER_URL} | |
| InstallerSha256: ${SHA_EXE} | |
| ManifestType: installer | |
| ManifestVersion: 1.10.0 | |
| EOF | |
| INSTALLER_MANIFEST="$MANIFEST_DIR/dongdongbh.Mindwtr.installer.yaml" | |
| test -s "$INSTALLER_MANIFEST" | |
| cat <<EOF | sed 's/^ //' > "$MANIFEST_DIR/dongdongbh.Mindwtr.locale.en-US.yaml" | |
| # yaml-language-server: \$schema=https://aka.ms/winget-manifest.defaultLocale.1.10.0.schema.json | |
| PackageIdentifier: dongdongbh.Mindwtr | |
| PackageVersion: ${VERSION} | |
| PackageLocale: en-US | |
| Publisher: dongdongbh | |
| PublisherUrl: https://github.com/dongdongbh | |
| PublisherSupportUrl: https://github.com/dongdongbh/Mindwtr/issues | |
| Author: dongdongbh | |
| PackageName: Mindwtr | |
| PackageUrl: https://github.com/dongdongbh/Mindwtr | |
| License: AGPL-3.0 | |
| LicenseUrl: https://github.com/dongdongbh/Mindwtr/blob/HEAD/LICENSE | |
| ShortDescription: A complete Getting Things Done (GTD) productivity system. | |
| Description: A complete Getting Things Done (GTD) productivity system. | |
| Moniker: mindwtr | |
| Tags: | |
| - cross-platform | |
| - getting-things-done | |
| - gtd | |
| - gtd-applications | |
| - gtd-workflow | |
| - personal-management | |
| - productivity | |
| - second-brain | |
| - task-management | |
| - todo-app | |
| ReleaseNotesUrl: https://github.com/${REPO}/releases/tag/${TAG} | |
| ManifestType: defaultLocale | |
| ManifestVersion: 1.10.0 | |
| EOF | |
| cat <<EOF | sed 's/^ //' > "$MANIFEST_DIR/dongdongbh.Mindwtr.locale.zh-CN.yaml" | |
| # yaml-language-server: \$schema=https://aka.ms/winget-manifest.locale.1.10.0.schema.json | |
| PackageIdentifier: dongdongbh.Mindwtr | |
| PackageVersion: ${VERSION} | |
| PackageLocale: zh-CN | |
| Publisher: dongdongbh | |
| PublisherUrl: https://github.com/dongdongbh | |
| PublisherSupportUrl: https://github.com/dongdongbh/Mindwtr/issues | |
| Author: dongdongbh | |
| PackageName: Mindwtr | |
| PackageUrl: https://github.com/dongdongbh/Mindwtr | |
| License: AGPL-3.0 | |
| LicenseUrl: https://github.com/dongdongbh/Mindwtr/blob/HEAD/LICENSE | |
| ShortDescription: 完整的 GTD(搞定)任务管理系统。 | |
| Description: 完整的 GTD(搞定)任务管理系统。 | |
| Moniker: mindwtr | |
| Tags: | |
| - cross-platform | |
| - getting-things-done | |
| - gtd | |
| - gtd-applications | |
| - gtd-workflow | |
| - personal-management | |
| - productivity | |
| - second-brain | |
| - task-management | |
| - todo-app | |
| ReleaseNotesUrl: https://github.com/${REPO}/releases/tag/${TAG} | |
| ManifestType: locale | |
| ManifestVersion: 1.10.0 | |
| EOF | |
| cat <<EOF | sed 's/^ //' > "$MANIFEST_DIR/dongdongbh.Mindwtr.yaml" | |
| # yaml-language-server: \$schema=https://aka.ms/winget-manifest.version.1.10.0.schema.json | |
| PackageIdentifier: dongdongbh.Mindwtr | |
| PackageVersion: ${VERSION} | |
| DefaultLocale: en-US | |
| ManifestType: version | |
| ManifestVersion: 1.10.0 | |
| EOF | |
| - name: Submit Winget PR | |
| if: steps.winget_check.outputs.exists == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.PACKAGES_REPO_TOKEN }} | |
| run: | | |
| VERSION="${{ steps.version.outputs.version }}" | |
| BRANCH="mindwtr-${VERSION}" | |
| cd winget-pkgs | |
| git checkout -b "$BRANCH" | |
| git add manifests/d/dongdongbh/Mindwtr/"$VERSION" | |
| git config --local user.name "GitHub Actions" | |
| git config --local user.email "actions@github.com" | |
| git commit -m "New version: Mindwtr ${VERSION}" | |
| git push -u origin "$BRANCH" | |
| gh pr create \ | |
| --repo microsoft/winget-pkgs \ | |
| --head "dongdongbh:${BRANCH}" \ | |
| --base master \ | |
| --title "New version: Mindwtr ${VERSION}" \ | |
| --body "Automated manifest update for Mindwtr ${VERSION}." | |
| - name: Commit and push | |
| run: | | |
| cd packages-repo | |
| git config --local user.name "GitHub Actions" | |
| git config --local user.email "actions@github.com" | |
| git add bucket/mindwtr.json | |
| if git diff --cached --quiet; then | |
| echo "No package changes to commit." | |
| exit 0 | |
| fi | |
| git commit -m "update: Mindwtr ${{ steps.version.outputs.version }}" | |
| git push | |
| update-flathub: | |
| needs: [validate, release] | |
| name: Update Flathub | |
| if: ${{ always() && needs.validate.result == 'success' && !contains(github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name, '-') && ((startsWith(github.ref, 'refs/tags/') && github.event_name == 'push' && needs.release.result == 'success' && !contains(github.ref_name, '-')) || (github.event_name == 'workflow_dispatch' && inputs.run_update_flathub && (needs.release.result == 'success' || needs.release.result == 'skipped') && !contains(inputs.tag, '-'))) }} | |
| uses: ./.github/workflows/update-flathub.yml | |
| with: | |
| tag: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }} | |
| secrets: inherit | |
| update-flathub-beta: | |
| needs: [validate, release] | |
| name: Update Flathub Beta | |
| if: ${{ always() && needs.validate.result == 'success' && !contains(github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name, '-') && ((startsWith(github.ref, 'refs/tags/') && github.event_name == 'push' && needs.release.result == 'success' && !contains(github.ref_name, '-')) || (github.event_name == 'workflow_dispatch' && inputs.run_update_flathub && (needs.release.result == 'success' || needs.release.result == 'skipped') && !contains(inputs.tag, '-'))) }} | |
| uses: ./.github/workflows/update-flathub.yml | |
| with: | |
| tag: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }} | |
| channel: beta | |
| secrets: inherit | |
| update-linux-repos: | |
| needs: [validate, release] | |
| if: ${{ always() && needs.validate.result == 'success' && !contains(github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name, '-') && ((startsWith(github.ref, 'refs/tags/') && github.event_name == 'push' && needs.release.result == 'success') || (github.event_name == 'workflow_dispatch' && inputs.run_update_linux_repos && (needs.release.result == 'success' || needs.release.result == 'skipped'))) }} | |
| permissions: | |
| contents: write | |
| uses: ./.github/workflows/publish-repo.yml | |
| secrets: inherit | |
| with: | |
| tag: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }} | |
| # Stable releases update both AUR beta identities so current and transitional | |
| # users pick stable back up after a test cycle without switching packages. | |
| update-aur-beta-bin: | |
| needs: [validate, release] | |
| if: ${{ always() && needs.validate.result == 'success' && !contains(github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name, '-') && ((startsWith(github.ref, 'refs/tags/') && github.event_name == 'push' && needs.release.result == 'success') || (github.event_name == 'workflow_dispatch' && inputs.run_update_aur && (needs.release.result == 'success' || needs.release.result == 'skipped'))) }} | |
| name: Update AUR Beta identities | |
| uses: ./.github/workflows/update-aur-beta.yml | |
| with: | |
| tag: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }} | |
| secrets: inherit | |
| # Stable releases also land in the beta repos (matching the Flathub beta | |
| # branch behavior) so beta-repo users pick stable back up after a test | |
| # cycle. Runs after the stable repo publish: both jobs push to gh-pages and | |
| # would race otherwise. | |
| update-linux-repos-beta: | |
| needs: [validate, release, update-linux-repos] | |
| if: ${{ always() && needs.validate.result == 'success' && needs.update-linux-repos.result == 'success' && !contains(github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name, '-') }} | |
| permissions: | |
| contents: write | |
| uses: ./.github/workflows/publish-repo.yml | |
| secrets: inherit | |
| with: | |
| tag: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }} | |
| channel: beta | |
| publish-chocolatey: | |
| needs: [validate, release] | |
| runs-on: windows-2025-vs2026 | |
| name: Publish Chocolatey | |
| timeout-minutes: 45 | |
| permissions: | |
| contents: read | |
| if: ${{ always() && needs.validate.result == 'success' && !contains(github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name, '-') && ((startsWith(github.ref, 'refs/tags/') && github.event_name == 'push' && needs.release.result == 'success' && !contains(github.ref, '-')) || (github.event_name == 'workflow_dispatch' && inputs.run_publish_chocolatey && (needs.release.result == 'success' || needs.release.result == 'skipped'))) && vars.CHOCOLATEY_PUBLISH_ENABLED == 'true' }} | |
| steps: | |
| - name: Checkout repo | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| - name: Resolve release tag/version | |
| id: version | |
| shell: pwsh | |
| run: | | |
| $inputTag = "${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}" | |
| $env:GITHUB_EVENT_INPUTS_TAG = "${{ github.event.inputs.tag }}" | |
| $lines = & scripts/ci/resolve-release-version.ps1 -InputTag $inputTag | |
| foreach ($line in $lines) { | |
| $line >> $env:GITHUB_OUTPUT | |
| } | |
| - name: Download Windows installer and compute SHA256 | |
| id: installer | |
| shell: pwsh | |
| run: | | |
| Set-StrictMode -Version Latest | |
| $ErrorActionPreference = 'Stop' | |
| $tag = "${{ steps.version.outputs.tag }}" | |
| $version = "${{ steps.version.outputs.version }}" | |
| $url = "https://github.com/${{ github.repository }}/releases/download/$tag/mindwtr_${version}_x64-setup.exe" | |
| Write-Host "Waiting for release asset: $url" | |
| $maxAttempts = 20 | |
| for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) { | |
| try { | |
| $response = Invoke-WebRequest -Uri $url -Method Head -MaximumRedirection 10 -ErrorAction Stop | |
| if ($response.StatusCode -ge 200 -and $response.StatusCode -lt 400) { | |
| break | |
| } | |
| } catch { | |
| if ($attempt -eq $maxAttempts) { | |
| throw "Release asset unavailable after $maxAttempts attempts: $url" | |
| } | |
| Start-Sleep -Seconds 15 | |
| } | |
| } | |
| $outFile = Join-Path $PWD "mindwtr-${version}-setup.exe" | |
| Invoke-WebRequest -Uri $url -OutFile $outFile -MaximumRedirection 10 | |
| $sha = (Get-FileHash -Path $outFile -Algorithm SHA256).Hash.ToLowerInvariant() | |
| "installer_url=$url" >> $env:GITHUB_OUTPUT | |
| "installer_path=$outFile" >> $env:GITHUB_OUTPUT | |
| "sha256=$sha" >> $env:GITHUB_OUTPUT | |
| Write-Host "Installer SHA256: $sha" | |
| - name: Ensure Chocolatey CLI exists | |
| shell: pwsh | |
| run: | | |
| if (-not (Get-Command choco -ErrorAction SilentlyContinue)) { | |
| throw "Chocolatey CLI (choco) not found on runner." | |
| } | |
| choco --version | |
| - name: Build Chocolatey package (auto-generated install script) | |
| id: pack | |
| shell: pwsh | |
| run: | | |
| Set-StrictMode -Version Latest | |
| $ErrorActionPreference = 'Stop' | |
| $version = "${{ steps.version.outputs.version }}" | |
| $installerUrl = "${{ steps.installer.outputs.installer_url }}" | |
| $sha = "${{ steps.installer.outputs.sha256 }}" | |
| $pkgRoot = Join-Path $PWD "chocolatey-package" | |
| $toolsDir = Join-Path $pkgRoot "tools" | |
| New-Item -ItemType Directory -Force -Path $toolsDir | Out-Null | |
| Copy-Item "metadata/chocolatey/mindwtr.nuspec" -Destination (Join-Path $pkgRoot "mindwtr.nuspec") -Force | |
| $template = Get-Content -Raw "metadata/chocolatey/tools/chocolateyInstall.ps1.template" | |
| $installScript = $template.Replace("__INSTALLER_URL__", $installerUrl).Replace("__CHECKSUM__", $sha) | |
| Set-Content -Path (Join-Path $toolsDir "chocolateyInstall.ps1") -Value $installScript -Encoding UTF8 -NoNewline | |
| Push-Location $pkgRoot | |
| choco pack "mindwtr.nuspec" --version "$version" | |
| Pop-Location | |
| $nupkg = Get-ChildItem -Path $pkgRoot -Filter "mindwtr.$version.nupkg" | Select-Object -First 1 | |
| if (-not $nupkg) { | |
| throw "Failed to generate Chocolatey package for version $version." | |
| } | |
| "nupkg_path=$($nupkg.FullName)" >> $env:GITHUB_OUTPUT | |
| Write-Host "Generated package: $($nupkg.FullName)" | |
| - name: Smoke test Chocolatey package locally | |
| shell: pwsh | |
| run: | | |
| Set-StrictMode -Version Latest | |
| $ErrorActionPreference = 'Stop' | |
| $pkgRoot = Join-Path $PWD "chocolatey-package" | |
| choco install mindwtr --source "$pkgRoot" --force --no-progress -y | |
| - name: Push package to Chocolatey | |
| shell: pwsh | |
| env: | |
| CHOCOLATEY_API_KEY: ${{ secrets.CHOCOLATEY_API_KEY }} | |
| run: | | |
| Set-StrictMode -Version Latest | |
| $ErrorActionPreference = 'Stop' | |
| if ([string]::IsNullOrWhiteSpace($env:CHOCOLATEY_API_KEY)) { | |
| throw "Missing CHOCOLATEY_API_KEY secret." | |
| } | |
| $nupkg = "${{ steps.pack.outputs.nupkg_path }}" | |
| $output = choco push "$nupkg" --source "https://push.chocolatey.org/" --api-key "$env:CHOCOLATEY_API_KEY" --timeout 2700 2>&1 | |
| $exitCode = $LASTEXITCODE | |
| $text = ($output | Out-String) | |
| Write-Host $text | |
| if ($exitCode -ne 0) { | |
| if ($text -match "already exists") { | |
| Write-Host "Chocolatey package version already exists. Skipping duplicate push." | |
| exit 0 | |
| } | |
| throw "Chocolatey push failed." | |
| } | |
| update-aur: | |
| needs: [validate, release] | |
| runs-on: ubuntu-latest | |
| name: Update AUR (mindwtr-bin) | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: read | |
| if: ${{ always() && needs.validate.result == 'success' && !contains(github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name, '-') && ((startsWith(github.ref, 'refs/tags/') && github.event_name == 'push' && needs.release.result == 'success') || (github.event_name == 'workflow_dispatch' && inputs.run_update_aur && (needs.release.result == 'success' || needs.release.result == 'skipped'))) }} | |
| steps: | |
| - name: Checkout repo | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| - name: Resolve release version | |
| id: version | |
| env: | |
| INPUT_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }} | |
| GITHUB_EVENT_INPUTS_TAG: ${{ github.event.inputs.tag }} | |
| run: | | |
| if [ -n "${INPUT_TAG:-}" ] && ! echo "${INPUT_TAG}" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$'; then | |
| echo "Invalid tag format" >&2 | |
| exit 1 | |
| fi | |
| scripts/ci/resolve-release-version.sh "$INPUT_TAG" >> "$GITHUB_OUTPUT" | |
| - name: Update PKGBUILD version | |
| run: | | |
| VERSION=${{ steps.version.outputs.version }} | |
| echo "Updating PKGBUILD to version $VERSION" | |
| sed -i "s/^pkgver=.*/pkgver=$VERSION/" aur/PKGBUILD | |
| sed -i "s/^pkgrel=.*/pkgrel=1/" aur/PKGBUILD | |
| shell: bash | |
| - name: Configure SSH for AUR | |
| shell: bash | |
| env: | |
| AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }} | |
| run: | | |
| set -euo pipefail | |
| if [ -z "${AUR_SSH_PRIVATE_KEY}" ]; then | |
| echo "Missing AUR_SSH_PRIVATE_KEY secret." | |
| exit 1 | |
| fi | |
| install -d -m 700 ~/.ssh | |
| printf '%s\n' "${AUR_SSH_PRIVATE_KEY}" > ~/.ssh/aur | |
| chmod 600 ~/.ssh/aur | |
| cat > ~/.ssh/config <<'EOF' | |
| Host aur.archlinux.org | |
| HostName aur.archlinux.org | |
| User aur | |
| IdentityFile ~/.ssh/aur | |
| IdentitiesOnly yes | |
| HostKeyAlgorithms ssh-ed25519 | |
| StrictHostKeyChecking yes | |
| EOF | |
| : > ~/.ssh/known_hosts | |
| chmod 600 ~/.ssh/known_hosts | |
| ssh-keyscan -t ed25519 aur.archlinux.org > "$RUNNER_TEMP/aur-known-hosts" 2>/dev/null | |
| ACTUAL_FINGERPRINT="$(ssh-keygen -lf "$RUNNER_TEMP/aur-known-hosts" -E sha256 | awk '{print $2}')" | |
| EXPECTED_FINGERPRINT='SHA256:RFzBCUItH9LZS0cKB5UE6ceAYhBD5C8GeOBip8Z11+4' | |
| if [ "$ACTUAL_FINGERPRINT" != "$EXPECTED_FINGERPRINT" ]; then | |
| echo "AUR SSH host fingerprint mismatch: expected ${EXPECTED_FINGERPRINT}, got ${ACTUAL_FINGERPRINT:-missing}." >&2 | |
| exit 1 | |
| fi | |
| cp "$RUNNER_TEMP/aur-known-hosts" ~/.ssh/known_hosts | |
| - name: Clone AUR repo | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| rm -rf aur-mindwtr-bin | |
| for attempt in 1 2 3; do | |
| if git clone ssh://aur@aur.archlinux.org/mindwtr-bin.git aur-mindwtr-bin; then | |
| exit 0 | |
| fi | |
| echo "AUR clone attempt ${attempt} failed; retrying..." >&2 | |
| rm -rf aur-mindwtr-bin | |
| sleep 3 | |
| done | |
| echo "Failed to clone mindwtr-bin from AUR after 3 attempts." >&2 | |
| exit 1 | |
| - name: Sync PKGBUILD into AUR repo | |
| shell: bash | |
| run: cp aur/PKGBUILD aur-mindwtr-bin/PKGBUILD | |
| - name: Update .SRCINFO and smoke package | |
| run: | | |
| HOST_UID="$(id -u)" | |
| HOST_GID="$(id -g)" | |
| docker run --rm \ | |
| -e HOST_UID="$HOST_UID" \ | |
| -e HOST_GID="$HOST_GID" \ | |
| -v "$PWD/aur-mindwtr-bin:/aur" \ | |
| -w /aur \ | |
| archlinux:latest \ | |
| bash -lc ' | |
| set -euo pipefail | |
| pacman -Sy --noconfirm --needed base-devel git pacman-contrib | |
| getent group "$HOST_GID" >/dev/null 2>&1 || groupadd -g "$HOST_GID" builder | |
| id -u builder >/dev/null 2>&1 || useradd -m -u "$HOST_UID" -g "$HOST_GID" builder | |
| chown -R builder:builder /aur | |
| su builder -c "updpkgsums" | |
| su builder -c "makepkg --verifysource --noconfirm" | |
| su builder -c "makepkg --cleanbuild --clean --nodeps --noconfirm" | |
| su builder -c "makepkg --printsrcinfo > /aur/.SRCINFO" | |
| ' | |
| shell: bash | |
| - name: Validate AUR package contents | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| node scripts/ci/validate-aur-package.mjs \ | |
| --package-dir aur-mindwtr-bin \ | |
| --package mindwtr-bin | |
| - name: Verify AUR package ownership before push | |
| shell: bash | |
| run: node scripts/ci/audit-aur-state.mjs | |
| - name: Commit and push changes | |
| shell: bash | |
| working-directory: aur-mindwtr-bin | |
| env: | |
| AUR_USERNAME: ${{ secrets.AUR_USERNAME }} | |
| AUR_EMAIL: ${{ secrets.AUR_EMAIL }} | |
| run: | | |
| set -euo pipefail | |
| if [ -z "$(git status --porcelain -- PKGBUILD .SRCINFO)" ]; then | |
| echo "No AUR binary-package changes to publish." | |
| exit 0 | |
| fi | |
| git config --local user.name "${AUR_USERNAME:-mindwtr-bot}" | |
| git config --local user.email "${AUR_EMAIL:-actions@github.com}" | |
| git add PKGBUILD .SRCINFO | |
| git commit -m "Update to ${{ steps.version.outputs.tag }}" | |
| PUSH_LOG="$RUNNER_TEMP/aur-bin-push.log" | |
| if git push origin master 2>&1 | tee "$PUSH_LOG"; then | |
| exit 0 | |
| fi | |
| if grep -Eqi 'AUR is down due to maintenance|disabled (all )?AUR pushes|pushes (are|have been) disabled' "$PUSH_LOG"; then | |
| echo "::warning::AUR publication delayed by Arch maintenance/security controls. Re-dispatch once pushes are restored." | |
| exit 0 | |
| fi | |
| echo "Failed to push mindwtr-bin to AUR." >&2 | |
| exit 1 | |
| update-aur-source: | |
| needs: [validate, release] | |
| runs-on: ubuntu-latest | |
| # Direct publish: dongdongbh is a co-maintainer of the community `mindwtr` | |
| # source package (first maintainer: yochananmarqos), so releases push it | |
| # like mindwtr-bin instead of parking a proposal artifact. The prepared | |
| # tree is still uploaded as an artifact so every push has an exact record. | |
| name: Update AUR (mindwtr source) | |
| timeout-minutes: 60 | |
| permissions: | |
| contents: read | |
| if: ${{ always() && needs.validate.result == 'success' && !contains(github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name, '-') && ((startsWith(github.ref, 'refs/tags/') && github.event_name == 'push' && needs.release.result == 'success') || (github.event_name == 'workflow_dispatch' && inputs.run_update_aur_source && (needs.release.result == 'success' || needs.release.result == 'skipped'))) }} | |
| steps: | |
| - name: Checkout release repo | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| with: | |
| path: source | |
| - name: Configure SSH for AUR | |
| shell: bash | |
| env: | |
| AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }} | |
| run: | | |
| set -euo pipefail | |
| if [ -z "${AUR_SSH_PRIVATE_KEY}" ]; then | |
| echo "Missing AUR_SSH_PRIVATE_KEY secret." | |
| exit 1 | |
| fi | |
| install -d -m 700 ~/.ssh | |
| printf '%s\n' "${AUR_SSH_PRIVATE_KEY}" > ~/.ssh/aur | |
| chmod 600 ~/.ssh/aur | |
| cat > ~/.ssh/config <<'EOF' | |
| Host aur.archlinux.org | |
| HostName aur.archlinux.org | |
| User aur | |
| IdentityFile ~/.ssh/aur | |
| IdentitiesOnly yes | |
| HostKeyAlgorithms ssh-ed25519 | |
| StrictHostKeyChecking yes | |
| EOF | |
| : > ~/.ssh/known_hosts | |
| chmod 600 ~/.ssh/known_hosts | |
| ssh-keyscan -t ed25519 aur.archlinux.org > "$RUNNER_TEMP/aur-known-hosts" 2>/dev/null | |
| ACTUAL_FINGERPRINT="$(ssh-keygen -lf "$RUNNER_TEMP/aur-known-hosts" -E sha256 | awk '{print $2}')" | |
| EXPECTED_FINGERPRINT='SHA256:RFzBCUItH9LZS0cKB5UE6ceAYhBD5C8GeOBip8Z11+4' | |
| if [ "$ACTUAL_FINGERPRINT" != "$EXPECTED_FINGERPRINT" ]; then | |
| echo "AUR SSH host fingerprint mismatch: expected ${EXPECTED_FINGERPRINT}, got ${ACTUAL_FINGERPRINT:-missing}." >&2 | |
| exit 1 | |
| fi | |
| cp "$RUNNER_TEMP/aur-known-hosts" ~/.ssh/known_hosts | |
| - name: Clone AUR repo | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| rm -rf aur-mindwtr | |
| for attempt in 1 2 3; do | |
| if git clone ssh://aur@aur.archlinux.org/mindwtr.git aur-mindwtr; then | |
| exit 0 | |
| fi | |
| echo "AUR clone attempt ${attempt} failed; retrying..." >&2 | |
| rm -rf aur-mindwtr | |
| sleep 3 | |
| done | |
| echo "Failed to clone mindwtr from AUR after 3 attempts." >&2 | |
| exit 1 | |
| - name: Resolve release version | |
| id: version | |
| env: | |
| INPUT_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }} | |
| GITHUB_EVENT_INPUTS_TAG: ${{ github.event.inputs.tag }} | |
| run: | | |
| if [ -n "${INPUT_TAG:-}" ] && ! echo "${INPUT_TAG}" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$'; then | |
| echo "Invalid tag format" >&2 | |
| exit 1 | |
| fi | |
| source/scripts/ci/resolve-release-version.sh "$INPUT_TAG" >> "$GITHUB_OUTPUT" | |
| - name: Update PKGBUILD version | |
| id: pkgbuild_version | |
| shell: bash | |
| working-directory: aur-mindwtr | |
| run: | | |
| VERSION=${{ steps.version.outputs.version }} | |
| echo "Updating mindwtr PKGBUILD to version $VERSION" | |
| CURRENT_PKGVER="$(sed -n 's/^pkgver=//p' PKGBUILD | head -n 1)" | |
| if [ "$CURRENT_PKGVER" != "$VERSION" ]; then | |
| sed -i "s/^pkgver=.*/pkgver=$VERSION/" PKGBUILD | |
| sed -i "s/^pkgrel=.*/pkgrel=1/" PKGBUILD | |
| echo "pkgver_changed=true" >> "$GITHUB_OUTPUT" | |
| echo "pkgver changed (${CURRENT_PKGVER} -> ${VERSION}); reset pkgrel to 1" | |
| else | |
| echo "pkgver_changed=false" >> "$GITHUB_OUTPUT" | |
| echo "pkgver unchanged (${VERSION}); keep current pkgrel for now" | |
| fi | |
| - name: Use release archive for source package | |
| shell: bash | |
| working-directory: aur-mindwtr | |
| run: | | |
| set -euo pipefail | |
| python3 - <<'PY' | |
| from pathlib import Path | |
| import re | |
| path = Path("PKGBUILD") | |
| text = path.read_text(encoding="utf-8") | |
| archive_source = ( | |
| 'source=("$pkgname-$pkgver.tar.gz::https://github.com/dongdongbh/Mindwtr/archive/refs/tags/v$pkgver.tar.gz"\n' | |
| ' "$pkgname.desktop"\n' | |
| ')' | |
| ) | |
| text, replaced = re.subn( | |
| r'source=\("git\+https://github\.com/dongdongbh/Mindwtr\.git#tag=v\$pkgver"\s*\n\s*"\$pkgname\.desktop"\s*\)', | |
| archive_source, | |
| text, | |
| count=1, | |
| ) | |
| if replaced: | |
| print("Switched source package from git tag clone to GitHub release archive.") | |
| elif "Mindwtr/archive/refs/tags/v$pkgver.tar.gz" in text: | |
| print("Source package already uses the GitHub release archive.") | |
| else: | |
| raise SystemExit("Could not find the expected Mindwtr source entry in PKGBUILD") | |
| text = text.replace('cd Mindwtr/apps/desktop/src-tauri', 'cd "$srcdir/Mindwtr-$pkgver/apps/desktop/src-tauri"') | |
| text = text.replace('cd Mindwtr/apps/desktop', 'cd "$srcdir/Mindwtr-$pkgver/apps/desktop"') | |
| text = text.replace('cd Mindwtr', 'cd "$srcdir/Mindwtr-$pkgver"') | |
| path.write_text(text, encoding="utf-8") | |
| PY | |
| - name: Sync Tauri compatibility patch | |
| shell: bash | |
| working-directory: aur-mindwtr | |
| run: | | |
| set -euo pipefail | |
| SOURCE_TAURI_CONF="../source/apps/desktop/src-tauri/tauri.conf.json" | |
| if jq -e '.bundle.macOS.infoPlist? // empty' "$SOURCE_TAURI_CONF" >/dev/null; then | |
| echo "Upstream tauri.conf.json still uses infoPlist; keeping tauri-v2-schema.patch in the AUR package." | |
| exit 0 | |
| fi | |
| echo "Upstream tauri.conf.json already uses AUR-compatible macOS files config; removing stale tauri-v2-schema.patch." | |
| rm -f tauri-v2-schema.patch | |
| sed -i \ | |
| -e "s/[[:space:]]*'tauri-v2-schema\\.patch'//" \ | |
| -e "/tauri_conf_v2_compat:/d" \ | |
| -e "/patch -Np1 -i \\.\\.\\/tauri-v2-schema\\.patch/d" \ | |
| PKGBUILD | |
| - name: Normalize desktop test runner for source package | |
| shell: bash | |
| working-directory: aur-mindwtr | |
| run: | | |
| set -euo pipefail | |
| python3 - <<'PY' | |
| from pathlib import Path | |
| path = Path("PKGBUILD") | |
| text = path.read_text(encoding="utf-8") | |
| if " pnpm run test || :\n" in text: | |
| print("PKGBUILD already uses pnpm run test") | |
| elif " bun run test || :\n" in text or " bun test || :\n" in text: | |
| text = text.replace( | |
| " # Relax tests: 116 passed, 15 failed\n", | |
| " # Run the desktop Vitest suite, but do not fail the package build on test failures.\n", | |
| 1, | |
| ) | |
| text = text.replace(" bun run test || :\n", " pnpm run test || :\n", 1) | |
| text = text.replace(" bun test || :\n", " pnpm run test || :\n", 1) | |
| path.write_text(text, encoding="utf-8") | |
| print("Updated PKGBUILD to use pnpm run test") | |
| else: | |
| raise SystemExit("Could not find the desktop test command in PKGBUILD") | |
| PY | |
| - name: Require Node 22 for prebuilt native modules | |
| shell: bash | |
| working-directory: aur-mindwtr | |
| run: | | |
| set -euo pipefail | |
| NODE_MAJOR="$(sed -n 's/^_nodeversion=//p' PKGBUILD | head -n 1)" | |
| if [ -z "$NODE_MAJOR" ]; then | |
| echo "::error::PKGBUILD has no _nodeversion pin; bun would run native install scripts with an unknown Node." | |
| exit 1 | |
| fi | |
| if [ "$NODE_MAJOR" -ge 22 ]; then | |
| echo "PKGBUILD already pins Node ${NODE_MAJOR}." | |
| else | |
| sed -i 's/^_nodeversion=.*/_nodeversion=22/' PKGBUILD | |
| echo "Bumped PKGBUILD _nodeversion ${NODE_MAJOR} -> 22 (better-sqlite3 12 ships no prebuilds below Node 22, and the build container has no node-gyp)." | |
| fi | |
| - name: Generate frozen pnpm lockfile for AUR | |
| shell: bash | |
| env: | |
| VERSION: ${{ steps.version.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| LOCK_WORKSPACE="$(mktemp -d)" | |
| trap 'rm -rf "$LOCK_WORKSPACE"' EXIT | |
| mkdir -p "$LOCK_WORKSPACE/source" | |
| curl -fL --retry 3 --retry-delay 3 \ | |
| "https://github.com/dongdongbh/Mindwtr/archive/refs/tags/v${VERSION}.tar.gz" \ | |
| -o "$LOCK_WORKSPACE/mindwtr.tar.gz" | |
| tar -xzf "$LOCK_WORKSPACE/mindwtr.tar.gz" \ | |
| -C "$LOCK_WORKSPACE/source" \ | |
| --strip-components=1 | |
| cat > "$LOCK_WORKSPACE/source/pnpm-workspace.yaml" <<'EOF' | |
| packages: | |
| - apps/desktop | |
| - packages/core | |
| patchedDependencies: | |
| mdast-util-gfm-autolink-literal@2.0.1: patches/mdast-util-gfm-autolink-literal@2.0.1.patch | |
| EOF | |
| HOST_UID="$(id -u)" | |
| HOST_GID="$(id -g)" | |
| docker run --rm \ | |
| -e HOST_UID="$HOST_UID" \ | |
| -e HOST_GID="$HOST_GID" \ | |
| -v "$LOCK_WORKSPACE/source:/source" \ | |
| -v "$PWD/aur-mindwtr:/aur" \ | |
| -w /source \ | |
| archlinux:latest \ | |
| bash -lc ' | |
| set -euo pipefail | |
| pacman -Sy --noconfirm --needed pnpm | |
| getent group "$HOST_GID" >/dev/null 2>&1 || groupadd -g "$HOST_GID" builder | |
| id -u builder >/dev/null 2>&1 || useradd -m -u "$HOST_UID" -g "$HOST_GID" builder | |
| chown -R builder:builder /source /aur | |
| su builder -c "pnpm install --lockfile-only --ignore-scripts --no-frozen-lockfile" | |
| install -o "$HOST_UID" -g "$HOST_GID" -m 0644 /source/pnpm-lock.yaml /aur/pnpm-lock.yaml | |
| install -o "$HOST_UID" -g "$HOST_GID" -m 0644 /source/pnpm-workspace.yaml /aur/pnpm-workspace.yaml | |
| ' | |
| if ! grep -Eq "^lockfileVersion: '9\\.0'$" aur-mindwtr/pnpm-lock.yaml; then | |
| echo "Generated AUR lockfile is not in the expected pnpm 11 format." >&2 | |
| exit 1 | |
| fi | |
| - name: Use frozen pnpm lockfile | |
| shell: bash | |
| working-directory: aur-mindwtr | |
| run: | | |
| set -euo pipefail | |
| python3 - <<'PY' | |
| from pathlib import Path | |
| import re | |
| path = Path("PKGBUILD") | |
| text = path.read_text(encoding="utf-8") | |
| text = text.replace(" 'bun'\n", " 'pnpm'\n") | |
| if " 'pnpm'\n" not in text: | |
| raise SystemExit("Could not replace the Bun make dependency with pnpm") | |
| source_pattern = re.compile( | |
| r'(source=\(.*?\n\s*"\$pkgname\.desktop"\n)' | |
| r'(?:\s*"(?:bun\.lock|pnpm-lock\.yaml|pnpm-workspace\.yaml)"\n)*' | |
| r'(\))', | |
| re.DOTALL, | |
| ) | |
| text, source_replacements = source_pattern.subn( | |
| r'\1 "pnpm-lock.yaml"\n' | |
| r' "pnpm-workspace.yaml"\n\2', | |
| text, | |
| count=1, | |
| ) | |
| if source_replacements != 1: | |
| raise SystemExit("Could not add pnpm lock files to the PKGBUILD source array") | |
| prepare_pattern = re.compile( | |
| r'( nvm install "\$\{_nodeversion\}"\n)' | |
| r'.*?' | |
| r'(\n export RUSTUP_TOOLCHAIN=stable)', | |
| re.DOTALL, | |
| ) | |
| text, install_replacements = prepare_pattern.subn( | |
| r'\1\n' | |
| r' cp "$srcdir/pnpm-lock.yaml" pnpm-lock.yaml\n' | |
| r' cp "$srcdir/pnpm-workspace.yaml" pnpm-workspace.yaml\n' | |
| r' pnpm install --frozen-lockfile --ignore-scripts --store-dir "$srcdir/pnpm-store"\n' | |
| r" sed -i 's/\"beforeBuildCommand\": \"bun run build:vite\"/\"beforeBuildCommand\": \"pnpm run build:vite\"/' apps/desktop/src-tauri/tauri.conf.json\n" | |
| r'\2', | |
| text, | |
| count=1, | |
| ) | |
| if install_replacements != 1: | |
| raise SystemExit("Could not replace the dependency install command in PKGBUILD") | |
| text = text.replace(' export BUN_INSTALL_CACHE_DIR="$srcdir/bun-cache"\n', '') | |
| text = text.replace(' bun run test || :\n', ' pnpm run test || :\n') | |
| if "bun install" in text or "bun run test" in text: | |
| raise SystemExit("PKGBUILD still invokes Bun") | |
| path.write_text(text, encoding="utf-8") | |
| print("PKGBUILD now installs the desktop/core workspace from the shipped pnpm lockfile") | |
| PY | |
| rm -f bun.lock | |
| git add -N -f -- pnpm-lock.yaml pnpm-workspace.yaml | |
| - name: Update checksums | |
| shell: bash | |
| working-directory: aur-mindwtr | |
| run: | | |
| HOST_UID="$(id -u)" | |
| HOST_GID="$(id -g)" | |
| docker run --rm \ | |
| -e HOST_UID="$HOST_UID" \ | |
| -e HOST_GID="$HOST_GID" \ | |
| -v "$PWD:/aur" \ | |
| -w /aur \ | |
| archlinux:latest \ | |
| bash -lc ' | |
| set -euo pipefail | |
| pacman -Sy --noconfirm --needed base-devel git pacman-contrib | |
| getent group "$HOST_GID" >/dev/null 2>&1 || groupadd -g "$HOST_GID" builder | |
| id -u builder >/dev/null 2>&1 || useradd -m -u "$HOST_UID" -g "$HOST_GID" builder | |
| if command -v updpkgsums >/dev/null 2>&1; then | |
| su builder -c "updpkgsums" | |
| else | |
| # Fallback path if updpkgsums is unavailable. | |
| su builder -c "makepkg -g > /tmp/makepkg-sums.txt" | |
| awk " | |
| /^[_[:alnum:]]*sums=\\(/ { in_sums=1; next } | |
| in_sums && /^\\)/ { in_sums=0; next } | |
| !in_sums { print } | |
| " /aur/PKGBUILD > /tmp/PKGBUILD.nosums | |
| { | |
| cat /tmp/PKGBUILD.nosums | |
| cat /tmp/makepkg-sums.txt | |
| } > /aur/PKGBUILD | |
| fi | |
| ' | |
| - name: Bump pkgrel for packaging-only changes | |
| shell: bash | |
| working-directory: aur-mindwtr | |
| run: | | |
| if [ "${{ steps.pkgbuild_version.outputs.pkgver_changed }}" = "false" ] && ! git diff --quiet -- PKGBUILD bun.lock pnpm-lock.yaml pnpm-workspace.yaml; then | |
| CURRENT_PKGREL="$(sed -n 's/^pkgrel=//p' PKGBUILD | head -n 1)" | |
| if [[ "$CURRENT_PKGREL" =~ ^[0-9]+$ ]]; then | |
| NEW_PKGREL="$((CURRENT_PKGREL + 1))" | |
| sed -i "s/^pkgrel=.*/pkgrel=${NEW_PKGREL}/" PKGBUILD | |
| echo "pkgver unchanged and checksums changed; bump pkgrel ${CURRENT_PKGREL} -> ${NEW_PKGREL}" | |
| else | |
| echo "::error::pkgrel is not numeric (${CURRENT_PKGREL}); cannot auto-bump." | |
| exit 1 | |
| fi | |
| fi | |
| - name: Update .SRCINFO | |
| shell: bash | |
| working-directory: aur-mindwtr | |
| run: | | |
| HOST_UID="$(id -u)" | |
| HOST_GID="$(id -g)" | |
| docker run --rm \ | |
| -e HOST_UID="$HOST_UID" \ | |
| -e HOST_GID="$HOST_GID" \ | |
| -v "$PWD:/aur" \ | |
| -w /aur \ | |
| archlinux:latest \ | |
| bash -lc ' | |
| set -euo pipefail | |
| pacman -Sy --noconfirm --needed base-devel git | |
| getent group "$HOST_GID" >/dev/null 2>&1 || groupadd -g "$HOST_GID" builder | |
| id -u builder >/dev/null 2>&1 || useradd -m -u "$HOST_UID" -g "$HOST_GID" builder | |
| su builder -c "makepkg --printsrcinfo > /aur/.SRCINFO" | |
| ' | |
| - name: Validate AUR package contents | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| node source/scripts/ci/validate-aur-package.mjs \ | |
| --package-dir aur-mindwtr \ | |
| --package mindwtr | |
| - name: Validate source package build (clean container) | |
| shell: bash | |
| working-directory: aur-mindwtr | |
| run: | | |
| HOST_UID="$(id -u)" | |
| HOST_GID="$(id -g)" | |
| docker run --rm \ | |
| -e HOST_UID="$HOST_UID" \ | |
| -e HOST_GID="$HOST_GID" \ | |
| -v "$PWD:/aur" \ | |
| -w /aur \ | |
| archlinux:latest \ | |
| bash -lc ' | |
| set -euo pipefail | |
| pacman -Sy --noconfirm --needed base-devel git | |
| mapfile -t pkgdeps < <(bash -lc " | |
| source PKGBUILD | |
| printf \"%s\\n\" \"\${depends[@]}\" \"\${makedepends[@]}\" \"\${checkdepends[@]}\" \ | |
| | sed -E \"s/[<>=].*$//\" \ | |
| | awk \"NF\" \ | |
| | sort -u | |
| ") | |
| if [ "${#pkgdeps[@]}" -gt 0 ]; then | |
| pacman -S --noconfirm --needed "${pkgdeps[@]}" | |
| fi | |
| getent group "$HOST_GID" >/dev/null 2>&1 || groupadd -g "$HOST_GID" builder | |
| id -u builder >/dev/null 2>&1 || useradd -m -u "$HOST_UID" -g "$HOST_GID" builder | |
| chown -R builder:builder /aur | |
| su builder -c "makepkg --cleanbuild --clean --nodeps --noconfirm" | |
| ' | |
| - name: Verify AUR package ownership before push | |
| shell: bash | |
| run: node source/scripts/ci/audit-aur-state.mjs | |
| - name: Prepare immutable publish record | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| bash source/scripts/ci/prepare-aur-proposal.sh \ | |
| aur-mindwtr \ | |
| "${{ steps.version.outputs.tag }}" \ | |
| aur-proposal | |
| - name: Preserve exact published tree as an artifact | |
| uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 | |
| with: | |
| name: aur-proposal-mindwtr-${{ steps.version.outputs.tag }} | |
| path: aur-proposal/ | |
| include-hidden-files: true | |
| if-no-files-found: error | |
| retention-days: 90 | |
| - name: Commit and push changes | |
| shell: bash | |
| working-directory: aur-mindwtr | |
| env: | |
| AUR_USERNAME: ${{ secrets.AUR_USERNAME }} | |
| AUR_EMAIL: ${{ secrets.AUR_EMAIL }} | |
| run: | | |
| set -euo pipefail | |
| if [ -z "$(git status --porcelain -- PKGBUILD .SRCINFO pnpm-lock.yaml pnpm-workspace.yaml tauri-v2-schema.patch bun.lock)" ]; then | |
| echo "No AUR source-package changes to publish." | |
| exit 0 | |
| fi | |
| git config --local user.name "${AUR_USERNAME:-mindwtr-bot}" | |
| git config --local user.email "${AUR_EMAIL:-actions@github.com}" | |
| git add PKGBUILD .SRCINFO | |
| git add -f -- pnpm-lock.yaml pnpm-workspace.yaml | |
| if git ls-files --error-unmatch bun.lock >/dev/null 2>&1; then | |
| git add -u -- bun.lock | |
| fi | |
| if [ -n "$(git status --porcelain -- tauri-v2-schema.patch)" ]; then | |
| git add -A -- tauri-v2-schema.patch | |
| fi | |
| git commit -m "Update to ${{ steps.version.outputs.tag }}" | |
| PUSH_LOG="$RUNNER_TEMP/aur-source-push.log" | |
| if git push origin master 2>&1 | tee "$PUSH_LOG"; then | |
| exit 0 | |
| fi | |
| if grep -Eqi 'AUR is down due to maintenance|disabled (all )?AUR pushes|pushes (are|have been) disabled' "$PUSH_LOG"; then | |
| echo "::warning::AUR publication delayed by Arch maintenance/security controls. Re-dispatch once pushes are restored." | |
| exit 0 | |
| fi | |
| echo "Failed to push mindwtr to AUR." >&2 | |
| exit 1 |