From 2db080690fcea2ada04172332f265d9d1972faed Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Sun, 15 Feb 2026 09:39:43 -0800 Subject: [PATCH 01/31] Add shared actions and workflows --- actions/lint-test-build/action.yml | 56 +++++++++ actions/node-ci/action.yml | 75 ++++++++++++ actions/restore-assets/action.yml | 62 ++++++++++ actions/setup-rust/action.yml | 56 +++++++++ actions/sync-assets/action.yml | 84 +++++++++++++ workflows/project-board-automation.yml | 156 +++++++++++++++++++++++++ 6 files changed, 489 insertions(+) create mode 100644 actions/lint-test-build/action.yml create mode 100644 actions/node-ci/action.yml create mode 100644 actions/restore-assets/action.yml create mode 100644 actions/setup-rust/action.yml create mode 100644 actions/sync-assets/action.yml create mode 100644 workflows/project-board-automation.yml diff --git a/actions/lint-test-build/action.yml b/actions/lint-test-build/action.yml new file mode 100644 index 0000000..b5eecc6 --- /dev/null +++ b/actions/lint-test-build/action.yml @@ -0,0 +1,56 @@ +name: 'Lint, Test, and Build' +description: 'Run lint, test, and build steps for Node.js projects' + +inputs: + working-directory: + description: 'Working directory for commands' + required: true + flavor: + description: 'Build flavor (dev or prod) - lint and test only run for dev' + required: false + default: 'dev' + run-lint: + description: 'Whether to run lint step' + required: false + default: 'true' + lint-command: + description: 'Command to run for linting' + required: false + default: 'npm run lint' + run-test: + description: 'Whether to run test step' + required: false + default: 'true' + test-command: + description: 'Command to run for testing' + required: false + default: 'npm run test' + run-build: + description: 'Whether to run build step' + required: false + default: 'true' + build-command: + description: 'Command to run for building' + required: false + default: 'npm run build' + +runs: + using: 'composite' + steps: + - name: Lint + if: inputs.run-lint == 'true' && inputs.flavor == 'dev' + shell: bash + run: ${{ inputs.lint-command }} + working-directory: ${{ inputs.working-directory }} + + - name: Test + if: inputs.run-test == 'true' && inputs.flavor == 'dev' + shell: bash + run: ${{ inputs.test-command }} + working-directory: ${{ inputs.working-directory }} + + - name: Build + if: inputs.run-build == 'true' + shell: bash + run: ${{ inputs.build-command }} + working-directory: ${{ inputs.working-directory }} diff --git a/actions/node-ci/action.yml b/actions/node-ci/action.yml new file mode 100644 index 0000000..68892b4 --- /dev/null +++ b/actions/node-ci/action.yml @@ -0,0 +1,75 @@ +name: 'Node.js CI Setup' +description: 'Checkout repository, setup Node.js, cache node_modules, and install dependencies' + +inputs: + node-version: + description: 'Node.js version to use' + required: false + default: '24.13.0' + checkout-path: + description: 'Path to checkout the repository' + required: false + default: '.' + working-directory: + description: 'Working directory for npm install (relative to checkout-path)' + required: true + flavor: + description: 'Build flavor (dev or prod)' + required: false + default: 'dev' + cache-prefix: + description: 'Prefix for cache key' + required: false + default: 'node' + checkout-submodules: + description: 'Whether to checkout submodules' + required: false + default: 'false' + checkout-ref: + description: 'Git ref to checkout (branch, tag, or SHA)' + required: false + default: '' + +runs: + using: 'composite' + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + path: ${{ inputs.checkout-path }} + ref: ${{ inputs.checkout-ref || github.ref }} + + - name: Checkout submodules + if: inputs.checkout-submodules == 'true' + shell: bash + run: git submodule update --init --recursive + working-directory: ${{ inputs.checkout-path }}/${{ inputs.working-directory }} + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: ${{ inputs.node-version }} + registry-url: 'https://registry.npmjs.org' + cache: 'npm' + cache-dependency-path: '${{ inputs.checkout-path }}/${{ inputs.working-directory }}/package-lock.json' + + - name: Cache node_modules + uses: actions/cache@v4 + with: + path: ${{ inputs.checkout-path }}/${{ inputs.working-directory }}/node_modules + key: ${{ runner.os }}-${{ inputs.cache-prefix }}-${{ inputs.flavor }}-node-${{ inputs.node-version }}-${{ hashFiles(format('{0}/{1}/package-lock.json', inputs.checkout-path, inputs.working-directory)) }} + restore-keys: | + ${{ runner.os }}-${{ inputs.cache-prefix }}-${{ inputs.flavor }}-node-${{ inputs.node-version }}- + ${{ runner.os }}-${{ inputs.cache-prefix }}-${{ inputs.flavor }}- + + - name: Install dependencies (dev) + if: inputs.flavor == 'dev' + shell: bash + run: npm ci + working-directory: ${{ inputs.checkout-path }}/${{ inputs.working-directory }} + + - name: Install dependencies (prod) + if: inputs.flavor == 'prod' + shell: bash + run: npm ci --omit=dev --omit=optional + working-directory: ${{ inputs.checkout-path }}/${{ inputs.working-directory }} diff --git a/actions/restore-assets/action.yml b/actions/restore-assets/action.yml new file mode 100644 index 0000000..969d75c --- /dev/null +++ b/actions/restore-assets/action.yml @@ -0,0 +1,62 @@ +name: 'Restore Cached Assets' +description: 'Restore cached mining pool and promo video assets from cache and/or artifacts' + +inputs: + frontend-path: + description: 'Path to frontend directory' + required: true + use-artifacts: + description: 'Whether to download from artifacts (requires cache job to have run first)' + required: false + default: 'true' + +outputs: + mining-pool-cache-hit: + description: 'Whether mining pool assets were found in cache' + value: ${{ steps.cache-mining-pool-restore.outputs.cache-hit }} + promo-video-cache-hit: + description: 'Whether promo video assets were found in cache' + value: ${{ steps.cache-promo-video-restore.outputs.cache-hit }} + +runs: + using: 'composite' + steps: + - name: Restore cached mining pool assets + continue-on-error: true + id: cache-mining-pool-restore + uses: actions/cache/restore@v4 + with: + path: mining-pool-assets.zip + key: mining-pool-assets-cache + + - name: Restore cached promo video assets + continue-on-error: true + id: cache-promo-video-restore + uses: actions/cache/restore@v4 + with: + path: promo-video-assets.zip + key: promo-video-assets-cache + + - name: Download mining pool artifact + if: inputs.use-artifacts == 'true' + uses: actions/download-artifact@v4 + with: + name: mining-pool-assets + continue-on-error: true + + - name: Download promo video artifact + if: inputs.use-artifacts == 'true' + uses: actions/download-artifact@v4 + with: + name: promo-video-assets + continue-on-error: true + + - name: Unzip mining pool assets + shell: bash + continue-on-error: true + run: unzip -o mining-pool-assets.zip -d ${{ inputs.frontend-path }}/src/resources/mining-pools + + - name: Unzip promo video assets + shell: bash + continue-on-error: true + run: unzip -o promo-video-assets.zip -d ${{ inputs.frontend-path }}/src/resources/promo-video diff --git a/actions/setup-rust/action.yml b/actions/setup-rust/action.yml new file mode 100644 index 0000000..9a48c10 --- /dev/null +++ b/actions/setup-rust/action.yml @@ -0,0 +1,56 @@ +name: 'Setup Rust Toolchain' +description: 'Read rust-toolchain file, cache Rust dependencies, and install the toolchain' + +inputs: + working-directory: + description: 'Working directory (repository root)' + required: true + rust-toolchain-path: + description: 'Path to rust-toolchain file relative to working-directory' + required: false + default: 'rust/gbt/rust-toolchain' + cargo-lock-path: + description: 'Path pattern for Cargo.lock files relative to working-directory' + required: false + default: 'rust/gbt/**/Cargo.lock' + rust-target-path: + description: 'Path to Rust target directory relative to working-directory' + required: false + default: 'rust/gbt/target/' + flavor: + description: 'Build flavor for cache key differentiation' + required: false + default: 'dev' + +outputs: + toolchain: + description: 'The Rust toolchain version that was installed' + value: ${{ steps.gettoolchain.outputs.toolchain }} + +runs: + using: 'composite' + steps: + - name: Read rust-toolchain file + id: gettoolchain + shell: bash + run: echo "toolchain=$(cat ./${{ inputs.rust-toolchain-path }})" >> $GITHUB_OUTPUT + working-directory: ${{ inputs.working-directory }} + + - name: Cache Rust dependencies + uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + ${{ inputs.working-directory }}/${{ inputs.rust-target-path }} + key: ${{ runner.os }}-cargo-${{ inputs.flavor }}-${{ hashFiles(format('{0}/{1}', inputs.working-directory, inputs.cargo-lock-path)) }} + restore-keys: | + ${{ runner.os }}-cargo-${{ inputs.flavor }}- + ${{ runner.os }}-cargo- + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@f7ccc83f9ed1e5b9c81d8a67d7ad1a747e22a561 + with: + toolchain: ${{ steps.gettoolchain.outputs.toolchain }} diff --git a/actions/sync-assets/action.yml b/actions/sync-assets/action.yml new file mode 100644 index 0000000..956db1f --- /dev/null +++ b/actions/sync-assets/action.yml @@ -0,0 +1,84 @@ +name: 'Sync and Cache Assets' +description: 'Sync assets from CDN, zip, upload as artifacts, and save to cache' + +inputs: + frontend-path: + description: 'Path to frontend directory' + required: true + github-token: + description: 'GitHub token for API access' + required: true + sync-command: + description: 'npm command to run for syncing assets' + required: false + default: 'npm run sync-assets-dev' + +runs: + using: 'composite' + steps: + - name: Restore cached mining pool assets + continue-on-error: true + id: cache-mining-pool-restore + uses: actions/cache/restore@v4 + with: + path: mining-pool-assets.zip + key: mining-pool-assets-cache + + - name: Restore cached promo video assets + continue-on-error: true + id: cache-promo-video-restore + uses: actions/cache/restore@v4 + with: + path: promo-video-assets.zip + key: promo-video-assets-cache + + - name: Unzip mining pool assets before sync + continue-on-error: true + shell: bash + run: unzip -o mining-pool-assets.zip -d ${{ inputs.frontend-path }}/src/resources/mining-pools + + - name: Unzip promo video assets before sync + continue-on-error: true + shell: bash + run: unzip -o promo-video-assets.zip -d ${{ inputs.frontend-path }}/src/resources/promo-video + + - name: Sync assets + shell: bash + run: ${{ inputs.sync-command }} + env: + GITHUB_TOKEN: ${{ inputs.github-token }} + MEMPOOL_CDN: 1 + VERBOSE: 1 + working-directory: ${{ inputs.frontend-path }} + + - name: Zip mining pool assets + shell: bash + run: zip -jrq mining-pool-assets.zip ${{ inputs.frontend-path }}/src/resources/mining-pools/* + + - name: Zip promo video assets + shell: bash + run: zip -jrq promo-video-assets.zip ${{ inputs.frontend-path }}/src/resources/promo-video/* + + - name: Upload mining pool assets artifact + uses: actions/upload-artifact@v4 + with: + name: mining-pool-assets + path: mining-pool-assets.zip + + - name: Upload promo video assets artifact + uses: actions/upload-artifact@v4 + with: + name: promo-video-assets + path: promo-video-assets.zip + + - name: Save mining pool assets cache + uses: actions/cache/save@v4 + with: + path: mining-pool-assets.zip + key: mining-pool-assets-cache + + - name: Save promo video assets cache + uses: actions/cache/save@v4 + with: + path: promo-video-assets.zip + key: promo-video-assets-cache diff --git a/workflows/project-board-automation.yml b/workflows/project-board-automation.yml new file mode 100644 index 0000000..5c57075 --- /dev/null +++ b/workflows/project-board-automation.yml @@ -0,0 +1,156 @@ +# Reusable workflow: Automate project board management +# - Add newly created issues to project board +# - Set status to "Review Needed" when a reviewer is requested on a non-draft PR +name: Project Board Automation + +on: + workflow_call: + inputs: + project-number: + description: 'The project board number' + required: false + type: number + default: 8 + runs-on: + description: 'Runner to use for the job' + required: false + type: string + default: 'ubuntu-latest' + handle-issues: + description: 'Whether to handle adding issues to project board' + required: false + type: boolean + default: true + secrets: + PROJECT_TOKEN: + description: 'PAT with project write access' + required: true + PROJECT_ID: + description: 'The project unique identifier' + required: true + STATUS_FIELD_ID: + description: 'The Status field unique identifier' + required: true + REVIEW_NEEDED_OPTION_ID: + description: 'The Review Needed option unique identifier' + required: true + +jobs: + manage-project-board: + runs-on: ${{ inputs.runs-on }} + steps: + - name: Update Project Board + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.PROJECT_TOKEN }} + script: | + const projectNumber = ${{ inputs.project-number }}; + const handleIssues = ${{ inputs.handle-issues }}; + + // Skip draft PRs + if (context.eventName === 'pull_request' && context.payload.pull_request.draft) { + console.log('PR is a draft, skipping Review Needed status...'); + return; + } + + // Handle new issues - add to project + if (context.eventName === 'issues' && handleIssues) { + const addMutation = ` + mutation($projectId: ID!, $contentId: ID!) { + addProjectV2ItemById(input: {projectId: $projectId, contentId: $contentId}) { + item { id } + } + } + `; + + try { + await github.graphql(addMutation, { + projectId: "${{ secrets.PROJECT_ID }}", + contentId: context.payload.issue.node_id + }); + + console.log(`Successfully added issue to project #${projectNumber}`); + } catch (error) { + // Handle case where the issue is already in the project + const errors = error && error.errors ? error.errors : []; + const alreadyInProject = errors.some(e => + typeof e.message === 'string' && + e.message.toLowerCase().includes('already') && + e.message.toLowerCase().includes('project') + ); + + if (alreadyInProject) { + console.log(`Issue is already in project #${projectNumber}, skipping add.`); + } else { + console.error(`Failed to add issue to project #${projectNumber}:`, error); + throw error; + } + } + return; + } + + // Handle PR review_requested - update status to "Review Needed" + if (context.eventName === 'pull_request') { + // GraphQL query to find the PR's project items + const query = ` + query($owner: String!, $repo: String!, $pr: Int!) { + repository(owner: $owner, name: $repo) { + pullRequest(number: $pr) { + projectItems(first: 10) { + nodes { + id + project { + number + } + } + } + } + } + } + `; + + // Execute the query with current repo/PR context + const result = await github.graphql(query, { + owner: context.repo.owner, + repo: context.repo.repo, + pr: context.payload.pull_request.number + }); + + // Find the project item that belongs to the target project + const projectItems = result.repository.pullRequest.projectItems.nodes; + const projectItem = projectItems.find(item => item.project.number === projectNumber); + + // Exit early if PR isn't in the target project + if (!projectItem) { + console.log(`PR is not in project #${projectNumber}, skipping...`); + return; + } + + // GraphQL mutation to update the Status field + const mutation = ` + mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) { + updateProjectV2ItemFieldValue( + input: { + projectId: $projectId + itemId: $itemId + fieldId: $fieldId + value: { singleSelectOptionId: $optionId } + } + ) { + projectV2Item { + id + } + } + } + `; + + // Execute the mutation + await github.graphql(mutation, { + projectId: "${{ secrets.PROJECT_ID }}", + itemId: projectItem.id, + fieldId: "${{ secrets.STATUS_FIELD_ID }}", + optionId: "${{ secrets.REVIEW_NEEDED_OPTION_ID }}" + }); + + console.log('Successfully updated project status to Review Needed'); + } From 43f4dcac6841265be614b5dc32051e2f44b0892a Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com> Date: Sun, 15 Feb 2026 09:47:32 -0800 Subject: [PATCH 02/31] Update actions/node-ci/action.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- actions/node-ci/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/node-ci/action.yml b/actions/node-ci/action.yml index 68892b4..1b2df40 100644 --- a/actions/node-ci/action.yml +++ b/actions/node-ci/action.yml @@ -46,7 +46,7 @@ runs: working-directory: ${{ inputs.checkout-path }}/${{ inputs.working-directory }} - name: Setup Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ inputs.node-version }} registry-url: 'https://registry.npmjs.org' From af4ba72538a413a9105b1ea6ea436dd977853d60 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com> Date: Sun, 15 Feb 2026 09:47:54 -0800 Subject: [PATCH 03/31] Update actions/setup-rust/action.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- actions/setup-rust/action.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/actions/setup-rust/action.yml b/actions/setup-rust/action.yml index 9a48c10..091f8b0 100644 --- a/actions/setup-rust/action.yml +++ b/actions/setup-rust/action.yml @@ -51,6 +51,8 @@ runs: ${{ runner.os }}-cargo- - name: Install Rust toolchain + # dtolnay/rust-toolchain pinned to a specific commit SHA for supply chain security. + # To identify or update the corresponding version/tag, check this SHA in the dtolnay/rust-toolchain repository. uses: dtolnay/rust-toolchain@f7ccc83f9ed1e5b9c81d8a67d7ad1a747e22a561 with: toolchain: ${{ steps.gettoolchain.outputs.toolchain }} From f6eaeb03a963c4822843d54b350cc3c1a62057ed Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com> Date: Sun, 15 Feb 2026 09:48:18 -0800 Subject: [PATCH 04/31] Update workflows/project-board-automation.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- workflows/project-board-automation.yml | 33 +++++++++++++++++++------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/workflows/project-board-automation.yml b/workflows/project-board-automation.yml index 5c57075..bc344c1 100644 --- a/workflows/project-board-automation.yml +++ b/workflows/project-board-automation.yml @@ -93,31 +93,46 @@ jobs: if (context.eventName === 'pull_request') { // GraphQL query to find the PR's project items const query = ` - query($owner: String!, $repo: String!, $pr: Int!) { + query($owner: String!, $repo: String!, $pr: Int!, $cursor: String) { repository(owner: $owner, name: $repo) { pullRequest(number: $pr) { - projectItems(first: 10) { + projectItems(first: 50, after: $cursor) { nodes { id project { number } } + pageInfo { + hasNextPage + endCursor + } } } } } `; - // Execute the query with current repo/PR context - const result = await github.graphql(query, { - owner: context.repo.owner, - repo: context.repo.repo, - pr: context.payload.pull_request.number - }); + // Paginate through all project items for the PR + let projectItems = []; + let cursor = null; + let hasNextPage = true; + + while (hasNextPage) { + const result = await github.graphql(query, { + owner: context.repo.owner, + repo: context.repo.repo, + pr: context.payload.pull_request.number, + cursor + }); + + const projectItemsConnection = result.repository.pullRequest.projectItems; + projectItems = projectItems.concat(projectItemsConnection.nodes || []); + hasNextPage = projectItemsConnection.pageInfo.hasNextPage; + cursor = projectItemsConnection.pageInfo.endCursor; + } // Find the project item that belongs to the target project - const projectItems = result.repository.pullRequest.projectItems.nodes; const projectItem = projectItems.find(item => item.project.number === projectNumber); // Exit early if PR isn't in the target project From f1ecdff196f53b17fbfa0a5f5de38b02442c774b Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com> Date: Sun, 15 Feb 2026 09:48:52 -0800 Subject: [PATCH 05/31] Update actions/restore-assets/action.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- actions/restore-assets/action.yml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/actions/restore-assets/action.yml b/actions/restore-assets/action.yml index 969d75c..84b0946 100644 --- a/actions/restore-assets/action.yml +++ b/actions/restore-assets/action.yml @@ -54,9 +54,21 @@ runs: - name: Unzip mining pool assets shell: bash continue-on-error: true - run: unzip -o mining-pool-assets.zip -d ${{ inputs.frontend-path }}/src/resources/mining-pools + run: | + if [ -f "mining-pool-assets.zip" ]; then + echo "Found mining-pool-assets.zip, unzipping..." + unzip -o mining-pool-assets.zip -d "${{ inputs.frontend-path }}/src/resources/mining-pools" + else + echo "mining-pool-assets.zip not found; skipping unzip." + fi - name: Unzip promo video assets shell: bash continue-on-error: true - run: unzip -o promo-video-assets.zip -d ${{ inputs.frontend-path }}/src/resources/promo-video + run: | + if [ -f "promo-video-assets.zip" ]; then + echo "Found promo-video-assets.zip, unzipping..." + unzip -o promo-video-assets.zip -d "${{ inputs.frontend-path }}/src/resources/promo-video" + else + echo "promo-video-assets.zip not found; skipping unzip." + fi From a95914faa23c3f32b2fcab0719ddfbe467b7909b Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com> Date: Sun, 15 Feb 2026 09:49:07 -0800 Subject: [PATCH 06/31] Update actions/sync-assets/action.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- actions/sync-assets/action.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/actions/sync-assets/action.yml b/actions/sync-assets/action.yml index 956db1f..c3098fd 100644 --- a/actions/sync-assets/action.yml +++ b/actions/sync-assets/action.yml @@ -35,12 +35,22 @@ runs: - name: Unzip mining pool assets before sync continue-on-error: true shell: bash - run: unzip -o mining-pool-assets.zip -d ${{ inputs.frontend-path }}/src/resources/mining-pools + run: | + if [[ -f "mining-pool-assets.zip" ]]; then + unzip -o mining-pool-assets.zip -d "${{ inputs.frontend-path }}/src/resources/mining-pools" + else + echo "No cached mining pool assets zip found; skipping unzip." + fi - name: Unzip promo video assets before sync continue-on-error: true shell: bash - run: unzip -o promo-video-assets.zip -d ${{ inputs.frontend-path }}/src/resources/promo-video + run: | + if [[ -f "promo-video-assets.zip" ]]; then + unzip -o promo-video-assets.zip -d "${{ inputs.frontend-path }}/src/resources/promo-video" + else + echo "No cached promo video assets zip found; skipping unzip." + fi - name: Sync assets shell: bash From 9c1dbe55079e89bd40cb1084a014cc09795cd5ad Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com> Date: Sun, 15 Feb 2026 09:51:13 -0800 Subject: [PATCH 07/31] Update actions/node-ci/action.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- actions/node-ci/action.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/actions/node-ci/action.yml b/actions/node-ci/action.yml index 1b2df40..8b1c0db 100644 --- a/actions/node-ci/action.yml +++ b/actions/node-ci/action.yml @@ -37,7 +37,8 @@ runs: uses: actions/checkout@v4 with: path: ${{ inputs.checkout-path }} - ref: ${{ inputs.checkout-ref || github.ref }} + # Use github.ref when checkout-ref input is empty + ref: ${{ inputs.checkout-ref != '' && inputs.checkout-ref || github.ref }} - name: Checkout submodules if: inputs.checkout-submodules == 'true' From 172f635242e95362c9b07f0d2d2cf416d7857ea9 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Sun, 15 Feb 2026 09:57:23 -0800 Subject: [PATCH 08/31] Add cache hit conditional --- actions/sync-assets/action.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/actions/sync-assets/action.yml b/actions/sync-assets/action.yml index c3098fd..e27c278 100644 --- a/actions/sync-assets/action.yml +++ b/actions/sync-assets/action.yml @@ -82,12 +82,14 @@ runs: path: promo-video-assets.zip - name: Save mining pool assets cache + if: steps.cache-mining-pool-restore.outputs.cache-hit != 'true' uses: actions/cache/save@v4 with: path: mining-pool-assets.zip key: mining-pool-assets-cache - name: Save promo video assets cache + if: steps.cache-promo-video-restore.outputs.cache-hit != 'true' uses: actions/cache/save@v4 with: path: promo-video-assets.zip From c3a4729350ed5933ceadd890b55a883213be547b Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Sun, 15 Feb 2026 10:04:06 -0800 Subject: [PATCH 09/31] Update project workflow error handling --- workflows/project-board-automation.yml | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/workflows/project-board-automation.yml b/workflows/project-board-automation.yml index bc344c1..e9a49f1 100644 --- a/workflows/project-board-automation.yml +++ b/workflows/project-board-automation.yml @@ -71,15 +71,20 @@ jobs: console.log(`Successfully added issue to project #${projectNumber}`); } catch (error) { - // Handle case where the issue is already in the project - const errors = error && error.errors ? error.errors : []; - const alreadyInProject = errors.some(e => - typeof e.message === 'string' && - e.message.toLowerCase().includes('already') && - e.message.toLowerCase().includes('project') - ); + const errors = error?.errors ?? []; + const isAlreadyInProject = errors.some(e => { + if (typeof e.message !== 'string') return false; + // Only handle errors from our mutation + const path = e.path ?? []; + const fromOurMutation = Array.isArray(path) && path.includes('addProjectV2ItemById'); + if (!fromOurMutation) return false; + // Match the known GitHub error phrasing for duplicate items + const msg = e.message.toLowerCase(); + return /already\s+(exists\s+)?in\s+(this\s+)?project\b/i.test(msg) || + (e.extensions?.code === 'UNPROCESSABLE' && msg.includes('already')); + }); - if (alreadyInProject) { + if (isAlreadyInProject) { console.log(`Issue is already in project #${projectNumber}, skipping add.`); } else { console.error(`Failed to add issue to project #${projectNumber}:`, error); From 29f16e939b97e8f89d5affeaee3ff384762d7cb8 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com> Date: Sun, 15 Feb 2026 10:50:23 -0800 Subject: [PATCH 10/31] Update workflows/project-board-automation.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- workflows/project-board-automation.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/workflows/project-board-automation.yml b/workflows/project-board-automation.yml index e9a49f1..1b9aa5e 100644 --- a/workflows/project-board-automation.yml +++ b/workflows/project-board-automation.yml @@ -48,7 +48,12 @@ jobs: const handleIssues = ${{ inputs.handle-issues }}; // Skip draft PRs - if (context.eventName === 'pull_request' && context.payload.pull_request.draft) { + if ( + context.eventName === 'pull_request' && + context.payload && + context.payload.pull_request && + context.payload.pull_request.draft + ) { console.log('PR is a draft, skipping Review Needed status...'); return; } From b082864ca61c0b9aef18c35d10b07f73513fd5f2 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com> Date: Sun, 15 Feb 2026 10:51:25 -0800 Subject: [PATCH 11/31] Update actions/node-ci/action.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- actions/node-ci/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/node-ci/action.yml b/actions/node-ci/action.yml index 8b1c0db..7cbd790 100644 --- a/actions/node-ci/action.yml +++ b/actions/node-ci/action.yml @@ -44,7 +44,7 @@ runs: if: inputs.checkout-submodules == 'true' shell: bash run: git submodule update --init --recursive - working-directory: ${{ inputs.checkout-path }}/${{ inputs.working-directory }} + working-directory: ${{ inputs.checkout-path }} - name: Setup Node.js uses: actions/setup-node@v4 From 8928db26b6f359a05b6cb0602309ce3f18dbec15 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com> Date: Sun, 15 Feb 2026 10:51:53 -0800 Subject: [PATCH 12/31] Update actions/node-ci/action.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- actions/node-ci/action.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/actions/node-ci/action.yml b/actions/node-ci/action.yml index 7cbd790..49c8af8 100644 --- a/actions/node-ci/action.yml +++ b/actions/node-ci/action.yml @@ -39,13 +39,7 @@ runs: path: ${{ inputs.checkout-path }} # Use github.ref when checkout-ref input is empty ref: ${{ inputs.checkout-ref != '' && inputs.checkout-ref || github.ref }} - - - name: Checkout submodules - if: inputs.checkout-submodules == 'true' - shell: bash - run: git submodule update --init --recursive - working-directory: ${{ inputs.checkout-path }} - + submodules: ${{ inputs.checkout-submodules }} - name: Setup Node.js uses: actions/setup-node@v4 with: From 70d2c9a97bd3eec27e033ad5d95188a26ae4be5c Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Sun, 15 Feb 2026 14:49:58 -0800 Subject: [PATCH 13/31] Prevent infinite pagination --- workflows/project-board-automation.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/workflows/project-board-automation.yml b/workflows/project-board-automation.yml index 1b9aa5e..725e4e4 100644 --- a/workflows/project-board-automation.yml +++ b/workflows/project-board-automation.yml @@ -124,11 +124,14 @@ jobs: `; // Paginate through all project items for the PR + const MAX_PAGES = 100; let projectItems = []; let cursor = null; let hasNextPage = true; + let page = 0; - while (hasNextPage) { + while (hasNextPage && page < MAX_PAGES) { + page += 1; const result = await github.graphql(query, { owner: context.repo.owner, repo: context.repo.repo, @@ -142,6 +145,10 @@ jobs: cursor = projectItemsConnection.pageInfo.endCursor; } + if (hasNextPage) { + console.warn(`Stopped after ${MAX_PAGES} pages; more project items may exist.`); + } + // Find the project item that belongs to the target project const projectItem = projectItems.find(item => item.project.number === projectNumber); From 7a4bbd8b6d66d08d4c627210db5d0946613eb1e7 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Sun, 15 Feb 2026 14:50:31 -0800 Subject: [PATCH 14/31] Improve caching --- actions/restore-assets/action.yml | 12 ++++++++++-- actions/sync-assets/action.yml | 16 ++++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/actions/restore-assets/action.yml b/actions/restore-assets/action.yml index 84b0946..cc138ed 100644 --- a/actions/restore-assets/action.yml +++ b/actions/restore-assets/action.yml @@ -9,6 +9,10 @@ inputs: description: 'Whether to download from artifacts (requires cache job to have run first)' required: false default: 'true' + cache-version: + description: 'Cache version (must match sync-assets for cache hits)' + required: false + default: 'v1' outputs: mining-pool-cache-hit: @@ -27,7 +31,9 @@ runs: uses: actions/cache/restore@v4 with: path: mining-pool-assets.zip - key: mining-pool-assets-cache + key: mining-pool-assets-${{ inputs.cache-version }}-${{ hashFiles(format('{0}/package.json', inputs.frontend-path), format('{0}/package-lock.json', inputs.frontend-path)) }} + restore-keys: | + mining-pool-assets-${{ inputs.cache-version }}- - name: Restore cached promo video assets continue-on-error: true @@ -35,7 +41,9 @@ runs: uses: actions/cache/restore@v4 with: path: promo-video-assets.zip - key: promo-video-assets-cache + key: promo-video-assets-${{ inputs.cache-version }}-${{ hashFiles(format('{0}/package.json', inputs.frontend-path), format('{0}/package-lock.json', inputs.frontend-path)) }} + restore-keys: | + promo-video-assets-${{ inputs.cache-version }}- - name: Download mining pool artifact if: inputs.use-artifacts == 'true' diff --git a/actions/sync-assets/action.yml b/actions/sync-assets/action.yml index e27c278..ee4dbbb 100644 --- a/actions/sync-assets/action.yml +++ b/actions/sync-assets/action.yml @@ -12,6 +12,10 @@ inputs: description: 'npm command to run for syncing assets' required: false default: 'npm run sync-assets-dev' + cache-version: + description: 'Cache version for manual invalidation (bump to force fresh sync)' + required: false + default: 'v1' runs: using: 'composite' @@ -22,7 +26,9 @@ runs: uses: actions/cache/restore@v4 with: path: mining-pool-assets.zip - key: mining-pool-assets-cache + key: mining-pool-assets-${{ inputs.cache-version }}-${{ hashFiles(format('{0}/package.json', inputs.frontend-path), format('{0}/package-lock.json', inputs.frontend-path)) }} + restore-keys: | + mining-pool-assets-${{ inputs.cache-version }}- - name: Restore cached promo video assets continue-on-error: true @@ -30,7 +36,9 @@ runs: uses: actions/cache/restore@v4 with: path: promo-video-assets.zip - key: promo-video-assets-cache + key: promo-video-assets-${{ inputs.cache-version }}-${{ hashFiles(format('{0}/package.json', inputs.frontend-path), format('{0}/package-lock.json', inputs.frontend-path)) }} + restore-keys: | + promo-video-assets-${{ inputs.cache-version }}- - name: Unzip mining pool assets before sync continue-on-error: true @@ -86,11 +94,11 @@ runs: uses: actions/cache/save@v4 with: path: mining-pool-assets.zip - key: mining-pool-assets-cache + key: mining-pool-assets-${{ inputs.cache-version }}-${{ hashFiles(format('{0}/package.json', inputs.frontend-path), format('{0}/package-lock.json', inputs.frontend-path)) }} - name: Save promo video assets cache if: steps.cache-promo-video-restore.outputs.cache-hit != 'true' uses: actions/cache/save@v4 with: path: promo-video-assets.zip - key: promo-video-assets-cache + key: promo-video-assets-${{ inputs.cache-version }}-${{ hashFiles(format('{0}/package.json', inputs.frontend-path), format('{0}/package-lock.json', inputs.frontend-path)) }} From e45b0a5652b6846b15bbe0fe09ccd72bbcd3b68b Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com> Date: Sun, 15 Feb 2026 15:38:23 -0800 Subject: [PATCH 15/31] Update workflows/project-board-automation.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- workflows/project-board-automation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workflows/project-board-automation.yml b/workflows/project-board-automation.yml index 725e4e4..6938e0c 100644 --- a/workflows/project-board-automation.yml +++ b/workflows/project-board-automation.yml @@ -85,7 +85,7 @@ jobs: if (!fromOurMutation) return false; // Match the known GitHub error phrasing for duplicate items const msg = e.message.toLowerCase(); - return /already\s+(exists\s+)?in\s+(this\s+)?project\b/i.test(msg) || + return /already\s+(exists\s+)?in\s+(this\s+)?project\b/.test(msg) || (e.extensions?.code === 'UNPROCESSABLE' && msg.includes('already')); }); From 27065c6168217203ec061f1e4680793764a9bd79 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com> Date: Sun, 15 Feb 2026 15:38:47 -0800 Subject: [PATCH 16/31] Update workflows/project-board-automation.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- workflows/project-board-automation.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/workflows/project-board-automation.yml b/workflows/project-board-automation.yml index 6938e0c..3f1dfb5 100644 --- a/workflows/project-board-automation.yml +++ b/workflows/project-board-automation.yml @@ -83,10 +83,12 @@ jobs: const path = e.path ?? []; const fromOurMutation = Array.isArray(path) && path.includes('addProjectV2ItemById'); if (!fromOurMutation) return false; + const isUnprocessable = e.extensions?.code === 'UNPROCESSABLE'; + if (!isUnprocessable) return false; // Match the known GitHub error phrasing for duplicate items const msg = e.message.toLowerCase(); - return /already\s+(exists\s+)?in\s+(this\s+)?project\b/.test(msg) || - (e.extensions?.code === 'UNPROCESSABLE' && msg.includes('already')); + return /already\s+(exists\s+)?in\s+(this\s+)?project\b/i.test(msg) || + msg.includes('already'); }); if (isAlreadyInProject) { From d98f445a6fc92abdca2ed20ddd12ccc72b37622d Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com> Date: Sun, 15 Feb 2026 15:39:10 -0800 Subject: [PATCH 17/31] Update actions/node-ci/action.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- actions/node-ci/action.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/actions/node-ci/action.yml b/actions/node-ci/action.yml index 49c8af8..cd2d564 100644 --- a/actions/node-ci/action.yml +++ b/actions/node-ci/action.yml @@ -33,12 +33,19 @@ inputs: runs: using: 'composite' steps: - - name: Checkout + - name: Checkout (custom ref) + if: inputs.checkout-ref != '' uses: actions/checkout@v4 with: path: ${{ inputs.checkout-path }} - # Use github.ref when checkout-ref input is empty - ref: ${{ inputs.checkout-ref != '' && inputs.checkout-ref || github.ref }} + ref: ${{ inputs.checkout-ref }} + submodules: ${{ inputs.checkout-submodules }} + - name: Checkout (default ref) + if: inputs.checkout-ref == '' + uses: actions/checkout@v4 + with: + path: ${{ inputs.checkout-path }} + # When checkout-ref is empty, rely on the default github.ref submodules: ${{ inputs.checkout-submodules }} - name: Setup Node.js uses: actions/setup-node@v4 From a43fce18ed8ae6f2e0d53ed2866161cc27121779 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com> Date: Sun, 15 Feb 2026 15:39:41 -0800 Subject: [PATCH 18/31] Update actions/restore-assets/action.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- actions/restore-assets/action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actions/restore-assets/action.yml b/actions/restore-assets/action.yml index cc138ed..4eda0f9 100644 --- a/actions/restore-assets/action.yml +++ b/actions/restore-assets/action.yml @@ -46,14 +46,14 @@ runs: promo-video-assets-${{ inputs.cache-version }}- - name: Download mining pool artifact - if: inputs.use-artifacts == 'true' + if: fromJSON(inputs.use-artifacts) uses: actions/download-artifact@v4 with: name: mining-pool-assets continue-on-error: true - name: Download promo video artifact - if: inputs.use-artifacts == 'true' + if: fromJSON(inputs.use-artifacts) uses: actions/download-artifact@v4 with: name: promo-video-assets From bd595c8d795aa817da62f7fe92a33ff55e79b795 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Sun, 15 Feb 2026 15:46:27 -0800 Subject: [PATCH 19/31] Sanitize command steps --- actions/lint-test-build/action.yml | 47 +++++++++++++++++++++--------- actions/sync-assets/action.yml | 16 ++++++---- 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/actions/lint-test-build/action.yml b/actions/lint-test-build/action.yml index b5eecc6..af3f747 100644 --- a/actions/lint-test-build/action.yml +++ b/actions/lint-test-build/action.yml @@ -1,5 +1,5 @@ name: 'Lint, Test, and Build' -description: 'Run lint, test, and build steps for Node.js projects' +description: 'Run lint, test, and build steps for Node.js projects. Script inputs must be npm script names only and should only be set by trusted workflow authors.' inputs: working-directory: @@ -13,26 +13,26 @@ inputs: description: 'Whether to run lint step' required: false default: 'true' - lint-command: - description: 'Command to run for linting' + lint-script: + description: 'npm script name for linting (e.g. from package.json). Must be a single script name; set only from trusted workflow authors.' required: false - default: 'npm run lint' + default: 'lint' run-test: description: 'Whether to run test step' required: false default: 'true' - test-command: - description: 'Command to run for testing' + test-script: + description: 'npm script name for testing (e.g. from package.json). Must be a single script name; set only from trusted workflow authors.' required: false - default: 'npm run test' + default: 'test' run-build: description: 'Whether to run build step' required: false default: 'true' - build-command: - description: 'Command to run for building' + build-script: + description: 'npm script name for building (e.g. from package.json). Must be a single script name; set only from trusted workflow authors.' required: false - default: 'npm run build' + default: 'build' runs: using: 'composite' @@ -40,17 +40,38 @@ runs: - name: Lint if: inputs.run-lint == 'true' && inputs.flavor == 'dev' shell: bash - run: ${{ inputs.lint-command }} + env: + LINT_SCRIPT: ${{ inputs.lint-script }} + run: | + if [[ ! "$LINT_SCRIPT" =~ ^[a-zA-Z0-9_-]+$ ]]; then + echo "::error::lint-script must match ^[a-zA-Z0-9_-]+$ (got: $LINT_SCRIPT)" + exit 1 + fi + npm run "$LINT_SCRIPT" working-directory: ${{ inputs.working-directory }} - name: Test if: inputs.run-test == 'true' && inputs.flavor == 'dev' shell: bash - run: ${{ inputs.test-command }} + env: + TEST_SCRIPT: ${{ inputs.test-script }} + run: | + if [[ ! "$TEST_SCRIPT" =~ ^[a-zA-Z0-9_-]+$ ]]; then + echo "::error::test-script must match ^[a-zA-Z0-9_-]+$ (got: $TEST_SCRIPT)" + exit 1 + fi + npm run "$TEST_SCRIPT" working-directory: ${{ inputs.working-directory }} - name: Build if: inputs.run-build == 'true' shell: bash - run: ${{ inputs.build-command }} + env: + BUILD_SCRIPT: ${{ inputs.build-script }} + run: | + if [[ ! "$BUILD_SCRIPT" =~ ^[a-zA-Z0-9_-]+$ ]]; then + echo "::error::build-script must match ^[a-zA-Z0-9_-]+$ (got: $BUILD_SCRIPT)" + exit 1 + fi + npm run "$BUILD_SCRIPT" working-directory: ${{ inputs.working-directory }} diff --git a/actions/sync-assets/action.yml b/actions/sync-assets/action.yml index ee4dbbb..2073333 100644 --- a/actions/sync-assets/action.yml +++ b/actions/sync-assets/action.yml @@ -1,5 +1,5 @@ name: 'Sync and Cache Assets' -description: 'Sync assets from CDN, zip, upload as artifacts, and save to cache' +description: 'Sync assets from CDN, zip, upload as artifacts, and save to cache. Script input must be an npm script name only and should only be set by trusted workflow authors.' inputs: frontend-path: @@ -8,10 +8,10 @@ inputs: github-token: description: 'GitHub token for API access' required: true - sync-command: - description: 'npm command to run for syncing assets' + sync-script: + description: 'npm script name for syncing assets (e.g. from package.json). Must be a single script name; set only from trusted workflow authors.' required: false - default: 'npm run sync-assets-dev' + default: 'sync-assets-dev' cache-version: description: 'Cache version for manual invalidation (bump to force fresh sync)' required: false @@ -62,11 +62,17 @@ runs: - name: Sync assets shell: bash - run: ${{ inputs.sync-command }} env: GITHUB_TOKEN: ${{ inputs.github-token }} MEMPOOL_CDN: 1 VERBOSE: 1 + SYNC_SCRIPT: ${{ inputs.sync-script }} + run: | + if [[ ! "$SYNC_SCRIPT" =~ ^[a-zA-Z0-9_-]+$ ]]; then + echo "::error::sync-script must match ^[a-zA-Z0-9_-]+$ (got: $SYNC_SCRIPT)" + exit 1 + fi + npm run "$SYNC_SCRIPT" working-directory: ${{ inputs.frontend-path }} - name: Zip mining pool assets From a9fea3d1dc1086acca838321c7c36b88b4f68fc5 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Sun, 15 Feb 2026 15:53:34 -0800 Subject: [PATCH 20/31] Fail caching faster --- actions/sync-assets/action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actions/sync-assets/action.yml b/actions/sync-assets/action.yml index 2073333..1d196db 100644 --- a/actions/sync-assets/action.yml +++ b/actions/sync-assets/action.yml @@ -20,8 +20,8 @@ inputs: runs: using: 'composite' steps: + # Cache miss is success with cache-hit=false; only real errors (network, permissions) fail the step. - name: Restore cached mining pool assets - continue-on-error: true id: cache-mining-pool-restore uses: actions/cache/restore@v4 with: @@ -30,8 +30,8 @@ runs: restore-keys: | mining-pool-assets-${{ inputs.cache-version }}- + # Cache miss is success with cache-hit=false; only real errors (network, permissions) fail the step. - name: Restore cached promo video assets - continue-on-error: true id: cache-promo-video-restore uses: actions/cache/restore@v4 with: From 704b535ff4b0e38632413f124cf93cbe89b39fe8 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Sun, 15 Feb 2026 16:01:58 -0800 Subject: [PATCH 21/31] Simplify the cache keys --- actions/restore-assets/action.yml | 4 ++-- actions/sync-assets/action.yml | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/actions/restore-assets/action.yml b/actions/restore-assets/action.yml index 4eda0f9..a59eb81 100644 --- a/actions/restore-assets/action.yml +++ b/actions/restore-assets/action.yml @@ -31,7 +31,7 @@ runs: uses: actions/cache/restore@v4 with: path: mining-pool-assets.zip - key: mining-pool-assets-${{ inputs.cache-version }}-${{ hashFiles(format('{0}/package.json', inputs.frontend-path), format('{0}/package-lock.json', inputs.frontend-path)) }} + key: mining-pool-assets-${{ inputs.cache-version }} restore-keys: | mining-pool-assets-${{ inputs.cache-version }}- @@ -41,7 +41,7 @@ runs: uses: actions/cache/restore@v4 with: path: promo-video-assets.zip - key: promo-video-assets-${{ inputs.cache-version }}-${{ hashFiles(format('{0}/package.json', inputs.frontend-path), format('{0}/package-lock.json', inputs.frontend-path)) }} + key: promo-video-assets-${{ inputs.cache-version }} restore-keys: | promo-video-assets-${{ inputs.cache-version }}- diff --git a/actions/sync-assets/action.yml b/actions/sync-assets/action.yml index 1d196db..1b25e5b 100644 --- a/actions/sync-assets/action.yml +++ b/actions/sync-assets/action.yml @@ -26,7 +26,7 @@ runs: uses: actions/cache/restore@v4 with: path: mining-pool-assets.zip - key: mining-pool-assets-${{ inputs.cache-version }}-${{ hashFiles(format('{0}/package.json', inputs.frontend-path), format('{0}/package-lock.json', inputs.frontend-path)) }} + key: mining-pool-assets-${{ inputs.cache-version }} restore-keys: | mining-pool-assets-${{ inputs.cache-version }}- @@ -36,7 +36,7 @@ runs: uses: actions/cache/restore@v4 with: path: promo-video-assets.zip - key: promo-video-assets-${{ inputs.cache-version }}-${{ hashFiles(format('{0}/package.json', inputs.frontend-path), format('{0}/package-lock.json', inputs.frontend-path)) }} + key: promo-video-assets-${{ inputs.cache-version }} restore-keys: | promo-video-assets-${{ inputs.cache-version }}- @@ -100,11 +100,11 @@ runs: uses: actions/cache/save@v4 with: path: mining-pool-assets.zip - key: mining-pool-assets-${{ inputs.cache-version }}-${{ hashFiles(format('{0}/package.json', inputs.frontend-path), format('{0}/package-lock.json', inputs.frontend-path)) }} + key: mining-pool-assets-${{ inputs.cache-version }} - name: Save promo video assets cache if: steps.cache-promo-video-restore.outputs.cache-hit != 'true' uses: actions/cache/save@v4 with: path: promo-video-assets.zip - key: promo-video-assets-${{ inputs.cache-version }}-${{ hashFiles(format('{0}/package.json', inputs.frontend-path), format('{0}/package-lock.json', inputs.frontend-path)) }} + key: promo-video-assets-${{ inputs.cache-version }} From 7314df1f3e97b4021913b2c097180efe394b4073 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Sun, 15 Feb 2026 16:14:49 -0800 Subject: [PATCH 22/31] Use remote hash as cache key --- actions/restore-assets/action.yml | 31 +++++++++++++++++++++----- actions/sync-assets/action.yml | 37 +++++++++++++++++++++++++------ 2 files changed, 56 insertions(+), 12 deletions(-) diff --git a/actions/restore-assets/action.yml b/actions/restore-assets/action.yml index a59eb81..db058a6 100644 --- a/actions/restore-assets/action.yml +++ b/actions/restore-assets/action.yml @@ -5,12 +5,15 @@ inputs: frontend-path: description: 'Path to frontend directory' required: true + github-token: + description: 'GitHub token for API access (used to compute cache key from remote SHAs)' + required: true use-artifacts: description: 'Whether to download from artifacts (requires cache job to have run first)' required: false default: 'true' cache-version: - description: 'Cache version (must match sync-assets for cache hits)' + description: 'Cache version fallback when remote SHA lookup fails' required: false default: 'v1' @@ -25,15 +28,33 @@ outputs: runs: using: 'composite' steps: + - name: Compute asset cache key from remote SHAs + id: asset-hash + shell: bash + env: + FALLBACK_VERSION: ${{ inputs.cache-version }} + run: | + AUTH_HEADER="Authorization: Bearer ${{ inputs.github-token }}" + LOGOS_SHA=$(curl -sf -H "$AUTH_HEADER" -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/repos/mempool/mining-pool-logos/commits/HEAD" 2>/dev/null | jq -r '.sha // empty') || true + PROMO_SHA=$(curl -sf -H "$AUTH_HEADER" -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/repos/mempool/mempool-promo/commits/HEAD" 2>/dev/null | jq -r '.sha // empty') || true + if [[ -n "$LOGOS_SHA" && -n "$PROMO_SHA" ]]; then + HASH=$(echo -n "${LOGOS_SHA}${PROMO_SHA}" | sha256sum | cut -c1-12) + else + HASH="$FALLBACK_VERSION" + fi + echo "hash=$HASH" >> $GITHUB_OUTPUT + - name: Restore cached mining pool assets continue-on-error: true id: cache-mining-pool-restore uses: actions/cache/restore@v4 with: path: mining-pool-assets.zip - key: mining-pool-assets-${{ inputs.cache-version }} + key: mining-pool-assets-${{ steps.asset-hash.outputs.hash }} restore-keys: | - mining-pool-assets-${{ inputs.cache-version }}- + mining-pool-assets-${{ steps.asset-hash.outputs.hash }}- - name: Restore cached promo video assets continue-on-error: true @@ -41,9 +62,9 @@ runs: uses: actions/cache/restore@v4 with: path: promo-video-assets.zip - key: promo-video-assets-${{ inputs.cache-version }} + key: promo-video-assets-${{ steps.asset-hash.outputs.hash }} restore-keys: | - promo-video-assets-${{ inputs.cache-version }}- + promo-video-assets-${{ steps.asset-hash.outputs.hash }}- - name: Download mining pool artifact if: fromJSON(inputs.use-artifacts) diff --git a/actions/sync-assets/action.yml b/actions/sync-assets/action.yml index 1b25e5b..9a604f5 100644 --- a/actions/sync-assets/action.yml +++ b/actions/sync-assets/action.yml @@ -13,22 +13,45 @@ inputs: required: false default: 'sync-assets-dev' cache-version: - description: 'Cache version for manual invalidation (bump to force fresh sync)' + description: 'Cache version fallback when remote SHA lookup fails' required: false default: 'v1' +outputs: + cache-key-hash: + description: 'Hash used for cache keys (from remote SHAs or cache-version fallback)' + value: ${{ steps.asset-hash.outputs.hash }} + runs: using: 'composite' steps: + - name: Compute asset cache key from remote SHAs + id: asset-hash + shell: bash + env: + FALLBACK_VERSION: ${{ inputs.cache-version }} + run: | + AUTH_HEADER="Authorization: Bearer ${{ inputs.github-token }}" + LOGOS_SHA=$(curl -sf -H "$AUTH_HEADER" -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/repos/mempool/mining-pool-logos/commits/HEAD" 2>/dev/null | jq -r '.sha // empty') || true + PROMO_SHA=$(curl -sf -H "$AUTH_HEADER" -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/repos/mempool/mempool-promo/commits/HEAD" 2>/dev/null | jq -r '.sha // empty') || true + if [[ -n "$LOGOS_SHA" && -n "$PROMO_SHA" ]]; then + HASH=$(echo -n "${LOGOS_SHA}${PROMO_SHA}" | sha256sum | cut -c1-12) + else + HASH="$FALLBACK_VERSION" + fi + echo "hash=$HASH" >> $GITHUB_OUTPUT + # Cache miss is success with cache-hit=false; only real errors (network, permissions) fail the step. - name: Restore cached mining pool assets id: cache-mining-pool-restore uses: actions/cache/restore@v4 with: path: mining-pool-assets.zip - key: mining-pool-assets-${{ inputs.cache-version }} + key: mining-pool-assets-${{ steps.asset-hash.outputs.hash }} restore-keys: | - mining-pool-assets-${{ inputs.cache-version }}- + mining-pool-assets-${{ steps.asset-hash.outputs.hash }}- # Cache miss is success with cache-hit=false; only real errors (network, permissions) fail the step. - name: Restore cached promo video assets @@ -36,9 +59,9 @@ runs: uses: actions/cache/restore@v4 with: path: promo-video-assets.zip - key: promo-video-assets-${{ inputs.cache-version }} + key: promo-video-assets-${{ steps.asset-hash.outputs.hash }} restore-keys: | - promo-video-assets-${{ inputs.cache-version }}- + promo-video-assets-${{ steps.asset-hash.outputs.hash }}- - name: Unzip mining pool assets before sync continue-on-error: true @@ -100,11 +123,11 @@ runs: uses: actions/cache/save@v4 with: path: mining-pool-assets.zip - key: mining-pool-assets-${{ inputs.cache-version }} + key: mining-pool-assets-${{ steps.asset-hash.outputs.hash }} - name: Save promo video assets cache if: steps.cache-promo-video-restore.outputs.cache-hit != 'true' uses: actions/cache/save@v4 with: path: promo-video-assets.zip - key: promo-video-assets-${{ inputs.cache-version }} + key: promo-video-assets-${{ steps.asset-hash.outputs.hash }} From cae82129e39be18062bad76b955aacd0917bf32b Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com> Date: Sun, 15 Feb 2026 16:18:28 -0800 Subject: [PATCH 23/31] Update actions/node-ci/action.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- actions/node-ci/action.yml | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/actions/node-ci/action.yml b/actions/node-ci/action.yml index cd2d564..cdb3762 100644 --- a/actions/node-ci/action.yml +++ b/actions/node-ci/action.yml @@ -33,20 +33,13 @@ inputs: runs: using: 'composite' steps: - - name: Checkout (custom ref) - if: inputs.checkout-ref != '' + - name: Checkout uses: actions/checkout@v4 with: path: ${{ inputs.checkout-path }} + # When checkout-ref is empty, actions/checkout uses the default github.ref ref: ${{ inputs.checkout-ref }} submodules: ${{ inputs.checkout-submodules }} - - name: Checkout (default ref) - if: inputs.checkout-ref == '' - uses: actions/checkout@v4 - with: - path: ${{ inputs.checkout-path }} - # When checkout-ref is empty, rely on the default github.ref - submodules: ${{ inputs.checkout-submodules }} - name: Setup Node.js uses: actions/setup-node@v4 with: From 9bcc06bca2258289af8f4ae30e6b78b60753587a Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com> Date: Sun, 15 Feb 2026 16:18:55 -0800 Subject: [PATCH 24/31] Update workflows/project-board-automation.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- workflows/project-board-automation.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/workflows/project-board-automation.yml b/workflows/project-board-automation.yml index 3f1dfb5..1a0715e 100644 --- a/workflows/project-board-automation.yml +++ b/workflows/project-board-automation.yml @@ -87,8 +87,7 @@ jobs: if (!isUnprocessable) return false; // Match the known GitHub error phrasing for duplicate items const msg = e.message.toLowerCase(); - return /already\s+(exists\s+)?in\s+(this\s+)?project\b/i.test(msg) || - msg.includes('already'); + return /already\s+(exists\s+)?in\s+(this\s+)?project\b/i.test(msg); }); if (isAlreadyInProject) { From e4e531d615a5855d16c6d48c17e9fb272b20ab54 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com> Date: Sun, 15 Feb 2026 16:19:09 -0800 Subject: [PATCH 25/31] Update workflows/project-board-automation.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- workflows/project-board-automation.yml | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/workflows/project-board-automation.yml b/workflows/project-board-automation.yml index 1a0715e..88ab44f 100644 --- a/workflows/project-board-automation.yml +++ b/workflows/project-board-automation.yml @@ -177,13 +177,21 @@ jobs: } `; - // Execute the mutation - await github.graphql(mutation, { - projectId: "${{ secrets.PROJECT_ID }}", - itemId: projectItem.id, - fieldId: "${{ secrets.STATUS_FIELD_ID }}", - optionId: "${{ secrets.REVIEW_NEEDED_OPTION_ID }}" - }); + // Execute the mutation with error handling for better debugging + try { + await github.graphql(mutation, { + projectId: "${{ secrets.PROJECT_ID }}", + itemId: projectItem.id, + fieldId: "${{ secrets.STATUS_FIELD_ID }}", + optionId: "${{ secrets.REVIEW_NEEDED_OPTION_ID }}" + }); - console.log('Successfully updated project status to Review Needed'); + console.log('Successfully updated project status to Review Needed'); + } catch (error) { + console.error( + `Failed to update project status to Review Needed for project #${projectNumber} and item ${projectItem.id}:`, + error + ); + throw error; + } } From 719732df3140a01ab4094a9f38589e9d0e3c7f37 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com> Date: Sun, 15 Feb 2026 16:20:53 -0800 Subject: [PATCH 26/31] Update workflows/project-board-automation.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- workflows/project-board-automation.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/workflows/project-board-automation.yml b/workflows/project-board-automation.yml index 88ab44f..6600277 100644 --- a/workflows/project-board-automation.yml +++ b/workflows/project-board-automation.yml @@ -148,7 +148,9 @@ jobs: if (hasNextPage) { console.warn(`Stopped after ${MAX_PAGES} pages; more project items may exist.`); - } + const message = `Stopped after ${MAX_PAGES} pages; more project items may exist. Failing to avoid using incomplete project data.`; + console.error(message); + throw new Error(message); // Find the project item that belongs to the target project const projectItem = projectItems.find(item => item.project.number === projectNumber); From a170eb2f4c76f12343a7e86e428f6ffe276f76b6 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Sun, 15 Feb 2026 16:25:20 -0800 Subject: [PATCH 27/31] Fix cache key --- actions/restore-assets/action.yml | 4 ++-- actions/sync-assets/action.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/actions/restore-assets/action.yml b/actions/restore-assets/action.yml index db058a6..15c5c6a 100644 --- a/actions/restore-assets/action.yml +++ b/actions/restore-assets/action.yml @@ -54,7 +54,7 @@ runs: path: mining-pool-assets.zip key: mining-pool-assets-${{ steps.asset-hash.outputs.hash }} restore-keys: | - mining-pool-assets-${{ steps.asset-hash.outputs.hash }}- + mining-pool-assets-${{ steps.asset-hash.outputs.hash }} - name: Restore cached promo video assets continue-on-error: true @@ -64,7 +64,7 @@ runs: path: promo-video-assets.zip key: promo-video-assets-${{ steps.asset-hash.outputs.hash }} restore-keys: | - promo-video-assets-${{ steps.asset-hash.outputs.hash }}- + promo-video-assets-${{ steps.asset-hash.outputs.hash }} - name: Download mining pool artifact if: fromJSON(inputs.use-artifacts) diff --git a/actions/sync-assets/action.yml b/actions/sync-assets/action.yml index 9a604f5..8f4c9e2 100644 --- a/actions/sync-assets/action.yml +++ b/actions/sync-assets/action.yml @@ -51,7 +51,7 @@ runs: path: mining-pool-assets.zip key: mining-pool-assets-${{ steps.asset-hash.outputs.hash }} restore-keys: | - mining-pool-assets-${{ steps.asset-hash.outputs.hash }}- + mining-pool-assets-${{ steps.asset-hash.outputs.hash }} # Cache miss is success with cache-hit=false; only real errors (network, permissions) fail the step. - name: Restore cached promo video assets @@ -61,7 +61,7 @@ runs: path: promo-video-assets.zip key: promo-video-assets-${{ steps.asset-hash.outputs.hash }} restore-keys: | - promo-video-assets-${{ steps.asset-hash.outputs.hash }}- + promo-video-assets-${{ steps.asset-hash.outputs.hash }} - name: Unzip mining pool assets before sync continue-on-error: true From e9c82948e6a5e6e333a841bdc58567ba15dd93f6 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com> Date: Sun, 15 Feb 2026 16:30:57 -0800 Subject: [PATCH 28/31] Update workflows/project-board-automation.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- workflows/project-board-automation.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/workflows/project-board-automation.yml b/workflows/project-board-automation.yml index 6600277..b215871 100644 --- a/workflows/project-board-automation.yml +++ b/workflows/project-board-automation.yml @@ -197,3 +197,4 @@ jobs: throw error; } } + } From 0075e032901f15621bf4f1c575b57ec38d3fd39f Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com> Date: Sun, 15 Feb 2026 16:32:22 -0800 Subject: [PATCH 29/31] Update workflows/project-board-automation.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- workflows/project-board-automation.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/workflows/project-board-automation.yml b/workflows/project-board-automation.yml index b215871..e434542 100644 --- a/workflows/project-board-automation.yml +++ b/workflows/project-board-automation.yml @@ -151,6 +151,7 @@ jobs: const message = `Stopped after ${MAX_PAGES} pages; more project items may exist. Failing to avoid using incomplete project data.`; console.error(message); throw new Error(message); + } // Find the project item that belongs to the target project const projectItem = projectItems.find(item => item.project.number === projectNumber); From 507563a2796c8748c580d7fb197b2852df507b5d Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com> Date: Sun, 15 Feb 2026 16:32:49 -0800 Subject: [PATCH 30/31] Update actions/restore-assets/action.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- actions/restore-assets/action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actions/restore-assets/action.yml b/actions/restore-assets/action.yml index 15c5c6a..c08d4ef 100644 --- a/actions/restore-assets/action.yml +++ b/actions/restore-assets/action.yml @@ -67,18 +67,18 @@ runs: promo-video-assets-${{ steps.asset-hash.outputs.hash }} - name: Download mining pool artifact + continue-on-error: true if: fromJSON(inputs.use-artifacts) uses: actions/download-artifact@v4 with: name: mining-pool-assets - continue-on-error: true - name: Download promo video artifact + continue-on-error: true if: fromJSON(inputs.use-artifacts) uses: actions/download-artifact@v4 with: name: promo-video-assets - continue-on-error: true - name: Unzip mining pool assets shell: bash From 2a8a35828dbeea9f51beaf9ab13e5ce5fb2637c1 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Sun, 15 Feb 2026 16:38:55 -0800 Subject: [PATCH 31/31] Use separate action to compute cache keys --- actions/compute-asset-hash/action.yml | 37 +++++++++++++++++++++++++++ actions/restore-assets/action.yml | 23 +++++------------ actions/sync-assets/action.yml | 23 +++++------------ 3 files changed, 49 insertions(+), 34 deletions(-) create mode 100644 actions/compute-asset-hash/action.yml diff --git a/actions/compute-asset-hash/action.yml b/actions/compute-asset-hash/action.yml new file mode 100644 index 0000000..e7c5831 --- /dev/null +++ b/actions/compute-asset-hash/action.yml @@ -0,0 +1,37 @@ +name: 'Compute Asset Cache Hash' +description: 'Compute hash from mining-pool-logos and mempool-promo HEAD SHAs for cache keys' + +inputs: + github-token: + description: 'GitHub token for API access' + required: true + cache-version: + description: 'Cache version fallback when remote SHA lookup fails' + required: false + default: 'v1' + +outputs: + hash: + description: 'Hash used for cache keys (12-char sha256 of both SHAs or cache-version)' + value: ${{ steps.compute-hash.outputs.hash }} + +runs: + using: 'composite' + steps: + - name: Compute asset cache key from remote SHAs + id: compute-hash + shell: bash + env: + FALLBACK_VERSION: ${{ inputs.cache-version }} + run: | + AUTH_HEADER="Authorization: Bearer ${{ inputs.github-token }}" + LOGOS_SHA=$(curl -sf -H "$AUTH_HEADER" -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/repos/mempool/mining-pool-logos/commits/HEAD" 2>/dev/null | jq -r '.sha // empty') || true + PROMO_SHA=$(curl -sf -H "$AUTH_HEADER" -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/repos/mempool/mempool-promo/commits/HEAD" 2>/dev/null | jq -r '.sha // empty') || true + if [[ -n "$LOGOS_SHA" && -n "$PROMO_SHA" ]]; then + HASH=$(echo -n "${LOGOS_SHA}${PROMO_SHA}" | sha256sum | cut -c1-12) + else + HASH="$FALLBACK_VERSION" + fi + echo "hash=$HASH" >> $GITHUB_OUTPUT diff --git a/actions/restore-assets/action.yml b/actions/restore-assets/action.yml index c08d4ef..bcffedb 100644 --- a/actions/restore-assets/action.yml +++ b/actions/restore-assets/action.yml @@ -30,21 +30,10 @@ runs: steps: - name: Compute asset cache key from remote SHAs id: asset-hash - shell: bash - env: - FALLBACK_VERSION: ${{ inputs.cache-version }} - run: | - AUTH_HEADER="Authorization: Bearer ${{ inputs.github-token }}" - LOGOS_SHA=$(curl -sf -H "$AUTH_HEADER" -H "X-GitHub-Api-Version: 2022-11-28" \ - "https://api.github.com/repos/mempool/mining-pool-logos/commits/HEAD" 2>/dev/null | jq -r '.sha // empty') || true - PROMO_SHA=$(curl -sf -H "$AUTH_HEADER" -H "X-GitHub-Api-Version: 2022-11-28" \ - "https://api.github.com/repos/mempool/mempool-promo/commits/HEAD" 2>/dev/null | jq -r '.sha // empty') || true - if [[ -n "$LOGOS_SHA" && -n "$PROMO_SHA" ]]; then - HASH=$(echo -n "${LOGOS_SHA}${PROMO_SHA}" | sha256sum | cut -c1-12) - else - HASH="$FALLBACK_VERSION" - fi - echo "hash=$HASH" >> $GITHUB_OUTPUT + uses: ./.github/actions/compute-asset-hash + with: + github-token: ${{ inputs.github-token }} + cache-version: ${{ inputs.cache-version }} - name: Restore cached mining pool assets continue-on-error: true @@ -54,7 +43,7 @@ runs: path: mining-pool-assets.zip key: mining-pool-assets-${{ steps.asset-hash.outputs.hash }} restore-keys: | - mining-pool-assets-${{ steps.asset-hash.outputs.hash }} + mining-pool-assets- - name: Restore cached promo video assets continue-on-error: true @@ -64,7 +53,7 @@ runs: path: promo-video-assets.zip key: promo-video-assets-${{ steps.asset-hash.outputs.hash }} restore-keys: | - promo-video-assets-${{ steps.asset-hash.outputs.hash }} + promo-video-assets- - name: Download mining pool artifact continue-on-error: true diff --git a/actions/sync-assets/action.yml b/actions/sync-assets/action.yml index 8f4c9e2..c079d33 100644 --- a/actions/sync-assets/action.yml +++ b/actions/sync-assets/action.yml @@ -27,21 +27,10 @@ runs: steps: - name: Compute asset cache key from remote SHAs id: asset-hash - shell: bash - env: - FALLBACK_VERSION: ${{ inputs.cache-version }} - run: | - AUTH_HEADER="Authorization: Bearer ${{ inputs.github-token }}" - LOGOS_SHA=$(curl -sf -H "$AUTH_HEADER" -H "X-GitHub-Api-Version: 2022-11-28" \ - "https://api.github.com/repos/mempool/mining-pool-logos/commits/HEAD" 2>/dev/null | jq -r '.sha // empty') || true - PROMO_SHA=$(curl -sf -H "$AUTH_HEADER" -H "X-GitHub-Api-Version: 2022-11-28" \ - "https://api.github.com/repos/mempool/mempool-promo/commits/HEAD" 2>/dev/null | jq -r '.sha // empty') || true - if [[ -n "$LOGOS_SHA" && -n "$PROMO_SHA" ]]; then - HASH=$(echo -n "${LOGOS_SHA}${PROMO_SHA}" | sha256sum | cut -c1-12) - else - HASH="$FALLBACK_VERSION" - fi - echo "hash=$HASH" >> $GITHUB_OUTPUT + uses: ./.github/actions/compute-asset-hash + with: + github-token: ${{ inputs.github-token }} + cache-version: ${{ inputs.cache-version }} # Cache miss is success with cache-hit=false; only real errors (network, permissions) fail the step. - name: Restore cached mining pool assets @@ -51,7 +40,7 @@ runs: path: mining-pool-assets.zip key: mining-pool-assets-${{ steps.asset-hash.outputs.hash }} restore-keys: | - mining-pool-assets-${{ steps.asset-hash.outputs.hash }} + mining-pool-assets- # Cache miss is success with cache-hit=false; only real errors (network, permissions) fail the step. - name: Restore cached promo video assets @@ -61,7 +50,7 @@ runs: path: promo-video-assets.zip key: promo-video-assets-${{ steps.asset-hash.outputs.hash }} restore-keys: | - promo-video-assets-${{ steps.asset-hash.outputs.hash }} + promo-video-assets- - name: Unzip mining pool assets before sync continue-on-error: true