Skip to content

feat(cli-manager): add quota refresh and stabilize CI #6

feat(cli-manager): add quota refresh and stabilize CI

feat(cli-manager): add quota refresh and stabilize CI #6

Workflow file for this run

name: release
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: write
pull-requests: write
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
jobs:
fork-release-disabled:
if: github.repository != 'dyndynjyxa/aio-coding-hub'
runs-on: ubuntu-latest
steps:
- name: Skip release automation in forks
run: echo "Release automation is disabled outside dyndynjyxa/aio-coding-hub."
release-please:
if: github.repository == 'dyndynjyxa/aio-coding-hub'
runs-on: ubuntu-latest
outputs:
release_created: ${{ steps.release.outputs.release_created }}
tag_name: ${{ steps.release.outputs.tag_name }}
release_id: ${{ steps.release.outputs.id }}
build_matrix: ${{ steps.support_matrix.outputs.build_matrix }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
- name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 22
- name: Validate support matrix contract
run: node scripts/support-matrix.mjs check
- id: support_matrix
name: Load release support matrix
shell: bash
run: |
set -euo pipefail
echo "build_matrix=$(node scripts/support-matrix.mjs build-matrix)" >> "$GITHUB_OUTPUT"
- id: release
uses: googleapis/release-please-action@8b8fd2cc23b2e18957157a9d923d75aa0c6f6ad5 # v4
with:
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
target-branch: main
skip-github-pull-request: "${{ github.event_name == 'push' && startsWith(github.event.head_commit.message, 'chore(main): release aio-coding-hub ') }}"
token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
- name: Check release PR changelog range
if: steps.release.outputs.prs_created == 'true'
env:
RELEASE_PLEASE_PRS: ${{ steps.release.outputs.prs }}
GITHUB_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN }}
run: node scripts/check-release-pr-changelog.mjs
build:
needs: release-please
if: needs.release-please.outputs.release_created == 'true'
strategy:
fail-fast: false
matrix:
include: ${{ fromJson(needs.release-please.outputs.build_matrix) }}
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Install pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
- name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 22
cache: pnpm
- name: Install Rust 1.90.0
uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9
with:
toolchain: 1.90.0
components: rustfmt, clippy
targets: ${{ matrix.target }}
- name: Rust cache
uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2
with:
workspaces: src-tauri
- name: Install Linux dependencies
if: matrix.platform == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y libasound2-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
- name: Install deps
run: pnpm install --frozen-lockfile
- name: Prepare updater signing key (validate password)
shell: bash
run: |
set -euo pipefail
raw_key="${TAURI_SIGNING_PRIVATE_KEY_SECRET:-}"
raw_pwd="${TAURI_SIGNING_PRIVATE_KEY_PASSWORD_SECRET:-}"
if [[ -z "${raw_key//[[:space:]]/}" ]]; then
echo "Missing required secret: TAURI_SIGNING_PRIVATE_KEY" >&2
exit 1
fi
if [[ -z "$raw_pwd" ]]; then
echo "Missing required secret: TAURI_SIGNING_PRIVATE_KEY_PASSWORD" >&2
exit 1
fi
# Tauri v2 updater key is stored as a single-line base64 string (as generated by `tauri signer generate --ci`).
# Normalize by stripping whitespace to avoid accidental newline issues in GitHub Secrets.
key_b64="$(printf '%s' "$raw_key" | tr -d '\r\n\t ')"
printf '%s' "$key_b64" > tauri-updater.key
chmod 600 tauri-updater.key
echo "::add-mask::$raw_pwd"
echo "sign-test" > sign-test.txt
if ! pnpm tauri signer sign -f tauri-updater.key -p "$raw_pwd" sign-test.txt >/dev/null 2>sign-check.err; then
cat sign-check.err
echo "Signing key/password check failed." >&2
exit 1
fi
rm -f sign-test.txt.sig sign-check.err
echo "TAURI_SIGNING_PRIVATE_KEY=$key_b64" >> "$GITHUB_ENV"
echo "TAURI_SIGNING_PRIVATE_KEY_PASSWORD=$raw_pwd" >> "$GITHUB_ENV"
env:
TAURI_SIGNING_PRIVATE_KEY_SECRET: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD_SECRET: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
- id: tauri
name: Build & upload (Tauri)
uses: tauri-apps/tauri-action@fce9c6108b31ea247710505d3aaaa893ee6768d4 # v0
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN }}
with:
releaseId: ${{ needs.release-please.outputs.release_id }}
tagName: ${{ needs.release-please.outputs.tag_name }}
args: --target ${{ matrix.target }} --bundles ${{ matrix.bundles }}
# Avoid matrix race on the shared `latest.json` asset; we generate/upload it in a dedicated job.
includeUpdaterJson: false
- name: Prepare stable updater assets (+ macOS zip)
shell: bash
run: |
set -euo pipefail
node scripts/support-matrix.mjs prepare-stable-assets \
--artifact-paths "$ARTIFACT_PATHS_JSON" \
--updater-platform "${{ matrix.updater_platform }}" \
--stable-label "${{ matrix.stable_label }}" \
--output-dir stable-assets
env:
ARTIFACT_PATHS_JSON: ${{ steps.tauri.outputs.artifactPaths }}
- name: Build macOS zip (portable)
if: startsWith(matrix.updater_platform, 'darwin-')
shell: bash
run: |
set -euo pipefail
label="${{ matrix.stable_label }}"
tgz="stable-assets/aio-coding-hub-${label}.tar.gz"
if [[ ! -f "$tgz" ]]; then
echo "Missing tarball: $tgz"
ls -la stable-assets || true
exit 1
fi
tmp="$(mktemp -d)"
tar -xzf "$tgz" -C "$tmp"
app_path="$(find "$tmp" -maxdepth 2 -name "*.app" -type d | head -n 1 || true)"
if [[ -z "$app_path" ]]; then
echo "No .app found inside $tgz"
find "$tmp" -maxdepth 3 -print || true
exit 1
fi
out="stable-assets/aio-coding-hub-${label}.zip"
ditto -c -k --sequesterRsrc --keepParent "$app_path" "$out"
echo "Created zip: $out"
- name: Build Windows portable ZIP
if: startsWith(matrix.updater_platform, 'windows-')
shell: pwsh
run: |
$label = "${{ matrix.stable_label }}"
$targetDir = "src-tauri/target/${{ matrix.target }}/release"
# Find the built application exe (not the NSIS/MSI installer)
$exeName = "AIO Coding Hub.exe"
$exePath = Join-Path $targetDir $exeName
if (-not (Test-Path $exePath)) {
$exeName = "aio-coding-hub.exe"
$exePath = Join-Path $targetDir $exeName
}
if (-not (Test-Path $exePath)) {
Write-Error "Cannot find built exe in $targetDir"
Get-ChildItem $targetDir -Filter "*.exe" | ForEach-Object { Write-Host $_.FullName }
exit 1
}
# Create a temp directory for the portable package
$portableDir = "portable-build/AIO Coding Hub"
New-Item -ItemType Directory -Path $portableDir -Force | Out-Null
# Copy the exe
Copy-Item $exePath $portableDir
# Copy resources if they exist (icons, webview assets, etc.)
$resourceDir = Join-Path $targetDir "resources"
if (Test-Path $resourceDir) {
Copy-Item -Recurse $resourceDir (Join-Path $portableDir "resources")
}
# Create the ZIP
$zipPath = "stable-assets/aio-coding-hub-${label}-portable.zip"
Compress-Archive -Path "$portableDir/*" -DestinationPath $zipPath -Force
Write-Host "Created portable ZIP: $zipPath"
Get-Item $zipPath | Select-Object Name, Length
- name: Build Wayland-compatible AppImage (Linux only)
if: startsWith(matrix.updater_platform, 'linux-')
shell: bash
run: |
set -euo pipefail
# Download pinned stable release of appimagetool (no FUSE required).
TOOL_URL="https://github.com/AppImage/appimagetool/releases/download/1.9.1/appimagetool-x86_64.AppImage"
TOOL_BIN="$(mktemp)"
curl -fsSL "$TOOL_URL" -o "$TOOL_BIN"
chmod +x "$TOOL_BIN"
label="${{ matrix.stable_label }}"
src="stable-assets/aio-coding-hub-${label}.AppImage"
out="stable-assets/aio-coding-hub-${label}-wayland.AppImage"
APPIMAGETOOL="$TOOL_BIN" ./scripts/repack-linux-appimage-wayland.sh "$src" "$out"
rm -f "$TOOL_BIN"
echo "Wayland AppImage: $out"
ls -lh "$out"
- name: Upload stable assets (workflow artifact)
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: stable-assets-${{ matrix.updater_platform }}
path: stable-assets/*
if-no-files-found: error
- name: Upload stable assets (GitHub Release)
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN }}
with:
tag_name: ${{ needs.release-please.outputs.tag_name }}
files: stable-assets/*
fail_on_unmatched_files: true
assemble-latest-json:
needs: [release-please, build]
if: needs.release-please.outputs.release_created == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 22
- name: Download stable assets (workflow artifacts)
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
pattern: stable-assets-*
merge-multiple: true
path: stable-assets
- name: Fetch release body
id: release_body
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
with:
github-token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
script: |
const releaseId = Number('${{ needs.release-please.outputs.release_id }}');
if (!Number.isFinite(releaseId) || releaseId <= 0) {
core.setFailed(`Invalid release_id: ${releaseId}`);
return;
}
const { data: release } = await github.rest.repos.getRelease({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: releaseId,
});
core.setOutput('body', release.body || '');
- name: Generate latest.json
shell: bash
run: |
set -euo pipefail
node scripts/support-matrix.mjs generate-latest-json \
--tag "${{ needs.release-please.outputs.tag_name }}" \
--repo "${{ github.repository }}" \
--pub-date "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
--stable-assets-dir stable-assets \
--output latest.json
env:
RELEASE_BODY: ${{ steps.release_body.outputs.body }}
FALLBACK_NOTES: "See release: https://github.com/${{ github.repository }}/releases/tag/${{ needs.release-please.outputs.tag_name }}"
- name: Delete existing latest.json (best-effort)
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
with:
github-token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
script: |
const releaseId = Number('${{ needs.release-please.outputs.release_id }}');
if (!Number.isFinite(releaseId) || releaseId <= 0) {
core.setFailed(`Invalid release_id: ${releaseId}`);
return;
}
const owner = context.repo.owner;
const repo = context.repo.repo;
const assets = await github.paginate(github.rest.repos.listReleaseAssets, {
owner,
repo,
release_id: releaseId,
per_page: 100,
});
const existing = assets.find((a) => a && a.name === 'latest.json');
if (!existing) {
core.info('No existing latest.json asset found.');
return;
}
core.info(`Deleting existing latest.json asset id=${existing.id}`);
await github.rest.repos.deleteReleaseAsset({
owner,
repo,
asset_id: existing.id,
});
- name: Upload latest.json (GitHub Release)
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN }}
with:
tag_name: ${{ needs.release-please.outputs.tag_name }}
files: latest.json
fail_on_unmatched_files: true
publish:
needs: [release-please, build, assemble-latest-json]
if: needs.release-please.outputs.release_created == 'true'
runs-on: ubuntu-latest
steps:
- name: Publish GitHub Release (unset draft)
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
with:
github-token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
script: |
const releaseId = Number('${{ needs.release-please.outputs.release_id }}');
if (!Number.isFinite(releaseId) || releaseId <= 0) {
core.setFailed(`Invalid release_id: ${releaseId}`);
return;
}
await github.rest.repos.updateRelease({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: releaseId,
draft: false,
});