Add shared actions and workflows

This commit is contained in:
Felipe Knorr Kuhn 2026-02-15 09:39:43 -08:00
parent cfda47d987
commit 2db080690f
No known key found for this signature in database
GPG Key ID: 79619B52BB097C1A
6 changed files with 489 additions and 0 deletions

View File

@ -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 }}

View File

@ -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 }}

View File

@ -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

View File

@ -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 }}

View File

@ -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

View File

@ -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');
}