From 3c26dd5eb9bff67bf2cdc1dee3ed81535241b922 Mon Sep 17 00:00:00 2001 From: Tickbase Date: Sun, 18 May 2025 01:28:42 +0200 Subject: [PATCH] release --- .github/workflows/build.yml | 12 ++- .github/workflows/release.yml | 184 ++++++++++++++++++++++++++++------ docs/release.md | 59 ++++++++++- scripts/release.js | 178 ++++++++++++++++++++++++++------ 4 files changed, 366 insertions(+), 67 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3fb33c7..48855f5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,12 +24,16 @@ jobs: uses: actions/setup-node@v4 with: node-version: 20 + cache: 'npm' - - name: Install Rust - uses: actions-rs/toolchain@v1 + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@v2 with: - toolchain: stable - profile: minimal + workspaces: 'src-tauri -> target' + cache-on-failure: true - name: Install system dependencies run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7737daf..643d13a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,16 +17,22 @@ on: description: 'Custom release notes (leave empty for auto-generated)' required: false type: string + dry_run: + description: 'Perform a dry run (no actual release)' + required: false + default: false + type: boolean env: CARGO_TERM_COLOR: always + CARGO_INCREMENTAL: 0 + RUST_BACKTRACE: short jobs: - create-release: + version-bump: runs-on: ubuntu-latest outputs: new_version: ${{ steps.version.outputs.new_version }} - release_id: ${{ steps.create_release.outputs.id }} steps: - uses: actions/checkout@v4 @@ -37,9 +43,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: 20 - - - name: Install Rust - uses: dtolnay/rust-toolchain@stable + cache: 'npm' - name: Calculate New Version id: version @@ -100,10 +104,11 @@ jobs: CHANGELOG="## Changes\n\n$COMMITS" # Save changelog to a file (multiline to output) - echo "$CHANGELOG" > changelog.md + echo -e "$CHANGELOG" > changelog.md echo "Generated changelog from commit messages" - name: Commit Version Changes + if: ${{ !github.event.inputs.dry_run }} env: NEW_VERSION: ${{ steps.version.outputs.new_version }} run: | @@ -115,36 +120,41 @@ jobs: git push origin HEAD:${GITHUB_REF#refs/heads/} git push origin "v$NEW_VERSION" - - name: Create GitHub Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NEW_VERSION: ${{ steps.version.outputs.new_version }} + # Upload changelog as an artifact for the publish-release job + - name: Upload Changelog + uses: actions/upload-artifact@v4 with: - tag_name: v${{ steps.version.outputs.new_version }} - release_name: CreamLinux v${{ steps.version.outputs.new_version }} - body_path: ${{ github.event.inputs.custom_release_notes == '' && 'changelog.md' || github.event.inputs.custom_release_notes }} - draft: false - prerelease: false + name: changelog + path: changelog.md + retention-days: 1 build-linux: - needs: create-release + needs: version-bump runs-on: ubuntu-latest + outputs: + appimage_path: ${{ steps.find-assets.outputs.appimage_path }} + deb_path: ${{ steps.find-assets.outputs.deb_path }} steps: - uses: actions/checkout@v4 with: - ref: v${{ needs.create-release.outputs.new_version }} + ref: v${{ needs.version-bump.outputs.new_version }} - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 20 + cache: 'npm' - - name: Install Rust + - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@v2 + with: + workspaces: 'src-tauri -> target' + cache-on-failure: true + - name: Install system dependencies run: | sudo apt-get update @@ -153,25 +163,143 @@ jobs: - name: Install frontend dependencies run: npm install + - name: Quick lint and type-check + run: | + npm run lint + npx tsc --noEmit + + - name: Build the app (optimized) + run: npm run tauri build + + - name: Find build assets + id: find-assets + run: | + echo "appimage_path=$(find src-tauri/target/release/bundle/appimage -name "*.AppImage" | head -n 1)" >> $GITHUB_OUTPUT + echo "deb_path=$(find src-tauri/target/release/bundle/deb -name "*.deb" | head -n 1)" >> $GITHUB_OUTPUT + + - name: Upload Linux builds as artifacts + uses: actions/upload-artifact@v4 + with: + name: linux-builds + path: | + ${{ steps.find-assets.outputs.appimage_path }} + ${{ steps.find-assets.outputs.deb_path }} + retention-days: 1 + + # Build for other platforms in parallel if needed - examples: + build-windows: + needs: version-bump + runs-on: windows-latest + if: false # Disabled until we add Windows build support + outputs: + msi_path: ${{ steps.find-assets.outputs.msi_path }} + + steps: + - uses: actions/checkout@v4 + with: + ref: v${{ needs.version-bump.outputs.new_version }} + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@v2 + with: + workspaces: 'src-tauri -> target' + cache-on-failure: true + + - name: Install frontend dependencies + run: npm install + - name: Build the app run: npm run tauri build - - name: Upload AppImage + - name: Find build assets + id: find-assets + shell: bash + run: | + echo "msi_path=$(find src-tauri/target/release/bundle/msi -name "*.msi" | head -n 1)" >> $GITHUB_OUTPUT + + - name: Upload Windows builds as artifacts + uses: actions/upload-artifact@v4 + with: + name: windows-builds + path: | + ${{ steps.find-assets.outputs.msi_path }} + retention-days: 1 + + # Final job to create the release and upload assets + publish-release: + needs: [version-bump, build-linux] + if: ${{ !github.event.inputs.dry_run }} + runs-on: ubuntu-latest + + steps: + - name: Download changelog + uses: actions/download-artifact@v4 + with: + name: changelog + + - name: Download Linux builds + uses: actions/download-artifact@v4 + with: + name: linux-builds + path: dist + + # Add this step if Windows builds are enabled + # - name: Download Windows builds + # if: needs.build-windows.result == 'success' + # uses: actions/download-artifact@v4 + # with: + # name: windows-builds + # path: dist + + - name: Check downloaded artifacts + run: | + echo "Contents of dist directory:" + find dist -type f | sort + + - name: Create GitHub Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NEW_VERSION: ${{ needs.version-bump.outputs.new_version }} + with: + tag_name: v${{ needs.version-bump.outputs.new_version }} + release_name: CreamLinux v${{ needs.version-bump.outputs.new_version }} + body_path: ${{ github.event.inputs.custom_release_notes == '' && 'changelog.md' || github.event.inputs.custom_release_notes }} + draft: false + prerelease: false + + - name: Get filenames + id: get_filenames + run: | + echo "appimage_filename=$(basename $(find dist -name '*.AppImage' | head -n 1))" >> $GITHUB_OUTPUT + echo "deb_filename=$(basename $(find dist -name '*.deb' | head -n 1))" >> $GITHUB_OUTPUT + + - name: Upload AppImage to release uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - upload_url: ${{ needs.create-release.outputs.release_id }} - asset_path: ./src-tauri/target/release/bundle/appimage/*.AppImage - asset_name: CreamLinux-${{ needs.create-release.outputs.new_version }}.AppImage + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./dist/${{ steps.get_filenames.outputs.appimage_filename }} + asset_name: CreamLinux-${{ needs.version-bump.outputs.new_version }}.AppImage asset_content_type: application/octet-stream - - name: Upload Debian Package + - name: Upload Debian Package to release uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - upload_url: ${{ needs.create-release.outputs.release_id }} - asset_path: ./src-tauri/target/release/bundle/deb/*.deb - asset_name: creamlinux_${{ needs.create-release.outputs.new_version }}_amd64.deb + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./dist/${{ steps.get_filenames.outputs.deb_filename }} + asset_name: creamlinux_${{ needs.version-bump.outputs.new_version }}_amd64.deb asset_content_type: application/vnd.debian.binary-package diff --git a/docs/release.md b/docs/release.md index 3f15a30..1fde4a1 100644 --- a/docs/release.md +++ b/docs/release.md @@ -18,6 +18,7 @@ To use the release automation, you need: - Git installed locally - GitHub CLI installed and authenticated - Proper GitHub permissions on the repository +- Optional: `act` for local testing (https://github.com/nektos/act) ## Starting a Release @@ -44,6 +45,36 @@ npm run release minor "Added DLC management feature and improved UI" If you don't provide custom notes, they will be automatically generated from commit messages since the last release. +### Advanced Options + +The release script supports several advanced options: + +#### Dry Run + +Run the release process without actually creating a release: + +```bash +npm run release patch --dry-run +``` + +This will perform all the version incrementing and workflow steps but won't create the actual GitHub release. + +#### Local Testing + +Test the workflow locally before running it on GitHub: + +```bash +npm run release minor --local +``` + +This requires the `act` tool to be installed (https://github.com/nektos/act) and runs the workflow locally to check for errors. + +You can combine options: + +```bash +npm run release patch --local --dry-run +``` + ### Method 2: Manually triggering the workflow in GitHub You can also manually trigger the workflow from the GitHub Actions tab: @@ -54,7 +85,8 @@ You can also manually trigger the workflow from the GitHub Actions tab: 4. Click "Run workflow" 5. Choose the version increment type (patch, minor, major) 6. Optionally enter custom release notes -7. Click "Run workflow" +7. Check the "dry run" option if you want to test without creating a release +8. Click "Run workflow" ## Release Process Details @@ -70,10 +102,19 @@ The release process follows these steps: - Commits the version changes - Creates a version tag - Pushes the changes and tag to the repository -5. **Release Creation**: Creates a GitHub Release with the changelog -6. **Build Process**: Builds the application for Linux +5. **Build Process**: Builds the application for Linux (and potentially other platforms) +6. **Release Creation**: Creates a GitHub Release with the changelog after all builds are complete 7. **Asset Upload**: Uploads the AppImage and Debian package to the release +## Optimizations + +The release workflow includes several optimizations: + +- **Caching**: Uses Rust and npm caches to speed up build times +- **Parallel Builds**: Builds for different platforms in parallel +- **Artifact Management**: Properly manages build artifacts between jobs +- **Error Handling**: Improves error detection and reporting + ## Release Artifacts The following artifacts are published to the GitHub release: @@ -94,7 +135,15 @@ CreamLinux follows [Semantic Versioning](https://semver.org/): If you encounter issues with the release process: 1. **Check GitHub Actions logs**: Review the workflow logs for detailed error information -2. **Verify permissions**: Ensure you have write permissions to the repository -3. **GitHub CLI authentication**: Run `gh auth status` to verify authentication +2. **Use local testing**: Run with `--local` flag to test locally before triggering on GitHub +3. **Try dry run mode**: Use `--dry-run` to test the process without creating an actual release +4. **Verify permissions**: Ensure you have write permissions to the repository +5. **GitHub CLI authentication**: Run `gh auth status` to verify authentication + +Common errors: + +- **"act not found"**: Install the `act` tool for local testing +- **Upload errors**: Check if the build produced the expected artifacts +- **Permission errors**: Check GitHub permissions and token access For technical issues with the release workflow, contact the CreamLinux maintainers. diff --git a/scripts/release.js b/scripts/release.js index b151389..f333746 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -5,12 +5,19 @@ * This script helps automate the release process by triggering the GitHub Actions workflow * * Usage: - * node scripts/release.js [patch|minor|major] [release notes] + * node scripts/release.js [patch|minor|major] [options] [release notes] + * + * Options: + * --dry-run Perform a dry run (no actual release) + * --local Test the workflow locally with act before running on GitHub + * --help Show help message * * Examples: * node scripts/release.js patch * node scripts/release.js minor "Added new feature X" * node scripts/release.js major "Major redesign with new UI" + * node scripts/release.js patch --dry-run + * node scripts/release.js minor --local */ import { execSync } from 'child_process' @@ -20,17 +27,36 @@ import path from 'path' // Configuration const GITHUB_WORKFLOW_NAME = 'release.yml' +// Check if act is installed for local testing +function isActInstalled() { + try { + execSync('act --version', { stdio: 'ignore' }) + return true + } catch (error) { + return false + } +} + // Validate environment -function validateEnvironment() { +function validateEnvironment(isLocal) { try { // Check if git is installed execSync('git --version', { stdio: 'ignore' }) - // Check if GitHub CLI is installed - execSync('gh --version', { stdio: 'ignore' }) + if (!isLocal) { + // Check if GitHub CLI is installed + execSync('gh --version', { stdio: 'ignore' }) - // Check if the user is authenticated with GitHub CLI - execSync('gh auth status', { stdio: 'ignore' }) + // Check if the user is authenticated with GitHub CLI + execSync('gh auth status', { stdio: 'ignore' }) + } else { + // Check if act is installed for local testing + if (!isActInstalled()) { + console.error('Error: "act" is required for local testing but is not installed.') + console.error('You can install it from: https://github.com/nektos/act') + process.exit(1) + } + } // Check if we're in a git repository execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' }) @@ -59,10 +85,9 @@ function validateEnvironment() { function parseArguments() { const args = process.argv.slice(2) - if (args.length === 0) { - console.error('Error: Missing version type argument') + if (args.length === 0 || args[0] === '--help') { showUsage() - process.exit(1) + process.exit(args[0] === '--help' ? 0 : 1) } const versionType = args[0].toLowerCase() @@ -72,38 +97,92 @@ function parseArguments() { process.exit(1) } - // Join remaining arguments as release notes - const releaseNotes = args.slice(1).join(' ') + // Extract options + const options = { + dryRun: args.includes('--dry-run'), + localTest: args.includes('--local'), + } - return { versionType, releaseNotes } + // Join remaining arguments as release notes (excluding options) + const releaseNotes = args + .slice(1) + .filter((arg) => !arg.startsWith('--')) + .join(' ') + + return { versionType, releaseNotes, options } } // Show usage information function showUsage() { - console.log('Usage: node scripts/release.js [patch|minor|major] [release notes]') + console.log('Usage: node scripts/release.js [patch|minor|major] [options] [release notes]') + console.log('') + console.log('Options:') + console.log(' --dry-run Perform a dry run (no actual release)') + console.log(' --local Test the workflow locally with act before running on GitHub') + console.log(' --help Show this help message') console.log('') console.log('Examples:') console.log(' node scripts/release.js patch') console.log(' node scripts/release.js minor "Added new feature X"') console.log(' node scripts/release.js major "Major redesign with new UI"') + console.log(' node scripts/release.js patch --dry-run') + console.log(' node scripts/release.js minor --local') +} + +// Run local test with act +function runLocalTest(versionType, releaseNotes) { + console.log('\n=== Running local workflow test with act ===') + + try { + const actCommand = `act workflow_dispatch -j version-bump -e ./scripts/test-event.json` + + // First create the test event JSON + const testEvent = { + inputs: { + version_type: versionType, + custom_release_notes: releaseNotes || '', + dry_run: true, + }, + } + + // Write the test event to a file + const fs = require('fs') + fs.writeFileSync('./scripts/test-event.json', JSON.stringify(testEvent, null, 2)) + + console.log('Running local test with act (this might take a while)...') + execSync(actCommand, { stdio: 'inherit' }) + + // Clean up + fs.unlinkSync('./scripts/test-event.json') + + console.log('\nāœ… Local test completed successfully!') + return true + } catch (error) { + console.error(`āŒ Local test failed: ${error.message}`) + return false + } } // Trigger GitHub workflow -function triggerWorkflow(versionType, releaseNotes) { +function triggerWorkflow(versionType, releaseNotes, isDryRun) { try { - console.log(`Triggering release workflow with version type: ${versionType}`) + console.log( + `Triggering release workflow with version type: ${versionType}${isDryRun ? ' (dry run)' : ''}` + ) - const command = releaseNotes - ? `gh workflow run ${GITHUB_WORKFLOW_NAME} -f version_type=${versionType} -f custom_release_notes="${releaseNotes}"` - : `gh workflow run ${GITHUB_WORKFLOW_NAME} -f version_type=${versionType}` + let command = `gh workflow run ${GITHUB_WORKFLOW_NAME} -f version_type=${versionType} -f dry_run=${isDryRun}` + + if (releaseNotes) { + command += ` -f custom_release_notes="${releaseNotes}"` + } execSync(command, { stdio: 'inherit' }) - console.log('\nRelease workflow triggered successfully!') + console.log('\nāœ… Release workflow triggered successfully!') console.log('You can check the progress in the Actions tab of your GitHub repository.') - console.log('https://github.com/Novattz/rust-gui-dev/actions') + console.log('https://github.com/yourusername/creamlinux/actions') } catch (error) { - console.error(`Failed to trigger workflow: ${error.message}`) + console.error(`āŒ Failed to trigger workflow: ${error.message}`) process.exit(1) } } @@ -112,23 +191,62 @@ function triggerWorkflow(versionType, releaseNotes) { function main() { console.log('=== CreamLinux Release Script ===\n') - validateEnvironment() - const { versionType, releaseNotes } = parseArguments() + const { versionType, releaseNotes, options } = parseArguments() - console.log(`Version type: ${versionType}`) + // Validate environment based on mode + validateEnvironment(options.localTest) + + console.log(`Version type: ${versionType} (${options.dryRun ? 'dry run' : 'actual release'})`) if (releaseNotes) { console.log(`Release notes: "${releaseNotes}"`) } else { console.log('Release notes: Auto-generated from commit messages') } - // Confirm with user - console.log('\nThis will trigger a GitHub Actions workflow to create a new release.') - console.log('Press Ctrl+C to cancel or wait 5 seconds to continue...') + if (options.localTest) { + // Run local test first + const success = runLocalTest(versionType, releaseNotes) - setTimeout(() => { - triggerWorkflow(versionType, releaseNotes) - }, 5000) + if (options.localTest) { + // Run local test first + const success = runLocalTest(versionType, releaseNotes) + + if (!success) { + console.error('\nLocal test failed. Fix the issues before running on GitHub.') + process.exit(1) + } + + // If it was just a local test, exit here + if (options.dryRun) { + console.log( + '\nLocal test completed successfully. Exiting without triggering GitHub workflow.' + ) + process.exit(0) + } + + // Ask if user wants to continue with actual GitHub workflow + console.log('\nLocal test passed! Do you want to trigger the actual GitHub workflow?') + console.log('Press Ctrl+C to cancel or wait 5 seconds to continue...') + + return new Promise((resolve) => { + setTimeout(() => { + triggerWorkflow(versionType, releaseNotes, options.dryRun) + resolve() + }, 5000) + }) + } else { + // No local test, just confirm and trigger workflow + console.log('\nThis will trigger a GitHub Actions workflow to create a new release.') + console.log('Press Ctrl+C to cancel or wait 5 seconds to continue...') + + return new Promise((resolve) => { + setTimeout(() => { + triggerWorkflow(versionType, releaseNotes, options.dryRun) + resolve() + }, 5000) + }) + } + } } main()