Skip to content

Commit e526ab9

Browse files
committed
feat: add reusable workflow to check PR labels for release conditions
Add check-pr-labels.yml workflow that checks for labels on PRs (or merged PRs on push events) and outputs boolean values for common release labels: - github-release - crates-release - prerelease - npm-release Supports both pull_request and push events, with optional default values for direct pushes
1 parent 0a619a5 commit e526ab9

11 files changed

Lines changed: 409 additions & 213 deletions

File tree

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
name: 'Create GitHub Release'
2+
description: 'Create a GitHub release with artifacts'
3+
4+
inputs:
5+
version:
6+
description: 'The version to release (e.g., 1.2.3)'
7+
required: true
8+
title:
9+
description: 'The release title'
10+
required: true
11+
notes:
12+
description: 'Release notes in markdown'
13+
required: false
14+
default: ''
15+
prerelease:
16+
description: 'Whether this is a prerelease'
17+
required: false
18+
default: false
19+
draft:
20+
description: 'Whether to create as draft'
21+
required: false
22+
default: false
23+
tag-prefix:
24+
description: 'Prefix for the git tag'
25+
required: false
26+
default: 'v'
27+
artifact-pattern:
28+
description: 'Pattern to match artifacts to attach (leave empty for none)'
29+
required: false
30+
default: ''
31+
32+
outputs:
33+
url:
34+
description: 'URL of the created release'
35+
value: ${{ steps.gh-release.outputs.url }}
36+
tag:
37+
description: 'The created tag name'
38+
value: ${{ inputs.tag-prefix }}${{ inputs.version }}
39+
40+
runs:
41+
using: 'composite'
42+
steps:
43+
- name: Download artifacts
44+
if: inputs.artifact-pattern != ''
45+
uses: actions/download-artifact@v4
46+
with:
47+
path: artifacts
48+
pattern: ${{ inputs.artifact-pattern }}
49+
merge-multiple: true
50+
51+
- name: Create Release
52+
id: gh-release
53+
uses: softprops/action-gh-release@v2
54+
with:
55+
tag_name: ${{ inputs.tag-prefix }}${{ inputs.version }}
56+
name: ${{ inputs.title }}
57+
body: |
58+
${{ inputs.notes }}
59+
60+
---
61+
**Version:** ${{ inputs.version }}
62+
draft: ${{ inputs.draft }}
63+
prerelease: ${{ inputs.prerelease }}
64+
files: ${{ inputs.artifact-pattern != '' && 'artifacts/*' || '' }}
65+
env:
66+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
67+

.github/workflows/generate-release-info.yml renamed to .github/workflows/blocks/actions/generate-release-info/action.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,6 @@ jobs:
124124
6. Include commit SHAs if necessary. If the commit is large, don't include it.
125125
7. 1-2 emojis max for personality
126126
8. Code diffs ONLY if necessary. If the diff is large, don't include it.
127-
127+
128128
The end goal is clear business technical writing that is easy to understand, use, and bring
129129
delight to the user
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: 'Publish to Crates.io'
2+
description: 'Publish a Rust crate to crates.io'
3+
4+
inputs:
5+
crate-name:
6+
description: 'The name of the crate to publish (from Cargo.toml)'
7+
required: true
8+
crate-path:
9+
description: 'Optional: Path to crate directory (e.g., crates/client). If empty, publishes from workspace root using -p flag.'
10+
required: false
11+
default: ''
12+
dry-run:
13+
description: 'Run cargo publish --dry-run instead of actual publish'
14+
required: false
15+
default: false
16+
17+
runs:
18+
using: 'composite'
19+
steps:
20+
- name: Setup Rust
21+
uses: dtolnay/rust-toolchain@stable
22+
23+
- name: Publish to crates.io
24+
shell: bash
25+
env:
26+
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
27+
run: |
28+
if [ -n "${{ inputs.crate-path }}" ]; then
29+
# Publish from crate directory
30+
echo "Publishing ${{ inputs.crate-name }} from ${{ inputs.crate-path }}..."
31+
cd "${{ inputs.crate-path }}"
32+
if [ "${{ inputs.dry-run }}" == "true" ]; then
33+
cargo publish --dry-run
34+
else
35+
cargo publish
36+
fi
37+
else
38+
# Publish from workspace root using -p flag
39+
echo "Publishing ${{ inputs.crate-name }} from workspace root..."
40+
if [ "${{ inputs.dry-run }}" == "true" ]; then
41+
cargo publish --dry-run -p "${{ inputs.crate-name }}"
42+
else
43+
cargo publish -p "${{ inputs.crate-name }}"
44+
fi
45+
fi
46+

.github/workflows/publish-npm.yml renamed to .github/workflows/blocks/actions/publish-npm/action.yaml

File renamed without changes.
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
name: 'Rust Build'
2+
description: 'Build Rust binary for multiple targets'
3+
4+
inputs:
5+
binary-name:
6+
description: 'The name of the crate/binary to build (from Cargo.toml)'
7+
required: true
8+
binary-path:
9+
description: 'Optional: Path to crate directory (e.g., "crates/client"). If empty, builds from workspace root using -p flag.'
10+
required: false
11+
default: ''
12+
target:
13+
description: 'Target triple (e.g., x86_64-unknown-linux-gnu)'
14+
required: true
15+
build-env:
16+
description: 'Compile-time env vars for .env file (e.g., "BASE_URL=https://example.com")'
17+
required: false
18+
default: ''
19+
20+
outputs:
21+
artifact-name:
22+
description: 'Name of the artifact'
23+
value: ${{ steps.package.outputs.artifact-name }}
24+
artifact-path:
25+
description: 'Path to the built artifact'
26+
value: ${{ steps.package.outputs.artifact-path }}
27+
28+
runs:
29+
using: 'composite'
30+
steps:
31+
- name: Setup Rust
32+
uses: dtolnay/rust-toolchain@stable
33+
with:
34+
targets: ${{ inputs.target }}
35+
36+
- name: Install deps (Linux)
37+
if: runner.os == 'Linux'
38+
shell: bash
39+
run: sudo apt-get update && sudo apt-get install -y libssl-dev pkg-config
40+
41+
- name: Install deps (macOS)
42+
if: runner.os == 'macOS'
43+
shell: bash
44+
run: brew install openssl@3
45+
46+
- name: Set OpenSSL env (macOS)
47+
if: runner.os == 'macOS'
48+
shell: bash
49+
run: |
50+
OPENSSL_DIR=$(brew --prefix openssl@3)
51+
echo "OPENSSL_DIR=${OPENSSL_DIR}" >> $GITHUB_ENV
52+
echo "PKG_CONFIG_PATH=${OPENSSL_DIR}/lib/pkgconfig" >> $GITHUB_ENV
53+
54+
- name: Create .env file
55+
if: inputs.build-env != ''
56+
shell: bash
57+
run: echo "${{ inputs.build-env }}" > .env
58+
59+
- name: Build
60+
shell: bash
61+
run: |
62+
if [ -n "${{ inputs.binary-path }}" ]; then
63+
# Build from crate directory
64+
cd "${{ inputs.binary-path }}"
65+
cargo build --release --target ${{ inputs.target }}
66+
else
67+
# Build from workspace root using -p flag
68+
cargo build --release --target ${{ inputs.target }} -p ${{ inputs.binary-name }}
69+
fi
70+
71+
- name: Package
72+
id: package
73+
shell: bash
74+
run: |
75+
mkdir -p artifacts
76+
if [ "${{ runner.os }}" == "Windows" ]; then
77+
ARTIFACT_NAME="${{ inputs.binary-name }}-${{ inputs.target }}.exe"
78+
ARTIFACT_PATH="artifacts/$ARTIFACT_NAME"
79+
cp target/${{ inputs.target }}/release/${{ inputs.binary-name }}.exe "$ARTIFACT_PATH"
80+
else
81+
ARTIFACT_NAME="${{ inputs.binary-name }}-${{ inputs.target }}"
82+
ARTIFACT_PATH="artifacts/$ARTIFACT_NAME"
83+
cp target/${{ inputs.target }}/release/${{ inputs.binary-name }} "$ARTIFACT_PATH"
84+
chmod +x "$ARTIFACT_PATH"
85+
fi
86+
echo "artifact-name=$ARTIFACT_NAME" >> $GITHUB_OUTPUT
87+
echo "artifact-path=$ARTIFACT_PATH" >> $GITHUB_OUTPUT
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: 'TypeScript Build'
2+
description: 'Build TypeScript project using Bun'
3+
4+
inputs:
5+
bun-version:
6+
description: 'Bun version to use'
7+
required: false
8+
default: 'latest'
9+
working-directory:
10+
description: 'Directory containing package.json'
11+
required: false
12+
default: '.'
13+
build-command:
14+
description: 'Build command to run'
15+
required: false
16+
default: 'bun run build'
17+
18+
runs:
19+
using: 'composite'
20+
steps:
21+
- name: Setup Bun
22+
uses: oven-sh/setup-bun@v2
23+
with:
24+
bun-version: ${{ inputs.bun-version }}
25+
26+
- name: Install dependencies
27+
shell: bash
28+
working-directory: ${{ inputs.working-directory }}
29+
run: bun install
30+
31+
- name: Build
32+
shell: bash
33+
working-directory: ${{ inputs.working-directory }}
34+
run: ${{ inputs.build-command }}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
name: 'Check PR Labels'
2+
description: 'Check PR labels and output flags for release decisions'
3+
4+
inputs:
5+
default-on-push:
6+
description: 'Comma-separated list of labels to default to true on direct push (no PR found). Format: github-release,crates-release'
7+
required: false
8+
default: ''
9+
10+
outputs:
11+
has_github_release:
12+
description: 'Whether PR has github-release label'
13+
value: ${{ steps.check-labels.outputs.has_github_release }}
14+
has_crates_release:
15+
description: 'Whether PR has crates-release label'
16+
value: ${{ steps.check-labels.outputs.has_crates_release }}
17+
has_prerelease:
18+
description: 'Whether PR has prerelease label'
19+
value: ${{ steps.check-labels.outputs.has_prerelease }}
20+
has_npm_release:
21+
description: 'Whether PR has npm-release label'
22+
value: ${{ steps.check-labels.outputs.has_npm_release }}
23+
24+
runs:
25+
using: 'composite'
26+
steps:
27+
- name: Check PR Labels
28+
id: check-labels
29+
uses: actions/github-script@v7
30+
with:
31+
script: |
32+
// Always check these labels
33+
const defaultOnPush = '${{ inputs.default-on-push }}'.split(',').map(l => l.trim().toLowerCase()).filter(Boolean);
34+
35+
let pr = null;
36+
let labels = [];
37+
38+
// If this is a pull_request event, use it directly
39+
if (context.eventName === 'pull_request') {
40+
pr = context.payload.pull_request;
41+
labels = (pr.labels || []).map(l => l.name.toLowerCase());
42+
console.log(`PR event: Found PR #${pr.number} with labels: ${labels.join(', ') || 'none'}`);
43+
} else {
44+
// For push events, find the merged PR
45+
try {
46+
const { data: prs } = await github.rest.pulls.list({
47+
owner: context.repo.owner,
48+
repo: context.repo.repo,
49+
state: 'closed',
50+
base: context.ref.replace('refs/heads/', ''),
51+
sort: 'updated',
52+
direction: 'desc',
53+
per_page: 10
54+
});
55+
56+
// Find the PR that was merged to this commit
57+
pr = prs.find(p => p.merge_commit_sha === context.sha);
58+
59+
if (pr) {
60+
const { data: prWithLabels } = await github.rest.pulls.get({
61+
owner: context.repo.owner,
62+
repo: context.repo.repo,
63+
pull_number: pr.number
64+
});
65+
labels = (prWithLabels.labels || []).map(l => l.name.toLowerCase());
66+
console.log(`Push event: Found merged PR #${pr.number} with labels: ${labels.join(', ') || 'none'}`);
67+
} else {
68+
console.log('No merged PR found for this commit.');
69+
}
70+
} catch (error) {
71+
console.log(`Error finding PR: ${error.message}`);
72+
}
73+
}
74+
75+
const labelsToCheck = ['github-release', 'crates-release', 'prerelease', 'npm-release'];
76+
77+
for (const label of defaultOnPush) {
78+
if (labelsToCheck.includes(label)) {
79+
labels.push(label);
80+
}
81+
}
82+
83+
const hasGithubRelease = labels.includes('github-release');
84+
const hasCratesRelease = labels.includes('crates-release');
85+
const hasPrerelease = labels.includes('prerelease');
86+
const hasNpmRelease = labels.includes('npm-release');
87+
88+
core.setOutput('has_github_release', hasGithubRelease ? 'true' : 'false');
89+
core.setOutput('has_crates_release', hasCratesRelease ? 'true' : 'false');
90+
core.setOutput('has_prerelease', hasPrerelease ? 'true' : 'false');
91+
core.setOutput('has_npm_release', hasNpmRelease ? 'true' : 'false');
92+
93+
return {
94+
has_github_release: hasGithubRelease,
95+
has_crates_release: hasCratesRelease,
96+
has_prerelease: hasPrerelease,
97+
has_npm_release: hasNpmRelease
98+
};
99+

0 commit comments

Comments
 (0)