Skip to content

fix: use buildHostToolEnv and simplify HTTP request types #256

fix: use buildHostToolEnv and simplify HTTP request types

fix: use buildHostToolEnv and simplify HTTP request types #256

name: Build All Platforms
on:
push:
branches: [main]
paths-ignore:
- README.md
- "**/*.md"
- "docs/**"
- "landing/**"
pull_request:
branches: [main]
paths-ignore:
- "docs/**"
- "landing/**"
jobs:
determine-version:
name: Determine and Set Version
runs-on: ubuntu-latest
outputs:
new_version: ${{ steps.version.outputs.new_version }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Determine semantic version
id: version
run: |
NEW_VERSION=$(node scripts/version.js)
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
echo "Determined version: $NEW_VERSION"
- name: Commit package.json version when it changed
run: |
git config --global user.name "GitHub Actions"
git config --global user.email "actions@github.com"
git add package.json
if ! git diff --cached --quiet; then
git commit -m "chore: update version to v${{ steps.version.outputs.new_version }} [skip ci]"
git push
else
echo "No version change needed"
fi
build-macos-arm64:
name: Build macOS ARM64 (Apple Silicon)
needs: determine-version
runs-on: macos-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Sync package.json version with computed release version
run: |
node -e "const fs=require('fs'); const path='package.json'; const pkg=JSON.parse(fs.readFileSync(path,'utf8')); pkg.version='${{ needs.determine-version.outputs.new_version }}'; fs.writeFileSync(path, JSON.stringify(pkg, null, 2) + '\n'); console.log('package.json version set to', pkg.version)"
- name: Determine macOS signing mode
id: mac_signing
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.MAC_CERTIFICATE || secrets.MACOS_CERTIFICATE_P12_BASE64 }}
P12_PASSWORD: ${{ secrets.MAC_CERTIFICATE_PWD || secrets.MACOS_CERTIFICATE_P12_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.MACOS_KEYCHAIN_PASSWORD || secrets.MAC_CERTIFICATE_PWD }}
CSC_NAME: ${{ secrets.MAC_DEVELOPER_ID }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
HAS_CERT_SECRETS=true
HAS_NOTARY_SECRETS=true
if [ -z "$BUILD_CERTIFICATE_BASE64" ] || [ -z "$P12_PASSWORD" ] || [ -z "$KEYCHAIN_PASSWORD" ]; then
HAS_CERT_SECRETS=false
fi
if [ -z "$APPLE_ID" ] || [ -z "$APPLE_APP_SPECIFIC_PASSWORD" ] || [ -z "$APPLE_TEAM_ID" ]; then
HAS_NOTARY_SECRETS=false
fi
if [ "$HAS_CERT_SECRETS" = "true" ]; then
echo "signing_enabled=true" >> "$GITHUB_OUTPUT"
if [ "$HAS_NOTARY_SECRETS" = "true" ]; then
echo "notarize_enabled=true" >> "$GITHUB_OUTPUT"
echo "macOS build mode: signed + notarized"
else
echo "notarize_enabled=false" >> "$GITHUB_OUTPUT"
echo "macOS build mode: signed only (missing notarization secrets)"
fi
else
echo "signing_enabled=false" >> "$GITHUB_OUTPUT"
echo "notarize_enabled=false" >> "$GITHUB_OUTPUT"
echo "macOS build mode: unsigned (missing signing certificate secrets)"
fi
- name: Prepare macOS signing keychain
if: steps.mac_signing.outputs.signing_enabled == 'true'
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.MAC_CERTIFICATE || secrets.MACOS_CERTIFICATE_P12_BASE64 }}
P12_PASSWORD: ${{ secrets.MAC_CERTIFICATE_PWD || secrets.MACOS_CERTIFICATE_P12_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.MACOS_KEYCHAIN_PASSWORD || secrets.MAC_CERTIFICATE_PWD }}
run: |
CERTIFICATE_PATH="$RUNNER_TEMP/certificate.p12"
KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db"
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode > "$CERTIFICATE_PATH"
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security import "$CERTIFICATE_PATH" -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security list-keychain -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | sed 's/"/"//g')
- name: Build renderer
run: npm run build:renderer
- name: Build Electron main process
run: npm run build:electron
- name: Build macOS ARM64 (Apple Silicon, signed/notarized)
if: steps.mac_signing.outputs.signing_enabled == 'true'
run: npx electron-builder --mac dmg --arm64 --publish never --config.mac.notarize=${{ steps.mac_signing.outputs.notarize_enabled }}
env:
GH_TOKEN: ${{ secrets.TOKEN }}
CSC_IDENTITY_AUTO_DISCOVERY: true
CSC_FOR_PULL_REQUEST: true
CSC_FOR_PULL_REQUEST_IF_PRIVATE: true
CSC_NAME: ${{ secrets.MAC_DEVELOPER_ID }}
DEBUG: electron-notarize*
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
USE_HARD_LINKS: false
- name: Build macOS ARM64 (Apple Silicon, unsigned)
if: steps.mac_signing.outputs.signing_enabled != 'true'
run: npx electron-builder --mac dmg --arm64 --publish never --config.mac.notarize=false
env:
GH_TOKEN: ${{ secrets.TOKEN }}
- name: Validate signed/notarized DMGs
if: steps.mac_signing.outputs.signing_enabled == 'true'
run: |
shopt -s nullglob
RETRY_MAX=4
RETRY_DELAY_SECONDS=30
staple_with_retry() {
local dmg="$1"
local attempt=1
while [ "$attempt" -le "$RETRY_MAX" ]; do
echo "Staple attempt $attempt/$RETRY_MAX for $dmg"
if xcrun stapler staple "$dmg"; then
if xcrun stapler validate "$dmg"; then
echo "Stapling and validation succeeded for $dmg"
return 0
fi
echo "Stapler validation failed for $dmg on attempt $attempt"
else
echo "Stapler staple failed for $dmg on attempt $attempt"
fi
if [ "$attempt" -lt "$RETRY_MAX" ]; then
echo "Ticket may still be propagating in Apple services; waiting ${RETRY_DELAY_SECONDS}s before retry"
sleep "$RETRY_DELAY_SECONDS"
fi
attempt=$((attempt + 1))
done
echo "Failed to staple and validate $dmg after $RETRY_MAX attempts"
return 1
}
DMGS=(release/*.dmg)
if [ ${#DMGS[@]} -eq 0 ]; then
echo "No DMG artifacts found in release/."
exit 1
fi
for dmg in "${DMGS[@]}"; do
echo "Validating $dmg"
if [ "${{ steps.mac_signing.outputs.notarize_enabled }}" = "true" ]; then
echo "Notarization enabled; attempting to staple DMG ticket for $dmg (best effort)"
if ! staple_with_retry "$dmg"; then
echo "Warning: DMG staple did not succeed for $dmg. Continuing because notarization tickets may exist only for the app bundle."
fi
else
echo "Notarization disabled; skipping stapler validation for $dmg"
fi
echo "Checking DMG container signature (non-blocking)"
if codesign --verify --verbose=2 "$dmg"; then
spctl -a -t open --context context:primary-signature -v "$dmg"
else
echo "Warning: DMG container is not signed/verifiable; continuing because shipped trust decision is enforced on enclosed app."
fi
MOUNT_POINT="$RUNNER_TEMP/mount-$(basename "$dmg" .dmg)"
mkdir -p "$MOUNT_POINT"
hdiutil attach "$dmg" -nobrowse -readonly -mountpoint "$MOUNT_POINT"
APP_PATH=$(find "$MOUNT_POINT" -maxdepth 1 -name "*.app" | head -n 1)
if [ -z "$APP_PATH" ]; then
echo "No .app found inside $dmg"
hdiutil detach "$MOUNT_POINT" -force || true
exit 1
fi
echo "Checking signature for $APP_PATH"
codesign --verify --deep --strict --verbose=2 "$APP_PATH"
APP_VALIDATION_DIR="$RUNNER_TEMP/validate-$(basename "$dmg" .dmg)"
APP_VALIDATION_PATH="$APP_VALIDATION_DIR/$(basename "$APP_PATH")"
rm -rf "$APP_VALIDATION_DIR"
mkdir -p "$APP_VALIDATION_DIR"
ditto "$APP_PATH" "$APP_VALIDATION_PATH"
if [ "${{ steps.mac_signing.outputs.notarize_enabled }}" = "true" ]; then
echo "Notarization enabled; stapling/validating app bundle ticket for writable copy $APP_VALIDATION_PATH"
staple_with_retry "$APP_VALIDATION_PATH"
fi
spctl -a -vv -t exec "$APP_VALIDATION_PATH"
rm -rf "$APP_VALIDATION_DIR"
hdiutil detach "$MOUNT_POINT"
done
- name: Upload macOS ARM64 artifacts
uses: actions/upload-artifact@v4
with:
name: Chaton-macOS-ARM64
path: release/*.dmg
if-no-files-found: error
build-macos-x64:
name: Build macOS x64 (Intel)
needs: determine-version
runs-on: macos-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Sync package.json version with computed release version
run: |
node -e "const fs=require('fs'); const path='package.json'; const pkg=JSON.parse(fs.readFileSync(path,'utf8')); pkg.version='${{ needs.determine-version.outputs.new_version }}'; fs.writeFileSync(path, JSON.stringify(pkg, null, 2) + '\n'); console.log('package.json version set to', pkg.version)"
- name: Determine macOS signing mode
id: mac_signing
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.MAC_CERTIFICATE || secrets.MACOS_CERTIFICATE_P12_BASE64 }}
P12_PASSWORD: ${{ secrets.MAC_CERTIFICATE_PWD || secrets.MACOS_CERTIFICATE_P12_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.MACOS_KEYCHAIN_PASSWORD || secrets.MAC_CERTIFICATE_PWD }}
CSC_NAME: ${{ secrets.MAC_DEVELOPER_ID }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
HAS_CERT_SECRETS=true
HAS_NOTARY_SECRETS=true
if [ -z "$BUILD_CERTIFICATE_BASE64" ] || [ -z "$P12_PASSWORD" ] || [ -z "$KEYCHAIN_PASSWORD" ]; then
HAS_CERT_SECRETS=false
fi
if [ -z "$APPLE_ID" ] || [ -z "$APPLE_APP_SPECIFIC_PASSWORD" ] || [ -z "$APPLE_TEAM_ID" ]; then
HAS_NOTARY_SECRETS=false
fi
if [ "$HAS_CERT_SECRETS" = "true" ]; then
echo "signing_enabled=true" >> "$GITHUB_OUTPUT"
if [ "$HAS_NOTARY_SECRETS" = "true" ]; then
echo "notarize_enabled=true" >> "$GITHUB_OUTPUT"
echo "macOS build mode: signed + notarized"
else
echo "notarize_enabled=false" >> "$GITHUB_OUTPUT"
echo "macOS build mode: signed only (missing notarization secrets)"
fi
else
echo "signing_enabled=false" >> "$GITHUB_OUTPUT"
echo "notarize_enabled=false" >> "$GITHUB_OUTPUT"
echo "macOS build mode: unsigned (missing signing certificate secrets)"
fi
- name: Prepare macOS signing keychain
if: steps.mac_signing.outputs.signing_enabled == 'true'
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.MAC_CERTIFICATE || secrets.MACOS_CERTIFICATE_P12_BASE64 }}
P12_PASSWORD: ${{ secrets.MAC_CERTIFICATE_PWD || secrets.MACOS_CERTIFICATE_P12_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.MACOS_KEYCHAIN_PASSWORD || secrets.MAC_CERTIFICATE_PWD }}
run: |
CERTIFICATE_PATH="$RUNNER_TEMP/certificate.p12"
KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db"
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode > "$CERTIFICATE_PATH"
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security import "$CERTIFICATE_PATH" -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security list-keychain -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | sed 's/"/"//g')
- name: Build renderer
run: npm run build:renderer
- name: Build Electron main process
run: npm run build:electron
- name: Build macOS x64 (Intel, signed/notarized)
if: steps.mac_signing.outputs.signing_enabled == 'true'
run: npx electron-builder --mac dmg --x64 --publish never --config.mac.notarize=${{ steps.mac_signing.outputs.notarize_enabled }}
env:
GH_TOKEN: ${{ secrets.TOKEN }}
CSC_IDENTITY_AUTO_DISCOVERY: true
CSC_FOR_PULL_REQUEST: true
CSC_FOR_PULL_REQUEST_IF_PRIVATE: true
CSC_NAME: ${{ secrets.MAC_DEVELOPER_ID }}
DEBUG: electron-notarize*
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
USE_HARD_LINKS: false
- name: Build macOS x64 (Intel, unsigned)
if: steps.mac_signing.outputs.signing_enabled != 'true'
run: npx electron-builder --mac dmg --x64 --publish never --config.mac.notarize=false
env:
GH_TOKEN: ${{ secrets.TOKEN }}
- name: Validate signed/notarized DMGs
if: steps.mac_signing.outputs.signing_enabled == 'true'
run: |
shopt -s nullglob
RETRY_MAX=4
RETRY_DELAY_SECONDS=30
staple_with_retry() {
local dmg="$1"
local attempt=1
while [ "$attempt" -le "$RETRY_MAX" ]; do
echo "Staple attempt $attempt/$RETRY_MAX for $dmg"
if xcrun stapler staple "$dmg"; then
if xcrun stapler validate "$dmg"; then
echo "Stapling and validation succeeded for $dmg"
return 0
fi
echo "Stapler validation failed for $dmg on attempt $attempt"
else
echo "Stapler staple failed for $dmg on attempt $attempt"
fi
if [ "$attempt" -lt "$RETRY_MAX" ]; then
echo "Ticket may still be propagating in Apple services; waiting ${RETRY_DELAY_SECONDS}s before retry"
sleep "$RETRY_DELAY_SECONDS"
fi
attempt=$((attempt + 1))
done
echo "Failed to staple and validate $dmg after $RETRY_MAX attempts"
return 1
}
DMGS=(release/*.dmg)
if [ ${#DMGS[@]} -eq 0 ]; then
echo "No DMG artifacts found in release/."
exit 1
fi
for dmg in "${DMGS[@]}"; do
echo "Validating $dmg"
if [ "${{ steps.mac_signing.outputs.notarize_enabled }}" = "true" ]; then
echo "Notarization enabled; attempting to staple DMG ticket for $dmg (best effort)"
if ! staple_with_retry "$dmg"; then
echo "Warning: DMG staple did not succeed for $dmg. Continuing because notarization tickets may exist only for the app bundle."
fi
else
echo "Notarization disabled; skipping stapler validation for $dmg"
fi
echo "Checking DMG container signature (non-blocking)"
if codesign --verify --verbose=2 "$dmg"; then
spctl -a -t open --context context:primary-signature -v "$dmg"
else
echo "Warning: DMG container is not signed/verifiable; continuing because shipped trust decision is enforced on enclosed app."
fi
MOUNT_POINT="$RUNNER_TEMP/mount-$(basename "$dmg" .dmg)"
mkdir -p "$MOUNT_POINT"
hdiutil attach "$dmg" -nobrowse -readonly -mountpoint "$MOUNT_POINT"
APP_PATH=$(find "$MOUNT_POINT" -maxdepth 1 -name "*.app" | head -n 1)
if [ -z "$APP_PATH" ]; then
echo "No .app found inside $dmg"
hdiutil detach "$MOUNT_POINT" -force || true
exit 1
fi
echo "Checking signature for $APP_PATH"
codesign --verify --deep --strict --verbose=2 "$APP_PATH"
APP_VALIDATION_DIR="$RUNNER_TEMP/validate-$(basename "$dmg" .dmg)"
APP_VALIDATION_PATH="$APP_VALIDATION_DIR/$(basename "$APP_PATH")"
rm -rf "$APP_VALIDATION_DIR"
mkdir -p "$APP_VALIDATION_DIR"
ditto "$APP_PATH" "$APP_VALIDATION_PATH"
if [ "${{ steps.mac_signing.outputs.notarize_enabled }}" = "true" ]; then
echo "Notarization enabled; stapling/validating app bundle ticket for writable copy $APP_VALIDATION_PATH"
staple_with_retry "$APP_VALIDATION_PATH"
fi
spctl -a -vv -t exec "$APP_VALIDATION_PATH"
rm -rf "$APP_VALIDATION_DIR"
hdiutil detach "$MOUNT_POINT"
done
- name: Upload macOS x64 artifacts
uses: actions/upload-artifact@v4
with:
name: Chaton-macOS-x64
path: release/*.dmg
if-no-files-found: error
build-windows:
name: Build Windows App
needs: determine-version
runs-on: windows-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Sync package.json version with computed release version
run: |
node -e "const fs=require('fs'); const path='package.json'; const pkg=JSON.parse(fs.readFileSync(path,'utf8')); pkg.version='${{ needs.determine-version.outputs.new_version }}'; fs.writeFileSync(path, JSON.stringify(pkg, null, 2) + '\n'); console.log('package.json version set to', pkg.version)"
- name: Build renderer
run: npm run build:renderer
- name: Build Electron main process
run: npm run build:electron
- name: Build Windows
run: npm run dist:win
env:
GH_TOKEN: ${{ secrets.TOKEN }}
- name: Upload Windows artifacts
uses: actions/upload-artifact@v4
with:
name: Chaton-Windows
path: release/*.exe
if-no-files-found: error
build-linux:
name: Build Linux App
needs: determine-version
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Sync package.json version with computed release version
run: |
node -e "const fs=require('fs'); const path='package.json'; const pkg=JSON.parse(fs.readFileSync(path,'utf8')); pkg.version='${{ needs.determine-version.outputs.new_version }}'; fs.writeFileSync(path, JSON.stringify(pkg, null, 2) + '\n'); console.log('package.json version set to', pkg.version)"
- name: Build renderer
run: npm run build:renderer
- name: Build Electron main process
run: npm run build:electron
- name: Build Linux
run: npm run dist:linux
env:
GH_TOKEN: ${{ secrets.TOKEN }}
- name: Upload Linux artifacts
uses: actions/upload-artifact@v4
with:
name: Chaton-Linux
path: release/*.AppImage
if-no-files-found: error
release:
permissions:
contents: write
name: Create Release
needs:
[
determine-version,
build-macos-arm64,
build-macos-x64,
build-windows,
build-linux,
]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Get current version from package.json
id: current_version
run: |
CURRENT_VERSION=$(node -p "require('./package.json').version")
echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
echo "Current version: $CURRENT_VERSION"
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: List all artifacts
run: find artifacts -type f
- name: Generate Release Notes with AI
id: generate_notes
continue-on-error: true
uses: actions/github-script@v7
with:
script: |
// Get the latest published release to use as baseline
let sinceDate = null;
let previousTag = null;
try {
const { data: latestRelease } = await github.rest.repos.getLatestRelease({
owner: context.repo.owner,
repo: context.repo.repo,
});
sinceDate = latestRelease.published_at;
previousTag = latestRelease.tag_name;
} catch (e) {
// No previous release exists, include everything
}
// Fetch commits since the last release
const commitParams = {
owner: context.repo.owner,
repo: context.repo.repo,
sha: 'main',
per_page: 100,
};
if (sinceDate) commitParams.since = sinceDate;
const { data: commits } = await github.rest.repos.listCommits(commitParams);
// Fetch closed PRs merged since the last release
const { data: prs } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'closed',
base: 'main',
sort: 'updated',
direction: 'desc',
per_page: 50,
});
const changes = [];
// Collect PRs merged after the previous release
for (const pr of prs) {
if (!pr.merged_at) continue;
if (sinceDate && new Date(pr.merged_at) <= new Date(sinceDate)) continue;
changes.push(`- ${pr.title} (PR #${pr.number})`);
}
// Collect conventional commits not already covered by a PR
const prCommitShas = new Set(prs.filter(pr => pr.merge_commit_sha).map(pr => pr.merge_commit_sha));
for (const commit of commits) {
if (prCommitShas.has(commit.sha)) continue;
const match = commit.commit.message.match(/^(feat|fix|docs|style|refactor|perf|test|chore)(?:\(.+\))?: (.+)/);
if (match) {
changes.push(`- ${match[1]}: ${match[2]}`);
}
}
const compareBase = previousTag || `v${process.env.CURRENT_VERSION}`;
const releaseNotes = [
'## What\'s Changed',
'',
changes.length > 0 ? changes.join('\n') : '- Minor improvements and bug fixes',
'',
'## Downloads',
'',
'- **macOS**: Apple Silicon (ARM64) and Intel (x64) .dmg files',
'- **Windows**: .exe installer',
'- **Linux**: .AppImage',
'',
`Full Changelog: https://github.com/${context.repo.owner}/${context.repo.repo}/compare/${compareBase}...v${process.env.NEW_VERSION}`,
].join('\n');
core.setOutput('release_notes', releaseNotes);
return releaseNotes;
- name: Create Release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ needs.determine-version.outputs.new_version }}
name: Release v${{ needs.determine-version.outputs.new_version }}
body: ${{ steps.generate_notes.outputs.release_notes || format('Automatic multi-platform build from commit {0}', github.sha) }}
draft: false
prerelease: false
files: artifacts/**/*
env:
TOKEN: ${{ secrets.TOKEN }}
CURRENT_VERSION: ${{ steps.current_version.outputs.current_version }}
NEW_VERSION: ${{ needs.determine-version.outputs.new_version }}