From 2b481c397cd7449cd132507e74439f32f9c0139e Mon Sep 17 00:00:00 2001 From: Pujit Mehrotra Date: Wed, 22 Oct 2025 13:49:15 -0400 Subject: [PATCH] chore: add manual-release workflow and extract build-artifacts workflow (#1755) Adds a workflow to create or override (github) releases with a release produced from a specific git ref. Refactors the main build process into a workflow call for reusability. ## Summary by CodeRabbit * **Chores** * Consolidated multi-target build pipeline for API, UI library, and web app with unified artifact publishing, improved caching, and simplified downstream wiring. * **New Features** * Added a manual, parameterized release workflow to create/update draft releases with optional prerelease tagging and generated release notes. --- .github/workflows/build-artifacts.yml | 201 ++++++++++++++++++++++++++ .github/workflows/build-plugin.yml | 5 + .github/workflows/main.yml | 191 ++---------------------- .github/workflows/manual-release.yml | 150 +++++++++++++++++++ 4 files changed, 372 insertions(+), 175 deletions(-) create mode 100644 .github/workflows/build-artifacts.yml create mode 100644 .github/workflows/manual-release.yml diff --git a/.github/workflows/build-artifacts.yml b/.github/workflows/build-artifacts.yml new file mode 100644 index 000000000..d2c4414cd --- /dev/null +++ b/.github/workflows/build-artifacts.yml @@ -0,0 +1,201 @@ +name: Build Artifacts + +on: + workflow_call: + inputs: + ref: + type: string + required: false + description: "Git ref to checkout (commit SHA, branch, or tag)" + version_override: + type: string + required: false + description: "Override version (for manual releases)" + outputs: + build_number: + description: "Build number for the artifacts" + value: ${{ jobs.build-api.outputs.build_number }} + secrets: + VITE_ACCOUNT: + required: true + VITE_CONNECT: + required: true + VITE_UNRAID_NET: + required: true + VITE_CALLBACK_KEY: + required: true + UNRAID_BOT_GITHUB_ADMIN_TOKEN: + required: false + +jobs: + build-api: + name: Build API + runs-on: ubuntu-latest + outputs: + build_number: ${{ steps.buildnumber.outputs.build_number }} + defaults: + run: + working-directory: api + steps: + - name: Checkout repo + uses: actions/checkout@v5 + with: + ref: ${{ inputs.ref || github.ref }} + fetch-depth: 0 + + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + run_install: false + + - name: Install Node + uses: actions/setup-node@v5 + with: + node-version-file: ".nvmrc" + cache: 'pnpm' + + - name: Cache APT Packages + uses: awalsh128/cache-apt-pkgs-action@v1.5.3 + with: + packages: bash procps python3 libvirt-dev jq zstd git build-essential + version: 1.0 + + - name: PNPM Install + run: | + cd ${{ github.workspace }} + pnpm install --frozen-lockfile + + - name: Get Git Short Sha and API version + id: vars + run: | + GIT_SHA=$(git rev-parse --short HEAD) + IS_TAGGED=$(git describe --tags --abbrev=0 --exact-match || echo '') + PACKAGE_LOCK_VERSION=$(jq -r '.version' package.json) + API_VERSION=${{ inputs.version_override && format('"{0}"', inputs.version_override) || '${PACKAGE_LOCK_VERSION}' }} + if [ -z "${{ inputs.version_override }}" ] && [ -z "$IS_TAGGED" ]; then + API_VERSION="${PACKAGE_LOCK_VERSION}+${GIT_SHA}" + fi + export API_VERSION + echo "API_VERSION=${API_VERSION}" >> $GITHUB_ENV + echo "PACKAGE_LOCK_VERSION=${PACKAGE_LOCK_VERSION}" >> $GITHUB_OUTPUT + + - name: Generate build number + id: buildnumber + uses: onyxmueller/build-tag-number@v1 + with: + token: ${{ secrets.UNRAID_BOT_GITHUB_ADMIN_TOKEN || github.token }} + prefix: ${{ inputs.version_override || steps.vars.outputs.PACKAGE_LOCK_VERSION }} + + - name: Build + run: | + pnpm run build:release + tar -czf deploy/unraid-api.tgz -C deploy/pack/ . + + - name: Upload tgz to Github artifacts + uses: actions/upload-artifact@v4 + with: + name: unraid-api + path: ${{ github.workspace }}/api/deploy/unraid-api.tgz + + build-unraid-ui-webcomponents: + name: Build Unraid UI Library (Webcomponent Version) + defaults: + run: + working-directory: unraid-ui + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v5 + with: + ref: ${{ inputs.ref || github.ref }} + + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + run_install: false + + - name: Install Node + uses: actions/setup-node@v5 + with: + node-version-file: ".nvmrc" + cache: 'pnpm' + + - name: Cache APT Packages + uses: awalsh128/cache-apt-pkgs-action@v1.5.3 + with: + packages: bash procps python3 libvirt-dev jq zstd git build-essential + version: 1.0 + + - name: Install dependencies + run: | + cd ${{ github.workspace }} + pnpm install --frozen-lockfile --filter @unraid/ui + + - name: Lint + run: pnpm run lint + + - name: Build + run: pnpm run build:wc + + - name: Upload Artifact to Github + uses: actions/upload-artifact@v4 + with: + name: unraid-wc-ui + path: unraid-ui/dist-wc/ + + build-web: + name: Build Web App + defaults: + run: + working-directory: web + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v5 + with: + ref: ${{ inputs.ref || github.ref }} + + - name: Create env file + run: | + touch .env + echo VITE_ACCOUNT=${{ secrets.VITE_ACCOUNT }} >> .env + echo VITE_CONNECT=${{ secrets.VITE_CONNECT }} >> .env + echo VITE_UNRAID_NET=${{ secrets.VITE_UNRAID_NET }} >> .env + echo VITE_CALLBACK_KEY=${{ secrets.VITE_CALLBACK_KEY }} >> .env + + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + run_install: false + + - name: Install Node + uses: actions/setup-node@v5 + with: + node-version-file: ".nvmrc" + cache: 'pnpm' + + - name: PNPM Install + run: | + cd ${{ github.workspace }} + pnpm install --frozen-lockfile --filter @unraid/web --filter @unraid/ui + + - name: Build Unraid UI + run: | + cd ${{ github.workspace }}/unraid-ui + pnpm run build + + - name: Lint files + run: pnpm run lint + + - name: Type Check + run: pnpm run type-check + + - name: Build + run: pnpm run build + + - name: Upload build to Github artifacts + uses: actions/upload-artifact@v4 + with: + name: unraid-wc-rich + path: web/dist + diff --git a/.github/workflows/build-plugin.yml b/.github/workflows/build-plugin.yml index 64bf4a327..265e5c061 100644 --- a/.github/workflows/build-plugin.yml +++ b/.github/workflows/build-plugin.yml @@ -27,6 +27,10 @@ on: type: string required: true description: "Build number for the plugin builds" + ref: + type: string + required: false + description: "Git ref (commit SHA, branch, or tag) to checkout" secrets: CF_ACCESS_KEY_ID: required: true @@ -49,6 +53,7 @@ jobs: - name: Checkout repo uses: actions/checkout@v5 with: + ref: ${{ inputs.ref }} fetch-depth: 0 - uses: pnpm/action-setup@v4 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 003506403..62a84f5d4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -154,170 +154,15 @@ jobs: files: ./coverage/coverage-final.json,../web/coverage/coverage-final.json,../unraid-ui/coverage/coverage-final.json,../packages/unraid-api-plugin-connect/coverage/coverage-final.json,../packages/unraid-shared/coverage/coverage-final.json fail_ci_if_error: false - build-api: - name: Build API - runs-on: ubuntu-latest - outputs: - build_number: ${{ steps.buildnumber.outputs.build_number }} - defaults: - run: - working-directory: api - steps: - - name: Checkout repo - uses: actions/checkout@v5 - - - uses: pnpm/action-setup@v4 - name: Install pnpm - with: - run_install: false - - - name: Install Node - uses: actions/setup-node@v5 - with: - node-version-file: ".nvmrc" - cache: 'pnpm' - - - name: Cache APT Packages - uses: awalsh128/cache-apt-pkgs-action@v1.5.3 - with: - packages: bash procps python3 libvirt-dev jq zstd git build-essential - version: 1.0 - - - name: PNPM Install - run: | - cd ${{ github.workspace }} - pnpm install --frozen-lockfile - - - name: Build - run: pnpm run build - - - name: Get Git Short Sha and API version - id: vars - run: | - GIT_SHA=$(git rev-parse --short HEAD) - IS_TAGGED=$(git describe --tags --abbrev=0 --exact-match || echo '') - PACKAGE_LOCK_VERSION=$(jq -r '.version' package.json) - API_VERSION=$([[ -n "$IS_TAGGED" ]] && echo "$PACKAGE_LOCK_VERSION" || echo "${PACKAGE_LOCK_VERSION}+${GIT_SHA}") - export API_VERSION - echo "API_VERSION=${API_VERSION}" >> $GITHUB_ENV - echo "PACKAGE_LOCK_VERSION=${PACKAGE_LOCK_VERSION}" >> $GITHUB_OUTPUT - - - name: Generate build number - id: buildnumber - uses: onyxmueller/build-tag-number@v1 - with: - token: ${{secrets.UNRAID_BOT_GITHUB_ADMIN_TOKEN}} - prefix: ${{steps.vars.outputs.PACKAGE_LOCK_VERSION}} - - - name: Build - run: | - pnpm run build:release - tar -czf deploy/unraid-api.tgz -C deploy/pack/ . - - - name: Upload tgz to Github artifacts - uses: actions/upload-artifact@v4 - with: - name: unraid-api - path: ${{ github.workspace }}/api/deploy/unraid-api.tgz - - build-unraid-ui-webcomponents: - name: Build Unraid UI Library (Webcomponent Version) - defaults: - run: - working-directory: unraid-ui - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v5 - - - uses: pnpm/action-setup@v4 - name: Install pnpm - with: - run_install: false - - - name: Install Node - uses: actions/setup-node@v5 - with: - node-version-file: ".nvmrc" - cache: 'pnpm' - - - name: Cache APT Packages - uses: awalsh128/cache-apt-pkgs-action@v1.5.3 - with: - packages: bash procps python3 libvirt-dev jq zstd git build-essential - version: 1.0 - - - name: Install dependencies - run: | - cd ${{ github.workspace }} - pnpm install --frozen-lockfile --filter @unraid/ui - - - name: Lint - run: pnpm run lint - - - name: Build - run: pnpm run build:wc - - - name: Upload Artifact to Github - uses: actions/upload-artifact@v4 - with: - name: unraid-wc-ui - path: unraid-ui/dist-wc/ - - build-web: - # needs: [build-unraid-ui] - name: Build Web App - defaults: - run: - working-directory: web - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v5 - - - name: Create env file - run: | - touch .env - echo VITE_ACCOUNT=${{ secrets.VITE_ACCOUNT }} >> .env - echo VITE_CONNECT=${{ secrets.VITE_CONNECT }} >> .env - echo VITE_UNRAID_NET=${{ secrets.VITE_UNRAID_NET }} >> .env - echo VITE_CALLBACK_KEY=${{ secrets.VITE_CALLBACK_KEY }} >> .env - - - uses: pnpm/action-setup@v4 - name: Install pnpm - with: - run_install: false - - - name: Install Node - uses: actions/setup-node@v5 - with: - node-version-file: ".nvmrc" - cache: 'pnpm' - - - name: PNPM Install - run: | - cd ${{ github.workspace }} - pnpm install --frozen-lockfile --filter @unraid/web --filter @unraid/ui - - - name: Build Unraid UI - run: | - cd ${{ github.workspace }}/unraid-ui - pnpm run build - - - name: Lint files - run: pnpm run lint - - - name: Type Check - run: pnpm run type-check - - - name: Build - run: pnpm run build - - - name: Upload build to Github artifacts - uses: actions/upload-artifact@v4 - with: - name: unraid-wc-rich - path: web/dist + build-artifacts: + name: Build All Artifacts + uses: ./.github/workflows/build-artifacts.yml + secrets: + VITE_ACCOUNT: ${{ secrets.VITE_ACCOUNT }} + VITE_CONNECT: ${{ secrets.VITE_CONNECT }} + VITE_UNRAID_NET: ${{ secrets.VITE_UNRAID_NET }} + VITE_CALLBACK_KEY: ${{ secrets.VITE_CALLBACK_KEY }} + UNRAID_BOT_GITHUB_ADMIN_TOKEN: ${{ secrets.UNRAID_BOT_GITHUB_ADMIN_TOKEN }} release-please: name: Release Please @@ -326,9 +171,7 @@ jobs: if: github.event_name == 'push' && github.ref == 'refs/heads/main' needs: - test-api - - build-api - - build-web - - build-unraid-ui-webcomponents + - build-artifacts permissions: contents: write pull-requests: write @@ -345,17 +188,15 @@ jobs: build-plugin-staging-pr: name: Build and Deploy Plugin needs: - - build-api - - build-web - - build-unraid-ui-webcomponents + - build-artifacts - test-api uses: ./.github/workflows/build-plugin.yml with: - RELEASE_CREATED: false + RELEASE_CREATED: 'false' TAG: ${{ github.event.pull_request.number && format('PR{0}', github.event.pull_request.number) || '' }} BUCKET_PATH: ${{ github.event.pull_request.number && format('unraid-api/tag/PR{0}', github.event.pull_request.number) || 'unraid-api' }} BASE_URL: "https://preview.dl.unraid.net/unraid-api" - BUILD_NUMBER: ${{ needs.build-api.outputs.build_number }} + BUILD_NUMBER: ${{ needs.build-artifacts.outputs.build_number }} secrets: CF_ACCESS_KEY_ID: ${{ secrets.CF_ACCESS_KEY_ID }} CF_SECRET_ACCESS_KEY: ${{ secrets.CF_SECRET_ACCESS_KEY }} @@ -367,15 +208,15 @@ jobs: name: Build and Deploy Production Plugin needs: - release-please - - build-api + - build-artifacts uses: ./.github/workflows/build-plugin.yml with: - RELEASE_CREATED: true + RELEASE_CREATED: 'true' RELEASE_TAG: ${{ needs.release-please.outputs.tag_name }} TAG: "" BUCKET_PATH: unraid-api BASE_URL: "https://stable.dl.unraid.net/unraid-api" - BUILD_NUMBER: ${{ needs.build-api.outputs.build_number }} + BUILD_NUMBER: ${{ needs.build-artifacts.outputs.build_number }} secrets: CF_ACCESS_KEY_ID: ${{ secrets.CF_ACCESS_KEY_ID }} CF_SECRET_ACCESS_KEY: ${{ secrets.CF_SECRET_ACCESS_KEY }} diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml new file mode 100644 index 000000000..e0b28d2aa --- /dev/null +++ b/.github/workflows/manual-release.yml @@ -0,0 +1,150 @@ +name: Manual Release + +on: + workflow_dispatch: + inputs: + version: + description: 'Version to release (e.g., 4.25.3)' + required: true + type: string + target_commitish: + description: 'Commit SHA or branch (leave empty for current HEAD)' + required: false + type: string + release_notes: + description: 'Release notes/changelog (leave empty to auto-generate from commits)' + required: false + type: string + prerelease: + description: 'Mark as prerelease' + required: false + type: boolean + default: false + +permissions: + contents: write + pull-requests: write + +jobs: + build-artifacts: + name: Build All Artifacts + uses: ./.github/workflows/build-artifacts.yml + with: + ref: ${{ inputs.target_commitish || github.ref }} + version_override: ${{ inputs.version }} + secrets: + VITE_ACCOUNT: ${{ secrets.VITE_ACCOUNT }} + VITE_CONNECT: ${{ secrets.VITE_CONNECT }} + VITE_UNRAID_NET: ${{ secrets.VITE_UNRAID_NET }} + VITE_CALLBACK_KEY: ${{ secrets.VITE_CALLBACK_KEY }} + UNRAID_BOT_GITHUB_ADMIN_TOKEN: ${{ secrets.UNRAID_BOT_GITHUB_ADMIN_TOKEN }} + + create-release: + name: Create GitHub Release (Draft) + runs-on: ubuntu-latest + needs: + - build-artifacts + outputs: + tag_name: ${{ steps.create_release.outputs.tag_name }} + release_notes: ${{ steps.generate_notes.outputs.release_notes }} + steps: + - name: Checkout repo + uses: actions/checkout@v5 + with: + ref: ${{ inputs.target_commitish || github.ref }} + fetch-depth: 0 + + - name: Generate Release Notes + id: generate_notes + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME="v${{ inputs.version }}" + + if [ -n "${{ inputs.release_notes }}" ]; then + NOTES="${{ inputs.release_notes }}" + else + PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") + + if [ -n "$PREV_TAG" ]; then + echo "Generating release notes from ${PREV_TAG}..HEAD" + NOTES=$(gh api repos/${{ github.repository }}/releases/generate-notes \ + -f tag_name="${TAG_NAME}" \ + -f target_commitish="${{ inputs.target_commitish || github.sha }}" \ + -f previous_tag_name="${PREV_TAG}" \ + --jq '.body') + else + echo "No previous tag found, generating basic release notes" + NOTES="Release ${{ inputs.version }}" + fi + fi + + echo "release_notes<> $GITHUB_OUTPUT + echo "$NOTES" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Create or Update Release as Draft + id: create_release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME="v${{ inputs.version }}" + TARGET="${{ inputs.target_commitish || github.sha }}" + + echo "tag_name=${TAG_NAME}" >> $GITHUB_OUTPUT + + if gh release view "${TAG_NAME}" > /dev/null 2>&1; then + echo "Release ${TAG_NAME} already exists, updating as draft..." + gh release edit "${TAG_NAME}" \ + --draft \ + --notes "${{ steps.generate_notes.outputs.release_notes }}" \ + ${{ inputs.prerelease && '--prerelease' || '' }} + else + echo "Creating new draft release ${TAG_NAME}..." + git tag "${TAG_NAME}" "${TARGET}" || true + git push origin "${TAG_NAME}" || true + + gh release create "${TAG_NAME}" \ + --draft \ + --title "${{ inputs.version }}" \ + --notes "${{ steps.generate_notes.outputs.release_notes }}" \ + --target "${TARGET}" \ + ${{ inputs.prerelease && '--prerelease' || '' }} + fi + + build-plugin-production: + name: Build and Deploy Production Plugin + needs: + - create-release + - build-artifacts + uses: ./.github/workflows/build-plugin.yml + with: + RELEASE_CREATED: 'true' + RELEASE_TAG: ${{ needs.create-release.outputs.tag_name }} + TAG: "" + BUCKET_PATH: unraid-api + BASE_URL: "https://stable.dl.unraid.net/unraid-api" + BUILD_NUMBER: ${{ needs.build-artifacts.outputs.build_number }} + ref: ${{ inputs.target_commitish || github.ref }} + secrets: + CF_ACCESS_KEY_ID: ${{ secrets.CF_ACCESS_KEY_ID }} + CF_SECRET_ACCESS_KEY: ${{ secrets.CF_SECRET_ACCESS_KEY }} + CF_BUCKET_PREVIEW: ${{ secrets.CF_BUCKET_PREVIEW }} + CF_ENDPOINT: ${{ secrets.CF_ENDPOINT }} + UNRAID_BOT_GITHUB_ADMIN_TOKEN: ${{ secrets.UNRAID_BOT_GITHUB_ADMIN_TOKEN }} + + publish-release: + name: Publish Release + runs-on: ubuntu-latest + needs: + - create-release + - build-plugin-production + steps: + - name: Publish Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME="${{ needs.create-release.outputs.tag_name }}" + echo "Publishing release ${TAG_NAME}..." + gh release edit "${TAG_NAME}" --draft=false --repo ${{ github.repository }} +