Added the Batch Processing Assistant (#901) #1181
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: Build and Release | |
| on: | |
| push: | |
| branches: | |
| - main | |
| tags: | |
| - "v*.*.*" | |
| pull_request: | |
| types: | |
| - opened | |
| - labeled | |
| - synchronize | |
| - reopened | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && (github.event.action != 'labeled' || github.event.label.name == 'run-pipeline') && github.event.pull_request.number || github.run_id }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' && (github.event.action != 'labeled' || github.event.label.name == 'run-pipeline') }} | |
| env: | |
| RETENTION_INTERMEDIATE_ASSETS: 1 | |
| RETENTION_RELEASE_ASSETS: 30 | |
| FLATPAK_REPOSITORY: MindWorkAI/Flatpak | |
| FLATPAK_WORKFLOW: flatpak.yml | |
| FLATPAK_YQ_VERSION: 4.44.6 | |
| FLATPAK_YQ_SHA256: 0c2b24e645b57d8e7c0566d18643a6d4f5580feeea3878127354a46f2a1e4598 | |
| FLATPAK_UV_VERSION: 0.11.28 | |
| FLATPAK_UV_SHA256: e490a6464492183c5d4534a5527fb4440f7f2bb2f228162ad7e4afe076dc0224 | |
| FLATPAK_FREEDESKTOP_VERSION: "25.08" | |
| jobs: | |
| determine_run_mode: | |
| name: Determine run mode | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| outputs: | |
| is_release: ${{ steps.determine.outputs.is_release }} | |
| is_main_push: ${{ steps.determine.outputs.is_main_push }} | |
| is_labeled_pr: ${{ steps.determine.outputs.is_labeled_pr }} | |
| is_pr_build: ${{ steps.determine.outputs.is_pr_build }} | |
| is_internal_pr: ${{ steps.determine.outputs.is_internal_pr }} | |
| build_enabled: ${{ steps.determine.outputs.build_enabled }} | |
| artifact_retention_days: ${{ steps.determine.outputs.artifact_retention_days }} | |
| skip_reason: ${{ steps.determine.outputs.skip_reason }} | |
| steps: | |
| - name: Determine run mode | |
| id: determine | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| PR_ACTION: ${{ github.event.action }} | |
| ACTION_LABEL_NAME: ${{ github.event.label.name }} | |
| REF: ${{ github.ref }} | |
| PR_LABELS: ${{ join(github.event.pull_request.labels.*.name, ' ') }} | |
| PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} | |
| REPOSITORY: ${{ github.repository }} | |
| run: | | |
| is_release=false | |
| is_main_push=false | |
| is_labeled_pr=false | |
| is_pr_build=false | |
| is_internal_pr=false | |
| build_enabled=false | |
| artifact_retention_days=0 | |
| skip_reason="Build disabled: event did not match main push, release tag, or labeled internal PR." | |
| if [[ "$EVENT_NAME" == "pull_request" && "$PR_HEAD_REPO" == "$REPOSITORY" ]]; then | |
| is_internal_pr=true | |
| fi | |
| has_run_pipeline_label=false | |
| if [[ " $PR_LABELS " == *" run-pipeline "* ]]; then | |
| has_run_pipeline_label=true | |
| fi | |
| if [[ "$REF" == refs/tags/v* ]]; then | |
| is_release=true | |
| build_enabled=true | |
| artifact_retention_days=${{ env.RETENTION_INTERMEDIATE_ASSETS }} | |
| skip_reason="" | |
| elif [[ "$EVENT_NAME" == "push" && "$REF" == "refs/heads/main" ]]; then | |
| is_main_push=true | |
| build_enabled=true | |
| artifact_retention_days=7 | |
| skip_reason="" | |
| elif [[ "$EVENT_NAME" == "pull_request" && "$PR_ACTION" == "labeled" && "$ACTION_LABEL_NAME" == "run-pipeline" ]]; then | |
| is_labeled_pr=true | |
| is_pr_build=true | |
| build_enabled=true | |
| artifact_retention_days=3 | |
| skip_reason="" | |
| elif [[ "$EVENT_NAME" == "pull_request" && "$PR_ACTION" != "labeled" && "$has_run_pipeline_label" == "true" ]]; then | |
| is_labeled_pr=true | |
| is_pr_build=true | |
| build_enabled=true | |
| artifact_retention_days=3 | |
| skip_reason="" | |
| elif [[ "$EVENT_NAME" == "pull_request" && "$PR_ACTION" == "labeled" ]]; then | |
| skip_reason="Build disabled: label '${ACTION_LABEL_NAME}' is not 'run-pipeline'." | |
| elif [[ "$EVENT_NAME" == "pull_request" && "$has_run_pipeline_label" != "true" ]]; then | |
| skip_reason="Build disabled: PR does not have the required 'run-pipeline' label." | |
| fi | |
| echo "is_release=${is_release}" >> "$GITHUB_OUTPUT" | |
| echo "is_main_push=${is_main_push}" >> "$GITHUB_OUTPUT" | |
| echo "is_labeled_pr=${is_labeled_pr}" >> "$GITHUB_OUTPUT" | |
| echo "is_pr_build=${is_pr_build}" >> "$GITHUB_OUTPUT" | |
| echo "is_internal_pr=${is_internal_pr}" >> "$GITHUB_OUTPUT" | |
| echo "build_enabled=${build_enabled}" >> "$GITHUB_OUTPUT" | |
| echo "artifact_retention_days=${artifact_retention_days}" >> "$GITHUB_OUTPUT" | |
| echo "skip_reason=${skip_reason}" >> "$GITHUB_OUTPUT" | |
| - name: Log run mode | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| REF: ${{ github.ref }} | |
| PR_LABELS: ${{ join(github.event.pull_request.labels.*.name, ', ') }} | |
| PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} | |
| REPOSITORY: ${{ github.repository }} | |
| IS_RELEASE: ${{ steps.determine.outputs.is_release }} | |
| IS_MAIN_PUSH: ${{ steps.determine.outputs.is_main_push }} | |
| IS_LABELED_PR: ${{ steps.determine.outputs.is_labeled_pr }} | |
| IS_PR_BUILD: ${{ steps.determine.outputs.is_pr_build }} | |
| IS_INTERNAL_PR: ${{ steps.determine.outputs.is_internal_pr }} | |
| BUILD_ENABLED: ${{ steps.determine.outputs.build_enabled }} | |
| ARTIFACT_RETENTION_DAYS: ${{ steps.determine.outputs.artifact_retention_days }} | |
| SKIP_REASON: ${{ steps.determine.outputs.skip_reason }} | |
| run: | | |
| echo "event_name: ${EVENT_NAME}" | |
| echo "ref: ${REF}" | |
| echo "repository: ${REPOSITORY}" | |
| echo "pr_head_repo: ${PR_HEAD_REPO}" | |
| echo "pr_labels: ${PR_LABELS}" | |
| echo "is_release: ${IS_RELEASE}" | |
| echo "is_main_push: ${IS_MAIN_PUSH}" | |
| echo "is_labeled_pr: ${IS_LABELED_PR}" | |
| echo "is_pr_build: ${IS_PR_BUILD}" | |
| echo "is_internal_pr: ${IS_INTERNAL_PR}" | |
| echo "build_enabled: ${BUILD_ENABLED}" | |
| echo "artifact_retention_days: ${ARTIFACT_RETENTION_DAYS}" | |
| echo "skip_reason: ${SKIP_REASON}" | |
| { | |
| echo "### Run Mode" | |
| echo "" | |
| echo "| Key | Value |" | |
| echo "| --- | --- |" | |
| echo "| event_name | ${EVENT_NAME} |" | |
| echo "| ref | ${REF} |" | |
| echo "| repository | ${REPOSITORY} |" | |
| echo "| pr_head_repo | ${PR_HEAD_REPO} |" | |
| echo "| pr_labels | ${PR_LABELS} |" | |
| echo "| is_release | ${IS_RELEASE} |" | |
| echo "| is_main_push | ${IS_MAIN_PUSH} |" | |
| echo "| is_labeled_pr | ${IS_LABELED_PR} |" | |
| echo "| is_pr_build | ${IS_PR_BUILD} |" | |
| echo "| is_internal_pr | ${IS_INTERNAL_PR} |" | |
| echo "| build_enabled | ${BUILD_ENABLED} |" | |
| echo "| artifact_retention_days | ${ARTIFACT_RETENTION_DAYS} |" | |
| echo "| skip_reason | ${SKIP_REASON} |" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| read_metadata: | |
| name: Read metadata | |
| runs-on: ubuntu-latest | |
| needs: determine_run_mode | |
| if: needs.determine_run_mode.outputs.build_enabled == 'true' | |
| permissions: | |
| contents: read | |
| outputs: | |
| formatted_version: ${{ steps.format_metadata.outputs.formatted_version }} | |
| formatted_build_time: ${{ steps.format_metadata.outputs.formatted_build_time }} | |
| changelog: ${{ steps.read_changelog.outputs.changelog }} | |
| version: ${{ steps.format_metadata.outputs.version }} | |
| source_commit: ${{ steps.format_metadata.outputs.source_commit }} | |
| pdfium_chromium_revision: ${{ steps.format_metadata.outputs.pdfium_chromium_revision }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Read and format metadata | |
| id: format_metadata | |
| run: | | |
| # Read the first two lines of the metadata file: | |
| version=$(sed -n '1p' metadata.txt) | |
| build_time=$(sed -n '2p' metadata.txt) | |
| pdfium_full_version=$(sed -n '11p' metadata.txt) | |
| pdfium_chromium_revision=$(echo "$pdfium_full_version" | cut -d'.' -f3) | |
| source_commit=$(git rev-parse HEAD) | |
| # Format the version: | |
| formatted_version="v${version}" | |
| # Format the build time according to RFC 3339: | |
| formatted_build_time=$(date -d "${build_time}" --utc +'%Y-%m-%dT%H:%M:%SZ') | |
| # Log the formatted metadata: | |
| echo "Formatted version: '${formatted_version}'" | |
| echo "Formatted build time: '${formatted_build_time}'" | |
| echo "Source commit: '${source_commit}'" | |
| echo "PDFium Chromium revision: '${pdfium_chromium_revision}'" | |
| # Set the outputs: | |
| echo "formatted_version=${formatted_version}" >> "$GITHUB_OUTPUT" | |
| echo "FORMATTED_VERSION=${formatted_version}" >> $GITHUB_ENV | |
| echo "formatted_build_time=${formatted_build_time}" >> "$GITHUB_OUTPUT" | |
| echo "version=${version}" >> "$GITHUB_OUTPUT" | |
| echo "source_commit=${source_commit}" >> "$GITHUB_OUTPUT" | |
| echo "pdfium_chromium_revision=${pdfium_chromium_revision}" >> "$GITHUB_OUTPUT" | |
| - name: Check tag vs. metadata version | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| run: | | |
| # Ensure, that the tag matches the version in the metadata file: | |
| if [ "${GITHUB_REF}" != "refs/tags/${FORMATTED_VERSION}" ]; then | |
| echo "Tag '${GITHUB_REF}' does not match the version in the metadata file '${FORMATTED_VERSION}'" | |
| exit 1 | |
| else | |
| echo "Tag '${GITHUB_REF}' matches the version in the metadata file '${FORMATTED_VERSION}'" | |
| fi | |
| - name: Read changelog | |
| id: read_changelog | |
| if: needs.determine_run_mode.outputs.is_release == 'true' | |
| run: | | |
| # Ensure, that the matching changelog file for the current version exists: | |
| if [ ! -f "app/MindWork AI Studio/wwwroot/changelog/${FORMATTED_VERSION}.md" ]; then | |
| echo "Changelog file 'app/MindWork AI Studio/wwwroot/changelog/${FORMATTED_VERSION}.md' not found" | |
| exit 1 | |
| else | |
| echo "Changelog file '${FORMATTED_VERSION}.md' found" | |
| fi | |
| # Read the changelog file: | |
| changelog=$(cat "app/MindWork AI Studio/wwwroot/changelog/${FORMATTED_VERSION}.md") | |
| # Set the output: | |
| echo "changelog<<EOOOF" >> "$GITHUB_OUTPUT" | |
| echo "${changelog}" >> "$GITHUB_OUTPUT" | |
| echo "EOOOF" >> "$GITHUB_OUTPUT" | |
| sync_flatpak_repo: | |
| name: Sync Flatpak repo | |
| runs-on: ubuntu-latest | |
| needs: [determine_run_mode, read_metadata] | |
| if: needs.determine_run_mode.outputs.is_release == 'true' | |
| permissions: | |
| contents: read | |
| outputs: | |
| flatpak_commit: ${{ steps.sync.outputs.flatpak_commit }} | |
| env: | |
| AI_STUDIO_TAG: ${{ needs.read_metadata.outputs.formatted_version }} | |
| AI_STUDIO_COMMIT: ${{ needs.read_metadata.outputs.source_commit }} | |
| PDFIUM_CHROMIUM_REVISION: ${{ needs.read_metadata.outputs.pdfium_chromium_revision }} | |
| steps: | |
| - name: Checkout AI Studio release metadata | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ env.AI_STUDIO_COMMIT }} | |
| path: ai-studio | |
| sparse-checkout: | | |
| metadata.txt | |
| runtime/packaging/linux/org.mindworkai.AIStudio.desktop | |
| runtime/packaging/linux/org.mindworkai.AIStudio.metainfo.xml | |
| sparse-checkout-cone-mode: false | |
| - name: Checkout Flatpak repository | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: ${{ env.FLATPAK_REPOSITORY }} | |
| token: ${{ secrets.FLATPAK_WORKFLOW_TOKEN }} | |
| ref: main | |
| path: flatpak | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Install Flatpak sync tools and SDK | |
| run: | | |
| set -euo pipefail | |
| sudo apt-get update | |
| sudo apt-get install --no-install-recommends -y flatpak | |
| flatpak remote-add \ | |
| --user \ | |
| --if-not-exists \ | |
| flathub \ | |
| https://flathub.org/repo/flathub.flatpakrepo | |
| flatpak install \ | |
| --user \ | |
| --noninteractive \ | |
| -y \ | |
| flathub \ | |
| "org.freedesktop.Sdk//${FLATPAK_FREEDESKTOP_VERSION}" \ | |
| "org.freedesktop.Sdk.Extension.dotnet9//${FLATPAK_FREEDESKTOP_VERSION}" | |
| tools_dir="$RUNNER_TEMP/flatpak-sync-tools" | |
| mkdir -p "$tools_dir" | |
| curl -fsSL \ | |
| -o "$tools_dir/yq" \ | |
| "https://github.com/mikefarah/yq/releases/download/v${FLATPAK_YQ_VERSION}/yq_linux_amd64" | |
| echo "${FLATPAK_YQ_SHA256} ${tools_dir}/yq" | sha256sum --check --strict | |
| chmod +x "$tools_dir/yq" | |
| uv_archive="$RUNNER_TEMP/uv-x86_64-unknown-linux-gnu.tar.gz" | |
| curl -fsSL \ | |
| -o "$uv_archive" \ | |
| "https://github.com/astral-sh/uv/releases/download/${FLATPAK_UV_VERSION}/uv-x86_64-unknown-linux-gnu.tar.gz" | |
| echo "${FLATPAK_UV_SHA256} ${uv_archive}" | sha256sum --check --strict | |
| tar -xzf "$uv_archive" -C "$tools_dir" | |
| install -m 0755 "$tools_dir/uv-x86_64-unknown-linux-gnu/uv" "$tools_dir/uv" | |
| echo "$tools_dir" >> "$GITHUB_PATH" | |
| "$tools_dir/uv" --version | |
| "$tools_dir/yq" --version | |
| - name: Update Flatpak release sources | |
| working-directory: flatpak | |
| env: | |
| PDFIUM_X64_ARCHIVE: ${{ runner.temp }}/pdfium-linux-x64.tgz | |
| PDFIUM_ARM64_ARCHIVE: ${{ runner.temp }}/pdfium-linux-arm64.tgz | |
| run: | | |
| set -euo pipefail | |
| release_version=$(sed -n '1p' ../ai-studio/metadata.txt) | |
| release_timestamp=$(sed -n '2p' ../ai-studio/metadata.txt) | |
| release_date=${release_timestamp%% *} | |
| test "$release_version" = "${AI_STUDIO_TAG#v}" | |
| [[ "$release_timestamp" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}[[:space:]][0-9]{2}:[0-9]{2}:[0-9]{2}[[:space:]]UTC$ ]] | |
| test -s ../ai-studio/runtime/packaging/linux/org.mindworkai.AIStudio.desktop | |
| python3 ./update-metainfo.py \ | |
| --check \ | |
| --metainfo ../ai-studio/runtime/packaging/linux/org.mindworkai.AIStudio.metainfo.xml \ | |
| "$release_version" \ | |
| "$release_date" | |
| pdfium_base_url="https://github.com/bblanchon/pdfium-binaries/releases/download/chromium%2F${PDFIUM_CHROMIUM_REVISION}" | |
| pdfium_x64_url="${pdfium_base_url}/pdfium-linux-x64.tgz" | |
| pdfium_arm64_url="${pdfium_base_url}/pdfium-linux-arm64.tgz" | |
| curl -fsSL -o "$PDFIUM_X64_ARCHIVE" "$pdfium_x64_url" | |
| curl -fsSL -o "$PDFIUM_ARM64_ARCHIVE" "$pdfium_arm64_url" | |
| export PDFIUM_X64_URL="$pdfium_x64_url" | |
| export PDFIUM_ARM64_URL="$pdfium_arm64_url" | |
| export PDFIUM_X64_SHA256 | |
| export PDFIUM_ARM64_SHA256 | |
| PDFIUM_X64_SHA256=$(sha256sum "$PDFIUM_X64_ARCHIVE" | awk '{print $1}') | |
| PDFIUM_ARM64_SHA256=$(sha256sum "$PDFIUM_ARM64_ARCHIVE" | awk '{print $1}') | |
| yq -i ' | |
| (.modules[] | select(.name == "mind-work-ai-studio").sources[0].tag) = strenv(AI_STUDIO_TAG) | | |
| (.modules[] | select(.name == "mind-work-ai-studio").sources[0].commit) = strenv(AI_STUDIO_COMMIT) | | |
| (.modules[] | select(.name == "mind-work-ai-studio").sources[] | select(.type == "archive" and .only-arches[0] == "x86_64").url) = strenv(PDFIUM_X64_URL) | | |
| (.modules[] | select(.name == "mind-work-ai-studio").sources[] | select(.type == "archive" and .only-arches[0] == "x86_64").sha256) = strenv(PDFIUM_X64_SHA256) | | |
| (.modules[] | select(.name == "mind-work-ai-studio").sources[] | select(.type == "archive" and .only-arches[0] == "aarch64").url) = strenv(PDFIUM_ARM64_URL) | | |
| (.modules[] | select(.name == "mind-work-ai-studio").sources[] | select(.type == "archive" and .only-arches[0] == "aarch64").sha256) = strenv(PDFIUM_ARM64_SHA256) | |
| ' org.mindworkai.AIStudio.yml | |
| ./update-dependencies | |
| test "$(yq -r '(.modules[] | select(.name == "mind-work-ai-studio")).sources[0].tag' org.mindworkai.AIStudio.yml)" = "$AI_STUDIO_TAG" | |
| test "$(yq -r '(.modules[] | select(.name == "mind-work-ai-studio")).sources[0].commit' org.mindworkai.AIStudio.yml)" = "$AI_STUDIO_COMMIT" | |
| test "$(yq -r '(.modules[] | select(.name == "mind-work-ai-studio")).sources[] | select(.type == "archive" and .only-arches[0] == "x86_64").url' org.mindworkai.AIStudio.yml)" = "$PDFIUM_X64_URL" | |
| test "$(yq -r '(.modules[] | select(.name == "mind-work-ai-studio")).sources[] | select(.type == "archive" and .only-arches[0] == "x86_64").sha256' org.mindworkai.AIStudio.yml)" = "$PDFIUM_X64_SHA256" | |
| test "$(yq -r '(.modules[] | select(.name == "mind-work-ai-studio")).sources[] | select(.type == "archive" and .only-arches[0] == "aarch64").url' org.mindworkai.AIStudio.yml)" = "$PDFIUM_ARM64_URL" | |
| test "$(yq -r '(.modules[] | select(.name == "mind-work-ai-studio")).sources[] | select(.type == "archive" and .only-arches[0] == "aarch64").sha256' org.mindworkai.AIStudio.yml)" = "$PDFIUM_ARM64_SHA256" | |
| for generated_source in cargo-sources.json dotnet-sources.json tauri-cli-sources.json; do | |
| test -s "$generated_source" | |
| jq -e 'type == "array" and length > 0' "$generated_source" > /dev/null | |
| done | |
| git diff --check | |
| git diff --stat | |
| - name: Commit and merge Flatpak sync | |
| id: sync | |
| working-directory: flatpak | |
| env: | |
| GH_TOKEN: ${{ secrets.FLATPAK_WORKFLOW_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| branch="sync/ai-studio-${AI_STUDIO_TAG}" | |
| git checkout -B "$branch" | |
| git add org.mindworkai.AIStudio.yml cargo-sources.json dotnet-sources.json tauri-cli-sources.json | |
| if git diff --cached --quiet; then | |
| echo "Flatpak repository is already synced for ${AI_STUDIO_TAG}." | |
| flatpak_commit=$(git rev-parse HEAD) | |
| remote_main=$(git ls-remote origin refs/heads/main | awk '{print $1}') | |
| if [ "$remote_main" != "$flatpak_commit" ]; then | |
| echo "Flatpak main advanced from ${flatpak_commit} to ${remote_main} during synchronization." | |
| exit 1 | |
| fi | |
| echo "flatpak_commit=${flatpak_commit}" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| git commit -m "Sync AI Studio ${AI_STUDIO_TAG}" | |
| sync_commit=$(git rev-parse HEAD) | |
| basic_auth=$(printf 'x-access-token:%s' "$GH_TOKEN" | base64 -w 0) | |
| git config --local http.https://github.com/.extraheader "AUTHORIZATION: basic ${basic_auth}" | |
| trap 'git config --local --unset-all http.https://github.com/.extraheader || true' EXIT | |
| remote_branch_sha=$(git ls-remote --heads origin "$branch" | awk '{print $1}') | |
| if [ -n "$remote_branch_sha" ]; then | |
| git push --force-with-lease="refs/heads/${branch}:${remote_branch_sha}" origin "HEAD:${branch}" | |
| else | |
| git push origin "HEAD:${branch}" | |
| fi | |
| pr_number=$(gh pr list \ | |
| --repo "$FLATPAK_REPOSITORY" \ | |
| --head "$branch" \ | |
| --state open \ | |
| --json number \ | |
| --jq '.[0].number // empty') | |
| if [ -z "$pr_number" ]; then | |
| pr_url=$(gh pr create \ | |
| --repo "$FLATPAK_REPOSITORY" \ | |
| --base main \ | |
| --head "$branch" \ | |
| --title "Sync AI Studio ${AI_STUDIO_TAG}" \ | |
| --body "Synchronizes the Flatpak manifest and generated dependency sources for MindWork AI Studio ${AI_STUDIO_TAG}.") | |
| pr_number="${pr_url##*/}" | |
| fi | |
| mergeable="UNKNOWN" | |
| for attempt in {1..30}; do | |
| pr_state=$(gh pr view "$pr_number" --repo "$FLATPAK_REPOSITORY" --json mergeable,mergeStateStatus) | |
| mergeable=$(echo "$pr_state" | jq -r '.mergeable') | |
| merge_state_status=$(echo "$pr_state" | jq -r '.mergeStateStatus') | |
| echo "PR #${pr_number}: mergeable=${mergeable}, mergeStateStatus=${merge_state_status}" | |
| if [ "$mergeable" = "MERGEABLE" ]; then | |
| break | |
| fi | |
| if [ "$mergeable" = "CONFLICTING" ]; then | |
| echo "Flatpak sync PR #${pr_number} has merge conflicts." | |
| exit 1 | |
| fi | |
| sleep 5 | |
| done | |
| if [ "$mergeable" != "MERGEABLE" ]; then | |
| echo "Timed out waiting for Flatpak sync PR #${pr_number} to become mergeable." | |
| exit 1 | |
| fi | |
| gh pr merge "$pr_number" \ | |
| --repo "$FLATPAK_REPOSITORY" \ | |
| --squash \ | |
| --delete-branch \ | |
| --match-head-commit "$sync_commit" | |
| for attempt in {1..120}; do | |
| pr_state=$(gh pr view "$pr_number" \ | |
| --repo "$FLATPAK_REPOSITORY" \ | |
| --json state,mergedAt,mergeCommit) | |
| state=$(echo "$pr_state" | jq -r '.state') | |
| merged_at=$(echo "$pr_state" | jq -r '.mergedAt // empty') | |
| merge_commit=$(echo "$pr_state" | jq -r '.mergeCommit.oid // empty') | |
| echo "PR #${pr_number}: state=${state}, mergedAt=${merged_at:-pending}, mergeCommit=${merge_commit:-pending}" | |
| if [ -n "$merged_at" ] && [ -n "$merge_commit" ]; then | |
| echo "flatpak_commit=${merge_commit}" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| if [ "$state" = "CLOSED" ]; then | |
| echo "Flatpak sync PR #${pr_number} was closed without being merged." | |
| exit 1 | |
| fi | |
| sleep 5 | |
| done | |
| echo "Timed out waiting for Flatpak sync PR #${pr_number} to be merged." | |
| exit 1 | |
| collect_flatpak_artifacts: | |
| name: Collect Flatpak artifacts | |
| runs-on: ubuntu-latest | |
| needs: [determine_run_mode, read_metadata, sync_flatpak_repo] | |
| if: needs.determine_run_mode.outputs.is_release == 'true' | |
| permissions: | |
| contents: read | |
| env: | |
| FLATPAK_COMMIT: ${{ needs.sync_flatpak_repo.outputs.flatpak_commit }} | |
| steps: | |
| - name: Dispatch and wait for Flatpak build | |
| id: flatpak_run | |
| env: | |
| GH_TOKEN: ${{ secrets.FLATPAK_WORKFLOW_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| find_run_id() { | |
| local created_after="$1" | |
| local runs | |
| runs=$(gh run list \ | |
| --repo "$FLATPAK_REPOSITORY" \ | |
| --workflow "$FLATPAK_WORKFLOW" \ | |
| --branch main \ | |
| --commit "$FLATPAK_COMMIT" \ | |
| --limit 20 \ | |
| --json databaseId,event,headSha,createdAt) | |
| echo "$runs" | jq -r \ | |
| --arg commit "$FLATPAK_COMMIT" \ | |
| --arg created_after "$created_after" \ | |
| '[.[] | select(.headSha == $commit and .event == "workflow_dispatch" and .createdAt >= $created_after)][0].databaseId // empty' | |
| } | |
| validate_required_artifacts() { | |
| local run_id="$1" | |
| local artifacts | |
| local expected_name | |
| local match_count | |
| local custom_count | |
| artifacts=$(gh api "repos/${FLATPAK_REPOSITORY}/actions/runs/${run_id}/artifacts?per_page=100") | |
| custom_count=$(echo "$artifacts" | jq \ | |
| '[.artifacts[] | select(.name | startswith("MindWork AI Studio Flatpak ("))] | length') | |
| if [ "$custom_count" -ne 2 ]; then | |
| echo "Flatpak run ${run_id} contains ${custom_count} release artifacts; expected 2." | |
| return 1 | |
| fi | |
| for expected_name in \ | |
| "MindWork AI Studio Flatpak (x86_64)" \ | |
| "MindWork AI Studio Flatpak (aarch64)"; do | |
| match_count=$(echo "$artifacts" | jq \ | |
| --arg name "$expected_name" \ | |
| '[.artifacts[] | select(.name == $name and .expired == false)] | length') | |
| if [ "$match_count" -ne 1 ]; then | |
| echo "Flatpak run ${run_id} does not contain one active '${expected_name}' artifact." | |
| return 1 | |
| fi | |
| done | |
| } | |
| wait_for_run() { | |
| local run_id="$1" | |
| local run_json | |
| local status | |
| local conclusion | |
| local head_sha | |
| local url | |
| for attempt in {1..180}; do | |
| run_json=$(gh run view "$run_id" \ | |
| --repo "$FLATPAK_REPOSITORY" \ | |
| --json status,conclusion,headSha,url) | |
| status=$(echo "$run_json" | jq -r '.status') | |
| conclusion=$(echo "$run_json" | jq -r '.conclusion // empty') | |
| head_sha=$(echo "$run_json" | jq -r '.headSha') | |
| url=$(echo "$run_json" | jq -r '.url') | |
| echo "Flatpak run ${run_id}: status=${status}, conclusion=${conclusion:-pending}, url=${url}" | |
| if [ "$head_sha" != "$FLATPAK_COMMIT" ]; then | |
| echo "Flatpak run ${run_id} targets '${head_sha}', expected '${FLATPAK_COMMIT}'." | |
| return 1 | |
| fi | |
| if [ "$status" = "completed" ]; then | |
| if [ "$conclusion" = "success" ] && validate_required_artifacts "$run_id"; then | |
| return 0 | |
| fi | |
| echo "Flatpak run ${run_id} completed without usable release artifacts." | |
| return 1 | |
| fi | |
| sleep 20 | |
| done | |
| echo "Timed out waiting for Flatpak run ${run_id}." | |
| return 2 | |
| } | |
| current_main=$(gh api "repos/${FLATPAK_REPOSITORY}/commits/main" --jq .sha) | |
| if [ "$current_main" != "$FLATPAK_COMMIT" ]; then | |
| echo "Flatpak main advanced from ${FLATPAK_COMMIT} to ${current_main} before the build could be dispatched." | |
| exit 1 | |
| fi | |
| dispatch_started_at=$(date --utc +'%Y-%m-%dT%H:%M:%SZ') | |
| gh workflow run "$FLATPAK_WORKFLOW" \ | |
| --repo "$FLATPAK_REPOSITORY" \ | |
| --ref main \ | |
| -f "artifact_retention_days=${RETENTION_INTERMEDIATE_ASSETS}" | |
| run_id="" | |
| for attempt in {1..15}; do | |
| run_id=$(find_run_id "$dispatch_started_at") | |
| if [ -n "$run_id" ]; then | |
| break | |
| fi | |
| echo "Waiting for the dispatched Flatpak workflow on commit ${FLATPAK_COMMIT}..." | |
| sleep 20 | |
| done | |
| if [ -z "$run_id" ]; then | |
| echo "Timed out waiting for a Flatpak workflow to start on commit ${FLATPAK_COMMIT}." | |
| exit 1 | |
| fi | |
| set +e | |
| wait_for_run "$run_id" | |
| wait_result=$? | |
| set -e | |
| if [ "$wait_result" -eq 0 ]; then | |
| echo "run_id=${run_id}" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| if [ "$wait_result" -eq 2 ]; then | |
| exit 1 | |
| fi | |
| echo "Re-running Flatpak run ${run_id} once." | |
| previous_attempt=$(gh api "repos/${FLATPAK_REPOSITORY}/actions/runs/${run_id}" --jq .run_attempt) | |
| gh run rerun "$run_id" --repo "$FLATPAK_REPOSITORY" | |
| rerun_started=false | |
| for attempt in {1..60}; do | |
| rerun_state=$(gh api "repos/${FLATPAK_REPOSITORY}/actions/runs/${run_id}") | |
| current_attempt=$(echo "$rerun_state" | jq -r '.run_attempt') | |
| current_status=$(echo "$rerun_state" | jq -r '.status') | |
| if [ "$current_attempt" -gt "$previous_attempt" ] || [ "$current_status" != "completed" ]; then | |
| rerun_started=true | |
| break | |
| fi | |
| sleep 2 | |
| done | |
| if [ "$rerun_started" != "true" ]; then | |
| echo "Timed out waiting for Flatpak run ${run_id} to start its retry." | |
| exit 1 | |
| fi | |
| if ! wait_for_run "$run_id"; then | |
| echo "Flatpak run ${run_id} did not succeed after one retry." | |
| exit 1 | |
| fi | |
| echo "run_id=${run_id}" >> "$GITHUB_OUTPUT" | |
| - name: Download Flatpak artifacts | |
| env: | |
| GH_TOKEN: ${{ secrets.FLATPAK_WORKFLOW_TOKEN }} | |
| RUN_ID: ${{ steps.flatpak_run.outputs.run_id }} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p flatpak-artifacts | |
| gh run download "$RUN_ID" \ | |
| --repo "$FLATPAK_REPOSITORY" \ | |
| --name "MindWork AI Studio Flatpak (x86_64)" \ | |
| --name "MindWork AI Studio Flatpak (aarch64)" \ | |
| --dir flatpak-artifacts | |
| expected_files=( | |
| "MindWork AI Studio_x86_64.flatpak" | |
| "MindWork AI Studio Plugin Pandoc_x86_64.flatpak" | |
| "MindWork AI Studio_aarch64.flatpak" | |
| "MindWork AI Studio Plugin Pandoc_aarch64.flatpak" | |
| ) | |
| flatpak_count=$(find flatpak-artifacts -type f -name '*.flatpak' | wc -l | tr -d ' ') | |
| if [ "$flatpak_count" -ne 4 ]; then | |
| echo "Downloaded ${flatpak_count} Flatpak files; expected 4." | |
| find flatpak-artifacts -type f -print | |
| exit 1 | |
| fi | |
| for expected_file in "${expected_files[@]}"; do | |
| match_count=$(find flatpak-artifacts -type f -name "$expected_file" | wc -l | tr -d ' ') | |
| if [ "$match_count" -ne 1 ]; then | |
| echo "Expected exactly one '${expected_file}', found ${match_count}." | |
| exit 1 | |
| fi | |
| matched_file=$(find flatpak-artifacts -type f -name "$expected_file" -print -quit) | |
| test -s "$matched_file" | |
| done | |
| find flatpak-artifacts -type f -name '*.flatpak' -print | |
| - name: Upload Flatpak artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: MindWork AI Studio Flatpak Release | |
| path: flatpak-artifacts/**/*.flatpak | |
| if-no-files-found: error | |
| overwrite: true | |
| retention-days: ${{ env.RETENTION_INTERMEDIATE_ASSETS }} | |
| build_main: | |
| name: Build app (${{ matrix.dotnet_runtime }}) | |
| needs: [determine_run_mode, read_metadata] | |
| if: needs.determine_run_mode.outputs.build_enabled == 'true' | |
| permissions: | |
| contents: read | |
| strategy: | |
| fail-fast: true | |
| matrix: | |
| include: | |
| - platform: 'macos-latest' # for ARM-based macOS (M1 and above) | |
| rust_target: 'aarch64-apple-darwin' | |
| dotnet_runtime: 'osx-arm64' | |
| dotnet_name_postfix: '-aarch64-apple-darwin' | |
| tauri_bundle: 'dmg,app,updater' | |
| tauri_bundle_pr: 'dmg' | |
| - platform: 'macos-latest' # for Intel-based macOS | |
| rust_target: 'x86_64-apple-darwin' | |
| dotnet_runtime: 'osx-x64' | |
| dotnet_name_postfix: '-x86_64-apple-darwin' | |
| tauri_bundle: 'dmg,app,updater' | |
| tauri_bundle_pr: 'dmg' | |
| - platform: 'ubuntu-22.04' # for x86-based Linux | |
| rust_target: 'x86_64-unknown-linux-gnu' | |
| dotnet_runtime: 'linux-x64' | |
| dotnet_name_postfix: '-x86_64-unknown-linux-gnu' | |
| tauri_bundle: 'appimage,updater' | |
| tauri_bundle_pr: 'appimage' | |
| - platform: 'ubuntu-22.04-arm' # for ARM-based Linux | |
| rust_target: 'aarch64-unknown-linux-gnu' | |
| dotnet_runtime: 'linux-arm64' | |
| dotnet_name_postfix: '-aarch64-unknown-linux-gnu' | |
| tauri_bundle: 'appimage,updater' | |
| tauri_bundle_pr: 'appimage' | |
| - platform: 'windows-latest' # for x86-based Windows | |
| rust_target: 'x86_64-pc-windows-msvc' | |
| dotnet_runtime: 'win-x64' | |
| dotnet_name_postfix: '-x86_64-pc-windows-msvc.exe' | |
| tauri_bundle: 'nsis,updater' | |
| tauri_bundle_pr: 'nsis' | |
| - platform: 'windows-latest' # for ARM-based Windows | |
| rust_target: 'aarch64-pc-windows-msvc' | |
| dotnet_runtime: 'win-arm64' | |
| dotnet_name_postfix: '-aarch64-pc-windows-msvc.exe' | |
| tauri_bundle: 'nsis,updater' | |
| tauri_bundle_pr: 'nsis' | |
| runs-on: ${{ matrix.platform }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| lfs: false | |
| - name: Read and format metadata (Unix) | |
| if: matrix.platform != 'windows-latest' | |
| run: | | |
| # Read the lines of the metadata file: | |
| app_version=$(sed -n '1p' metadata.txt) | |
| build_time=$(sed -n '2p' metadata.txt) | |
| build_number=$(sed -n '3p' metadata.txt) | |
| # Next line is the .NET SDK version. | |
| # The format is '8.0.205 (commit 3e1383b780)'. | |
| # We extract only the version number, though: | |
| dotnet_sdk_version=$(sed -n '4p' metadata.txt | sed 's/[^0-9.]*\([0-9.]*\).*/\1/') | |
| # Next line is the .NET runtime version. | |
| # The format is '8.0.5 (commit 087e15321b)'. | |
| # We extract only the version number, though: | |
| dotnet_runtime_version=$(sed -n '5p' metadata.txt | sed 's/[^0-9.]*\([0-9.]*\).*/\1/') | |
| # Next line is the Rust version. | |
| # The format is '1.78.0 (commit 9b00956e5)'. | |
| # We extract only the version number, though: | |
| rust_version=$(sed -n '6p' metadata.txt | sed 's/[^0-9.]*\([0-9.]*\).*/\1/') | |
| # Next line is the MudBlazor version: | |
| mud_blazor_version=$(sed -n '7p' metadata.txt) | |
| # Next line is the Tauri version: | |
| tauri_version=$(sed -n '8p' metadata.txt) | |
| # Format the app version: | |
| formatted_app_version="v${app_version}" | |
| # Set the architecture: | |
| if sed --version 2>/dev/null | grep -q GNU; then | |
| sed -i "10s/.*/${{ matrix.dotnet_runtime }}/" metadata.txt | |
| else | |
| sed -i '' "10s/.*/${{ matrix.dotnet_runtime }}/" metadata.txt | |
| fi | |
| # Next line is the necessary PDFium version. | |
| # The format is '137.0.7123.0'. What we need | |
| # is the '7123' part: | |
| pdfium_version=$(sed -n '11p' metadata.txt) | |
| pdfium_version=$(echo $pdfium_version | cut -d'.' -f3) | |
| # Next line is the vector store version: | |
| vector_store_version="$(sed -n '12p' metadata.txt)" | |
| # Write the metadata to the environment: | |
| echo "APP_VERSION=${app_version}" >> $GITHUB_ENV | |
| echo "FORMATTED_APP_VERSION=${formatted_app_version}" >> $GITHUB_ENV | |
| echo "BUILD_TIME=${build_time}" >> $GITHUB_ENV | |
| echo "BUILD_NUMBER=${build_number}" >> $GITHUB_ENV | |
| echo "DOTNET_SDK_VERSION=${dotnet_sdk_version}" >> $GITHUB_ENV | |
| echo "DOTNET_RUNTIME_VERSION=${dotnet_runtime_version}" >> $GITHUB_ENV | |
| echo "RUST_VERSION=${rust_version}" >> $GITHUB_ENV | |
| echo "MUD_BLAZOR_VERSION=${mud_blazor_version}" >> $GITHUB_ENV | |
| echo "TAURI_VERSION=${tauri_version}" >> $GITHUB_ENV | |
| echo "ARCHITECTURE=${{ matrix.dotnet_runtime }}" >> $GITHUB_ENV | |
| echo "PDFIUM_VERSION=${pdfium_version}" >> $GITHUB_ENV | |
| echo "VECTOR_STORE_VERSION=${vector_store_version}" >> $GITHUB_ENV | |
| # Log the metadata: | |
| echo "App version: '${formatted_app_version}'" | |
| echo "Build time: '${build_time}'" | |
| echo "Build number: '${build_number}'" | |
| echo ".NET SDK version: '${dotnet_sdk_version}'" | |
| echo ".NET runtime version: '${dotnet_runtime_version}'" | |
| echo "Rust version: '${rust_version}'" | |
| echo "MudBlazor version: '${mud_blazor_version}'" | |
| echo "Tauri version: '${tauri_version}'" | |
| echo "Architecture: '${{ matrix.dotnet_runtime }}'" | |
| echo "PDFium version: '${pdfium_version}'" | |
| echo "Vector store version: '${vector_store_version}'" | |
| - name: Read and format metadata (Windows) | |
| if: matrix.platform == 'windows-latest' | |
| run: | | |
| # Read the lines of the metadata file: | |
| $metadata = Get-Content metadata.txt | |
| $app_version = $metadata[0] | |
| $build_time = $metadata[1] | |
| $build_number = $metadata[2] | |
| # Next line is the .NET SDK version. | |
| # The format is '8.0.205 (commit 3e1383b780)'. | |
| # We extract only the version number, though: | |
| $dotnet_sdk_version = ($metadata[3] -match '(\d+\.\d+\.\d+)') | Out-Null; $dotnet_sdk_version = $matches[1] | |
| # Next line is the .NET runtime version. | |
| # The format is '8.0.5 (commit 087e15321b)'. | |
| # We extract only the version number, though: | |
| $dotnet_runtime_version = ($metadata[4] -match '(\d+\.\d+\.\d+)') | Out-Null; $dotnet_runtime_version = $matches[1] | |
| # Next line is the Rust version. | |
| # The format is '1.78.0 (commit 9b00956e5)'. | |
| # We extract only the version number, though: | |
| $rust_version = ($metadata[5] -match '(\d+\.\d+\.\d+)') | Out-Null; $rust_version = $matches[1] | |
| $mud_blazor_version = $metadata[6] | |
| $tauri_version = $metadata[7] | |
| # Format the app version: | |
| $formatted_app_version = "v${app_version}" | |
| # Set the architecture: | |
| $metadata[9] = "${{ matrix.dotnet_runtime }}" | |
| # Write the changed metadata back to the file: | |
| Set-Content -Path metadata.txt -Value $metadata | |
| # Next line is the necessary PDFium version. | |
| # The format is '137.0.7123.0'. What we need | |
| # is the '7123' part: | |
| $pdfium_version = $metadata[10] | |
| $pdfium_version = $pdfium_version.Split('.')[2] | |
| # Next line is the vector store version: | |
| $vector_store_version = $metadata[11] | |
| # Write the metadata to the environment: | |
| Write-Output "APP_VERSION=${app_version}" >> $env:GITHUB_ENV | |
| Write-Output "FORMATTED_APP_VERSION=${formatted_app_version}" >> $env:GITHUB_ENV | |
| Write-Output "BUILD_TIME=${build_time}" >> $env:GITHUB_ENV | |
| Write-Output "BUILD_NUMBER=${build_number}" >> $env:GITHUB_ENV | |
| Write-Output "DOTNET_SDK_VERSION=${dotnet_sdk_version}" >> $env:GITHUB_ENV | |
| Write-Output "DOTNET_RUNTIME_VERSION=${dotnet_runtime_version}" >> $env:GITHUB_ENV | |
| Write-Output "RUST_VERSION=${rust_version}" >> $env:GITHUB_ENV | |
| Write-Output "MUD_BLAZOR_VERSION=${mud_blazor_version}" >> $env:GITHUB_ENV | |
| Write-Output "ARCHITECTURE=${{ matrix.dotnet_runtime }}" >> $env:GITHUB_ENV | |
| Write-Output "PDFIUM_VERSION=${pdfium_version}" >> $env:GITHUB_ENV | |
| Write-Output "VECTOR_STORE_VERSION=${vector_store_version}" >> $env:GITHUB_ENV | |
| # Log the metadata: | |
| Write-Output "App version: '${formatted_app_version}'" | |
| Write-Output "Build time: '${build_time}'" | |
| Write-Output "Build number: '${build_number}'" | |
| Write-Output ".NET SDK version: '${dotnet_sdk_version}'" | |
| Write-Output ".NET runtime version: '${dotnet_runtime_version}'" | |
| Write-Output "Rust version: '${rust_version}'" | |
| Write-Output "MudBlazor version: '${mud_blazor_version}'" | |
| Write-Output "Tauri version: '${tauri_version}'" | |
| Write-Output "Architecture: '${{ matrix.dotnet_runtime }}'" | |
| Write-Output "PDFium version: '${pdfium_version}'" | |
| Write-Output "Vector store version: '${vector_store_version}'" | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: ${{ env.DOTNET_SDK_VERSION }} | |
| cache: true | |
| cache-dependency-path: 'app/MindWork AI Studio/packages.lock.json' | |
| - name: Deploy PDFium (Unix) | |
| if: matrix.platform != 'windows-latest' | |
| env: | |
| PDFIUM_VERSION: ${{ env.PDFIUM_VERSION }} | |
| DOTNET_RUNTIME: ${{ matrix.dotnet_runtime }} | |
| run: | | |
| set -e | |
| # Target directory: | |
| TLIB_DIR="runtime/resources/libraries" | |
| mkdir -p "$TLIB_DIR" | |
| case "${DOTNET_RUNTIME}" in | |
| linux-x64) | |
| PDFIUM_FILE="linux-x64.tgz" | |
| LIB_SOURCE="lib/libpdfium.so" | |
| LIB_TARGET="libpdfium.so" | |
| ;; | |
| linux-arm64) | |
| PDFIUM_FILE="linux-arm64.tgz" | |
| LIB_SOURCE="lib/libpdfium.so" | |
| LIB_TARGET="libpdfium.so" | |
| ;; | |
| osx-x64) | |
| PDFIUM_FILE="mac-x64.tgz" | |
| LIB_SOURCE="lib/libpdfium.dylib" | |
| LIB_TARGET="libpdfium.dylib" | |
| ;; | |
| osx-arm64) | |
| PDFIUM_FILE="mac-arm64.tgz" | |
| LIB_SOURCE="lib/libpdfium.dylib" | |
| LIB_TARGET="libpdfium.dylib" | |
| ;; | |
| *) | |
| echo "Unknown platform: ${DOTNET_RUNTIME}" | |
| exit 1 | |
| ;; | |
| esac | |
| PDFIUM_URL="https://github.com/bblanchon/pdfium-binaries/releases/download/chromium%2F${PDFIUM_VERSION}/pdfium-${PDFIUM_FILE}" | |
| echo "Download PDFium $PDFIUM_URL ..." | |
| TMP=$(mktemp -d) | |
| ARCHIVE="${TMP}/pdfium.tgz" | |
| curl -fsSL -o "$ARCHIVE" "$PDFIUM_URL" | |
| echo "Extracting PDFium ..." | |
| tar xzf "$ARCHIVE" -C "$TMP" | |
| SRC="${TMP}/${LIB_SOURCE}" | |
| if [ ! -f "$SRC" ]; then | |
| echo "Was not able to find PDFium source: $SRC" | |
| exit 1 | |
| fi | |
| echo "Copy PDFium from ${LIB_TARGET} to ${TLIB_DIR}/" | |
| cp -f "$SRC" "$TLIB_DIR/$LIB_TARGET" | |
| echo "Cleaning up ..." | |
| rm -fr "$TMP" | |
| - name: Deploy PDFium (Windows) | |
| if: matrix.platform == 'windows-latest' | |
| env: | |
| PDFIUM_VERSION: ${{ env.PDFIUM_VERSION }} | |
| DOTNET_RUNTIME: ${{ matrix.dotnet_runtime }} | |
| run: | | |
| $TLIB_DIR = "runtime\resources\libraries" | |
| New-Item -ItemType Directory -Force -Path $TLIB_DIR | Out-Null | |
| switch ($env:DOTNET_RUNTIME) { | |
| "win-x64" { | |
| $PDFIUM_FILE = "win-x64.tgz" | |
| $LIB_SOURCE = "bin\pdfium.dll" | |
| $LIB_TARGET = "pdfium.dll" | |
| } | |
| "win-arm64" { | |
| $PDFIUM_FILE = "win-arm64.tgz" | |
| $LIB_SOURCE = "bin\pdfium.dll" | |
| $LIB_TARGET = "pdfium.dll" | |
| } | |
| default { | |
| Write-Error "Unknown platform: $($env:DOTNET_RUNTIME)" | |
| exit 1 | |
| } | |
| } | |
| $PDFIUM_URL = "https://github.com/bblanchon/pdfium-binaries/releases/download/chromium%2F$($env:PDFIUM_VERSION)/pdfium-$PDFIUM_FILE" | |
| Write-Host "Download $PDFIUM_URL ..." | |
| # Create a unique temporary directory (not just a file) | |
| $TMP = Join-Path ([System.IO.Path]::GetTempPath()) ([System.IO.Path]::GetRandomFileName()) | |
| New-Item -ItemType Directory -Path $TMP -Force | Out-Null | |
| $ARCHIVE = Join-Path $TMP "pdfium.tgz" | |
| Invoke-WebRequest -Uri $PDFIUM_URL -OutFile $ARCHIVE | |
| Write-Host "Extracting PDFium ..." | |
| tar -xzf $ARCHIVE -C $TMP | |
| $SRC = Join-Path $TMP $LIB_SOURCE | |
| if (!(Test-Path $SRC)) { | |
| Write-Error "Cannot find PDFium source: $SRC" | |
| exit 1 | |
| } | |
| $DEST = Join-Path $TLIB_DIR $LIB_TARGET | |
| Copy-Item -Path $SRC -Destination $DEST -Force | |
| Write-Host "Cleaning up ..." | |
| Remove-Item $ARCHIVE -Force -ErrorAction SilentlyContinue | |
| # Try to remove the temporary directory, but ignore errors if files are still in use | |
| try { | |
| Remove-Item $TMP -Recurse -Force -ErrorAction Stop | |
| Write-Host "Successfully cleaned up temporary directory: $TMP" | |
| } catch { | |
| Write-Warning "Could not fully clean up temporary directory: $TMP. This is usually harmless as Windows will clean it up later. Error: $($_.Exception.Message)" | |
| } | |
| - name: Build .NET project | |
| run: | | |
| cd "app/MindWork AI Studio" | |
| dotnet publish --configuration release --runtime ${{ matrix.dotnet_runtime }} --disable-build-servers --force --output ../../publish/dotnet | |
| - name: Move & rename .NET artifact (Unix) | |
| if: matrix.platform != 'windows-latest' | |
| run: | | |
| mkdir -p "app/MindWork AI Studio/bin/dist" | |
| cd publish/dotnet | |
| mv mindworkAIStudio "../../app/MindWork AI Studio/bin/dist/mindworkAIStudioServer${{ matrix.dotnet_name_postfix }}" | |
| - name: Move & rename .NET artifact (Windows) | |
| if: matrix.platform == 'windows-latest' | |
| run: | | |
| New-Item -ItemType Directory -Path "app\MindWork AI Studio\bin\dist" -Force | |
| cd publish/dotnet | |
| mv mindworkAIStudio.exe "../../app/MindWork AI Studio/bin/dist/mindworkAIStudioServer${{ matrix.dotnet_name_postfix }}" | |
| - name: Cache Rust | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/git/db/ | |
| ~/.cargo/registry/index/ | |
| ~/.cargo/registry/cache/ | |
| runtime/target | |
| key: target-${{ matrix.dotnet_runtime }}-rust-${{ env.RUST_VERSION }} | |
| - name: Setup Rust (stable) | |
| uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: ${{ env.RUST_VERSION }} | |
| targets: ${{ matrix.rust_target }} | |
| - name: Cache Tauri CLI | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cargo-tauri-cli | |
| key: tauri-cli-v2-${{ runner.os }}-${{ runner.arch }} | |
| - name: Setup dependencies (Ubuntu-specific, x86) | |
| if: matrix.platform == 'ubuntu-22.04' && contains(matrix.rust_target, 'x86_64') | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf libfuse2 xdg-utils gstreamer1.0-plugins-base gstreamer1.0-plugins-good | |
| - name: Setup dependencies (Ubuntu-specific, ARM) | |
| if: matrix.platform == 'ubuntu-22.04-arm' && contains(matrix.rust_target, 'aarch64') | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf libfuse2 xdg-utils gstreamer1.0-plugins-base gstreamer1.0-plugins-good | |
| - name: Setup Tauri (Unix) | |
| if: matrix.platform != 'windows-latest' | |
| run: | | |
| echo "$HOME/.cargo-tauri-cli/bin" >> "$GITHUB_PATH" | |
| export PATH="$HOME/.cargo-tauri-cli/bin:$PATH" | |
| if ! cargo tauri --version 2>/dev/null | grep -Eq '^tauri-cli 2\.'; then | |
| cargo install tauri-cli --version "^2.11.0" --locked --force --root "$HOME/.cargo-tauri-cli" | |
| else | |
| echo "Tauri CLI v2 is already installed" | |
| fi | |
| - name: Setup Tauri (Windows) | |
| if: matrix.platform == 'windows-latest' | |
| run: | | |
| "$env:USERPROFILE\.cargo-tauri-cli\bin" >> $env:GITHUB_PATH | |
| $env:PATH = "$env:USERPROFILE\.cargo-tauri-cli\bin;$env:PATH" | |
| $tauriVersion = cargo tauri --version 2>$null | |
| if (-not $tauriVersion -or $tauriVersion -notmatch '^tauri-cli 2\.') { | |
| cargo install tauri-cli --version "^2.11.0" --locked --force --root "$env:USERPROFILE\.cargo-tauri-cli" | |
| } else { | |
| Write-Output "Tauri CLI v2 is already installed" | |
| } | |
| - name: Delete previous artifact, which may exist due to caching (macOS) | |
| if: startsWith(matrix.platform, 'macos') | |
| run: | | |
| dmg_dir="runtime/target/${{ matrix.rust_target }}/release/bundle/dmg" | |
| macos_dir="runtime/target/${{ matrix.rust_target }}/release/bundle/macos" | |
| if [ -d "$dmg_dir" ]; then | |
| find "$dmg_dir" -maxdepth 1 -name 'MindWork AI Studio_*.dmg' -delete | |
| fi | |
| if [ -d "$macos_dir" ]; then | |
| find "$macos_dir" -maxdepth 1 -name '*.app' -exec rm -rf {} + | |
| find "$macos_dir" -maxdepth 1 -name '*.app.tar.gz*' -delete | |
| fi | |
| - name: Delete previous artifact, which may exist due to caching (Windows - MSI) | |
| if: startsWith(matrix.platform, 'windows') && contains(matrix.tauri_bundle, 'msi') | |
| run: | | |
| rm -Force "runtime/target/${{ matrix.rust_target }}/release/bundle/msi/MindWork AI Studio_*.msi" -ErrorAction SilentlyContinue | |
| rm -Force "runtime/target/${{ matrix.rust_target }}/release/bundle/msi/MindWork AI Studio*msi.zip*" -ErrorAction SilentlyContinue | |
| - name: Delete previous artifact, which may exist due to caching (Windows - NSIS) | |
| if: startsWith(matrix.platform, 'windows') && contains(matrix.tauri_bundle, 'nsis') | |
| run: | | |
| rm -Force "runtime/target/${{ matrix.rust_target }}/release/bundle/nsis/MindWork AI Studio_*.exe" -ErrorAction SilentlyContinue | |
| rm -Force "runtime/target/${{ matrix.rust_target }}/release/bundle/nsis/MindWork AI Studio*nsis.zip*" -ErrorAction SilentlyContinue | |
| - name: Delete previous artifact, which may exist due to caching (Linux - AppImage) | |
| if: startsWith(matrix.platform, 'ubuntu') && contains(matrix.tauri_bundle, 'appimage') | |
| run: | | |
| rm -f runtime/target/${{ matrix.rust_target }}/release/bundle/appimage/*.AppImage | |
| rm -f runtime/target/${{ matrix.rust_target }}/release/bundle/appimage/*.AppImage.tar.gz* | |
| - name: Build Tauri project (Unix) | |
| if: matrix.platform != 'windows-latest' | |
| env: | |
| PRIVATE_PUBLISH_KEY: ${{ secrets.PRIVATE_PUBLISH_KEY }} | |
| PRIVATE_PUBLISH_KEY_PASSWORD: ${{ secrets.PRIVATE_PUBLISH_KEY_PASSWORD }} | |
| run: | | |
| bundles="${{ matrix.tauri_bundle }}" | |
| tauri_config_args=() | |
| if [ "${{ needs.determine_run_mode.outputs.is_pr_build }}" = "true" ]; then | |
| echo "Running PR test build without updater bundle signing" | |
| bundles="${{ matrix.tauri_bundle_pr }}" | |
| tauri_config_args=(--config '{"bundle":{"createUpdaterArtifacts":false}}') | |
| else | |
| export TAURI_SIGNING_PRIVATE_KEY="$PRIVATE_PUBLISH_KEY" | |
| export TAURI_SIGNING_PRIVATE_KEY_PASSWORD="$PRIVATE_PUBLISH_KEY_PASSWORD" | |
| fi | |
| cd runtime | |
| cargo tauri build --target ${{ matrix.rust_target }} --bundles "$bundles" "${tauri_config_args[@]}" | |
| if [ "${{ needs.determine_run_mode.outputs.is_pr_build }}" = "true" ]; then | |
| updater_artifact_count=$(find target/${{ matrix.rust_target }}/release/bundle -type f \( -name '*.app.tar.gz*' -o -name '*.AppImage.tar.gz*' -o -name '*nsis.zip*' \) | wc -l) | |
| if [ "$updater_artifact_count" -ne 0 ]; then | |
| echo "PR builds must not generate updater artifacts." | |
| find target/${{ matrix.rust_target }}/release/bundle -type f \( -name '*.app.tar.gz*' -o -name '*.AppImage.tar.gz*' -o -name '*nsis.zip*' \) | |
| exit 1 | |
| fi | |
| fi | |
| if [ "${{ needs.determine_run_mode.outputs.is_pr_build }}" != "true" ] && [[ "${{ matrix.platform }}" == macos* ]]; then | |
| app_update_archive_count=$(find target/${{ matrix.rust_target }}/release/bundle/macos -maxdepth 1 -name '*.app.tar.gz' | wc -l) | |
| app_update_signature_count=$(find target/${{ matrix.rust_target }}/release/bundle/macos -maxdepth 1 -name '*.app.tar.gz.sig' | wc -l) | |
| if [ "$app_update_archive_count" -eq 0 ] || [ "$app_update_signature_count" -eq 0 ]; then | |
| echo "Expected macOS updater artifacts were not generated." | |
| exit 1 | |
| fi | |
| fi | |
| - name: Build Tauri project (Windows) | |
| if: matrix.platform == 'windows-latest' | |
| env: | |
| PRIVATE_PUBLISH_KEY: ${{ secrets.PRIVATE_PUBLISH_KEY }} | |
| PRIVATE_PUBLISH_KEY_PASSWORD: ${{ secrets.PRIVATE_PUBLISH_KEY_PASSWORD }} | |
| run: | | |
| $bundles = "${{ matrix.tauri_bundle }}" | |
| $tauriConfigArgs = @() | |
| if ("${{ needs.determine_run_mode.outputs.is_pr_build }}" -eq "true") { | |
| Write-Output "Running PR test build without updater bundle signing" | |
| $bundles = "${{ matrix.tauri_bundle_pr }}" | |
| $tauriConfigArgs = @("--config", '{"bundle":{"createUpdaterArtifacts":false}}') | |
| } else { | |
| $env:TAURI_SIGNING_PRIVATE_KEY="$env:PRIVATE_PUBLISH_KEY" | |
| $env:TAURI_SIGNING_PRIVATE_KEY_PASSWORD="$env:PRIVATE_PUBLISH_KEY_PASSWORD" | |
| } | |
| cd runtime | |
| cargo tauri build --target ${{ matrix.rust_target }} --bundles $bundles @tauriConfigArgs | |
| if ("${{ needs.determine_run_mode.outputs.is_pr_build }}" -eq "true") { | |
| $updaterArtifacts = Get-ChildItem -Path "target/${{ matrix.rust_target }}/release/bundle" -Recurse -File -Include "*.app.tar.gz*", "*.AppImage.tar.gz*", "*nsis.zip*" -ErrorAction SilentlyContinue | |
| if ($updaterArtifacts.Count -ne 0) { | |
| Write-Error "PR builds must not generate updater artifacts." | |
| $updaterArtifacts | ForEach-Object { Write-Error $_.FullName } | |
| exit 1 | |
| } | |
| } | |
| - name: Upload artifact (macOS) | |
| if: startsWith(matrix.platform, 'macos') | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: MindWork AI Studio (macOS ${{ matrix.dotnet_runtime }}) | |
| path: | | |
| runtime/target/${{ matrix.rust_target }}/release/bundle/dmg/MindWork AI Studio_*.dmg | |
| runtime/target/${{ matrix.rust_target }}/release/bundle/macos/*.app.tar.gz* | |
| if-no-files-found: error | |
| retention-days: ${{ fromJSON(needs.determine_run_mode.outputs.artifact_retention_days) }} | |
| - name: Upload artifact (Windows - MSI) | |
| if: startsWith(matrix.platform, 'windows') && contains(matrix.tauri_bundle, 'msi') | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: MindWork AI Studio (Windows - MSI ${{ matrix.dotnet_runtime }}) | |
| path: | | |
| runtime/target/${{ matrix.rust_target }}/release/bundle/msi/MindWork AI Studio_*.msi | |
| runtime/target/${{ matrix.rust_target }}/release/bundle/msi/MindWork AI Studio*msi.zip* | |
| if-no-files-found: error | |
| retention-days: ${{ fromJSON(needs.determine_run_mode.outputs.artifact_retention_days) }} | |
| - name: Upload artifact (Windows - NSIS) | |
| if: startsWith(matrix.platform, 'windows') && contains(matrix.tauri_bundle, 'nsis') | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: MindWork AI Studio (Windows - NSIS ${{ matrix.dotnet_runtime }}) | |
| path: | | |
| runtime/target/${{ matrix.rust_target }}/release/bundle/nsis/MindWork AI Studio_*.exe | |
| runtime/target/${{ matrix.rust_target }}/release/bundle/nsis/MindWork AI Studio*nsis.zip* | |
| if-no-files-found: error | |
| retention-days: ${{ fromJSON(needs.determine_run_mode.outputs.artifact_retention_days) }} | |
| - name: Upload artifact (Linux - AppImage) | |
| if: startsWith(matrix.platform, 'ubuntu') && contains(matrix.tauri_bundle, 'appimage') | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: MindWork AI Studio (Linux - AppImage ${{ matrix.dotnet_runtime }}) | |
| path: | | |
| runtime/target/${{ matrix.rust_target }}/release/bundle/appimage/*.AppImage | |
| runtime/target/${{ matrix.rust_target }}/release/bundle/appimage/*.AppImage.tar.gz* | |
| if-no-files-found: error | |
| retention-days: ${{ fromJSON(needs.determine_run_mode.outputs.artifact_retention_days) }} | |
| create_release: | |
| name: Prepare & create release | |
| runs-on: ubuntu-latest | |
| needs: [build_main, collect_flatpak_artifacts, read_metadata] | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| permissions: {} | |
| steps: | |
| - name: Create artifact directory | |
| run: mkdir -p $GITHUB_WORKSPACE/artifacts | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: ${{ github.workspace }}/artifacts | |
| merge-multiple: false | |
| - name: Display structure of previously artifacts | |
| run: ls -Rlhat $GITHUB_WORKSPACE/artifacts | |
| - name: Prepare release assets | |
| env: | |
| VERSION: ${{ needs.read_metadata.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| RELEASE_DIR="$GITHUB_WORKSPACE/release/assets" | |
| declare -A release_asset_sources | |
| # Ensure the release directory exists: | |
| mkdir -p "$RELEASE_DIR" | |
| # Find and process files in the artifacts directory: | |
| while IFS= read -r -d '' FILE; do | |
| if [[ "$FILE" == *"osx-x64"* && "$FILE" == *".tar.gz.sig" ]]; then | |
| TARGET_NAME="MindWork AI Studio_x64.app.tar.gz.sig" | |
| elif [[ "$FILE" == *"osx-x64"* && "$FILE" == *".tar.gz" ]]; then | |
| TARGET_NAME="MindWork AI Studio_x64.app.tar.gz" | |
| elif [[ "$FILE" == *"osx-arm64"* && "$FILE" == *".tar.gz.sig" ]]; then | |
| TARGET_NAME="MindWork AI Studio_aarch64.app.tar.gz.sig" | |
| elif [[ "$FILE" == *"osx-arm64"* && "$FILE" == *".tar.gz" ]]; then | |
| TARGET_NAME="MindWork AI Studio_aarch64.app.tar.gz" | |
| else | |
| TARGET_NAME="$(basename "$FILE")" | |
| TARGET_NAME=$(echo "$TARGET_NAME" | sed "s/_${VERSION}//") | |
| fi | |
| if [ -n "${release_asset_sources[$TARGET_NAME]+x}" ]; then | |
| echo "Duplicate release asset name '${TARGET_NAME}':" | |
| echo " ${release_asset_sources[$TARGET_NAME]}" | |
| echo " ${FILE}" | |
| exit 1 | |
| fi | |
| release_asset_sources[$TARGET_NAME]="$FILE" | |
| cp "$FILE" "${RELEASE_DIR}/${TARGET_NAME}" | |
| done < <(find "$GITHUB_WORKSPACE/artifacts" -type f -print0) | |
| expected_flatpaks=( | |
| "MindWork AI Studio_x86_64.flatpak" | |
| "MindWork AI Studio Plugin Pandoc_x86_64.flatpak" | |
| "MindWork AI Studio_aarch64.flatpak" | |
| "MindWork AI Studio Plugin Pandoc_aarch64.flatpak" | |
| ) | |
| flatpak_count=$(find "$RELEASE_DIR" -maxdepth 1 -type f -name '*.flatpak' | wc -l | tr -d ' ') | |
| if [ "$flatpak_count" -ne 4 ]; then | |
| echo "Prepared ${flatpak_count} Flatpak release assets; expected 4." | |
| exit 1 | |
| fi | |
| for expected_flatpak in "${expected_flatpaks[@]}"; do | |
| test -s "${RELEASE_DIR}/${expected_flatpak}" | |
| done | |
| # Display the structure of the release directory: | |
| ls -Rlhat $GITHUB_WORKSPACE/release/assets | |
| - name: Create .update directory | |
| run: mkdir -p $GITHUB_WORKSPACE/.updates | |
| - name: Build platform JSON for latest.json file | |
| env: | |
| FORMATTED_VERSION: ${{ needs.read_metadata.outputs.formatted_version }} | |
| run: | | |
| # Here, we create the JSON object: | |
| platforms_json=$(jq -n '{}') | |
| # Display the structure of the release directory: | |
| ls -Rlhat $GITHUB_WORKSPACE/release/assets | |
| # Iterate over all signature files: | |
| while IFS= read -r -d '' sig_file; do | |
| echo "Processing signature file '$sig_file':" | |
| # | |
| # Next, we determine the "update platform". | |
| # We store the result in the $platform variable. | |
| # | |
| # We derive the platform from the signature file name: | |
| # - platform=darwin-aarch64 when path contains 'aarch64-apple-darwin' | |
| # - platform=darwin-x86_64 when path contains 'x86_64-apple-darwin' | |
| # - platform=linux-x86_64 when path contains 'x86_64-unknown-linux-' | |
| # - platform=linux-aarch64 when path contains 'aarch64-unknown-linux-' | |
| # - platform=windows-x86_64 when path contains 'x86_64-pc-windows-' | |
| # - platform=windows-aarch64 when path contains 'aarch64-pc-windows-' | |
| # | |
| if [[ "$sig_file" == *"aarch64.app"* ]]; then | |
| platform="darwin-aarch64" | |
| elif [[ "$sig_file" == *"x64.app"* ]]; then | |
| platform="darwin-x86_64" | |
| elif [[ "$sig_file" == *"amd64.AppImage"* ]]; then | |
| platform="linux-x86_64" | |
| elif [[ "$sig_file" == *"aarch64.AppImage"* ]]; then | |
| platform="linux-aarch64" | |
| elif [[ "$sig_file" == *"x64-setup"* ]]; then | |
| platform="windows-x86_64" | |
| elif [[ "$sig_file" == *"arm64-setup"* ]]; then | |
| platform="windows-aarch64" | |
| else | |
| echo "Platform not recognized: '$sig_file'" | |
| exit 1 | |
| fi | |
| # Read the signature: | |
| signature=$(cat "$sig_file") | |
| # Extract the artifact name and create the URL: | |
| artifact_name=$(basename "$sig_file" .sig) | |
| # Replaces spaces by '.': | |
| encoded_artifact_name=$(echo "$artifact_name" | sed 's/ /./g') | |
| # Create the URL: | |
| url="https://github.com/MindWorkAI/AI-Studio/releases/download/$FORMATTED_VERSION/$encoded_artifact_name" | |
| # Build the JSON object: | |
| platforms_json=$(echo "$platforms_json" | jq --arg platform "$platform" --arg signature "$signature" --arg url "$url" '.[$platform] = {"signature": $signature, "url": $url}') | |
| done < <(find $GITHUB_WORKSPACE/release/assets -type f -name '*.sig' -print0) | |
| # Write the JSON object to a temporary file: | |
| echo "$platforms_json" > $GITHUB_WORKSPACE/.updates/platforms.json | |
| - name: Create latest.json | |
| env: | |
| FORMATTED_VERSION: ${{ needs.read_metadata.outputs.formatted_version }} | |
| FORMATTED_BUILD_TIME: ${{ needs.read_metadata.outputs.formatted_build_time }} | |
| CHANGELOG: ${{ needs.read_metadata.outputs.changelog }} | |
| run: | | |
| # Read the platforms JSON, which was created in the previous step: | |
| platforms=$(cat $GITHUB_WORKSPACE/.updates/platforms.json) | |
| # Create the latest.json file via jq so the changelog is escaped as valid JSON. | |
| jq -n \ | |
| --arg version "$FORMATTED_VERSION" \ | |
| --arg notes "$CHANGELOG" \ | |
| --arg pub_date "$FORMATTED_BUILD_TIME" \ | |
| --argjson platforms "$platforms" \ | |
| '{ | |
| version: $version, | |
| notes: $notes, | |
| pub_date: $pub_date, | |
| platforms: $platforms | |
| }' > $GITHUB_WORKSPACE/release/assets/latest.json | |
| - name: Validate latest.json | |
| env: | |
| CHANGELOG: ${{ needs.read_metadata.outputs.changelog }} | |
| run: | | |
| # Ensure the generated file is valid JSON and the changelog round-trips unchanged. | |
| jq -e . $GITHUB_WORKSPACE/release/assets/latest.json > /dev/null | |
| generated_notes=$(jq -r '.notes' $GITHUB_WORKSPACE/release/assets/latest.json) | |
| if [[ "$generated_notes" != "$CHANGELOG" ]]; then | |
| echo "The generated notes field does not match the changelog input." | |
| exit 1 | |
| fi | |
| for platform in darwin-aarch64 darwin-x86_64 linux-aarch64 linux-x86_64 windows-aarch64 windows-x86_64; do | |
| if ! jq -e --arg platform "$platform" '.platforms[$platform]' $GITHUB_WORKSPACE/release/assets/latest.json > /dev/null; then | |
| echo "The generated latest.json is missing platform '$platform'." | |
| exit 1 | |
| fi | |
| done | |
| - name: Show all release assets | |
| run: ls -Rlhat $GITHUB_WORKSPACE/release/assets | |
| - name: Display the content of latest.json | |
| run: cat $GITHUB_WORKSPACE/release/assets/latest.json | |
| - name: Upload release assets | |
| uses: actions/upload-artifact@v4 | |
| env: | |
| FORMATTED_VERSION: ${{ needs.read_metadata.outputs.formatted_version }} | |
| with: | |
| name: MindWork AI Studio ${{ env.FORMATTED_VERSION }} Release | |
| path: release/assets/ | |
| if-no-files-found: error | |
| retention-days: ${{ env.RETENTION_RELEASE_ASSETS }} | |
| publish_release: | |
| name: Publish release | |
| runs-on: ubuntu-latest | |
| needs: [read_metadata, create_release] | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| permissions: | |
| contents: write | |
| env: | |
| FORMATTED_VERSION: ${{ needs.read_metadata.outputs.formatted_version }} | |
| CHANGELOG: ${{ needs.read_metadata.outputs.changelog }} | |
| steps: | |
| - name: Create release folder | |
| run: mkdir -p release/assets | |
| - name: Download release assets | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: MindWork AI Studio ${{ env.FORMATTED_VERSION }} Release | |
| path: release/assets | |
| - name: Display the content of the release folder | |
| run: ls -Rlhat release/assets | |
| - name: Scan for threats | |
| id: virus_total | |
| uses: crazy-max/ghaction-virustotal@v4 | |
| with: | |
| vt_api_key: ${{ secrets.VIRUS_TOTAL_KEY }} | |
| files: release/assets/* | |
| request_rate: 4 | |
| vt_monitor: false | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| update_release_body: true | |
| - name: Append scan results to changelog | |
| run: | | |
| changelog=$(cat <<'EOOOOOOOF' | |
| ${{ env.CHANGELOG }} | |
| EOOOOOOOF | |
| ) | |
| links="${{ steps.virus_total.outputs.analysis }}" | |
| # Create a temporary file for the changelog: | |
| temp_changelog=$(mktemp) | |
| # Add the new Markdown section: | |
| echo -e "$changelog" > $temp_changelog | |
| echo -e "\n\n## Virus scans" >> $temp_changelog | |
| # Split the analysis output by comma: | |
| IFS=',' read -ra analysis_array <<< "$links" | |
| # Append each file and link to the changelog: | |
| for item in "${analysis_array[@]}"; do | |
| # Get the part before the first '=': | |
| filename="${item%%=*}" | |
| filename=$(echo $filename | xargs) | |
| # Extract the base name of the file | |
| base_filename=$(basename "$filename") | |
| # Ignore the latest.json file: | |
| if [[ "$base_filename" == "latest.json" ]]; then | |
| continue | |
| fi | |
| # Get the part after the first '=': | |
| link="${item#*=}" | |
| link=$(echo $link | xargs) | |
| # Append this scan result to the changelog: | |
| echo "- [$base_filename]($link)" >> $temp_changelog | |
| done | |
| # Export the modified changelog (using HEREDOC syntax for multi-line support): | |
| echo "CHANGELOG<<EOOOF" >> $GITHUB_ENV | |
| cat $temp_changelog >> $GITHUB_ENV | |
| echo "EOOOF" >> $GITHUB_ENV | |
| - name: Create release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| prerelease: true | |
| draft: false | |
| make_latest: false | |
| body: ${{ env.CHANGELOG }} | |
| name: "Release ${{ env.FORMATTED_VERSION }}" | |
| fail_on_unmatched_files: true | |
| files: | | |
| release/assets/* |