mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
- Included a new step to install dependencies for the skills directory in the GitHub Actions release workflow to ensure all necessary packages are available for the build process.
351 lines
12 KiB
YAML
351 lines
12 KiB
YAML
name: Release
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
release_type:
|
|
description: Version increment type
|
|
required: true
|
|
type: choice
|
|
options:
|
|
- patch
|
|
- minor
|
|
- major
|
|
|
|
permissions:
|
|
contents: write
|
|
|
|
concurrency:
|
|
group: release-main
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
prepare-release:
|
|
name: Prepare release commit and tag
|
|
runs-on: ubuntu-latest
|
|
environment: Production
|
|
outputs:
|
|
version: ${{ steps.bump.outputs.version }}
|
|
tag: ${{ steps.bump.outputs.tag }}
|
|
sha: ${{ steps.push.outputs.sha }}
|
|
steps:
|
|
- name: Enforce main branch
|
|
if: github.ref != 'refs/heads/main'
|
|
run: |
|
|
echo "This workflow can only run from main. Current ref: $GITHUB_REF"
|
|
exit 1
|
|
|
|
- name: Generate GitHub App token
|
|
id: app-token
|
|
uses: tibdex/github-app-token@v1
|
|
with:
|
|
app_id: ${{ secrets.XGITHUB_APP_ID }}
|
|
private_key: ${{ secrets.XGITHUB_APP_PRIVATE_KEY }}
|
|
|
|
- name: Checkout main
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: main
|
|
fetch-depth: 0
|
|
token: ${{ steps.app-token.outputs.token }}
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 24.x
|
|
|
|
- name: Configure Git
|
|
env:
|
|
APP_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
run: |
|
|
git config user.name "github-actions[bot]"
|
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
git remote set-url origin https://${APP_TOKEN}@github.com/${GITHUB_REPOSITORY}.git
|
|
git fetch origin --tags
|
|
git checkout main
|
|
git pull origin main --ff-only
|
|
|
|
- name: Compute next version and sync release files
|
|
id: bump
|
|
env:
|
|
RELEASE_TYPE: ${{ inputs.release_type }}
|
|
run: |
|
|
node <<'NODE'
|
|
const fs = require('fs');
|
|
|
|
const releaseType = process.env.RELEASE_TYPE;
|
|
const allowed = new Set(['patch', 'minor', 'major']);
|
|
if (!allowed.has(releaseType)) {
|
|
throw new Error(`Invalid release_type: ${releaseType}`);
|
|
}
|
|
|
|
const packagePath = 'package.json';
|
|
const tauriPath = 'src-tauri/tauri.conf.json';
|
|
const cargoPath = 'src-tauri/Cargo.toml';
|
|
|
|
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
const match = String(pkg.version || '').match(/^(\d+)\.(\d+)\.(\d+)$/);
|
|
if (!match) {
|
|
throw new Error(`package.json version must be SemVer X.Y.Z, found: ${pkg.version}`);
|
|
}
|
|
|
|
let major = Number(match[1]);
|
|
let minor = Number(match[2]);
|
|
let patch = Number(match[3]);
|
|
|
|
if (releaseType === 'major') {
|
|
major += 1;
|
|
minor = 0;
|
|
patch = 0;
|
|
} else if (releaseType === 'minor') {
|
|
minor += 1;
|
|
patch = 0;
|
|
} else {
|
|
patch += 1;
|
|
}
|
|
|
|
const nextVersion = `${major}.${minor}.${patch}`;
|
|
|
|
pkg.version = nextVersion;
|
|
fs.writeFileSync(packagePath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
|
|
const tauri = JSON.parse(fs.readFileSync(tauriPath, 'utf8'));
|
|
tauri.version = nextVersion;
|
|
fs.writeFileSync(tauriPath, `${JSON.stringify(tauri, null, 2)}\n`);
|
|
|
|
const cargo = fs.readFileSync(cargoPath, 'utf8');
|
|
const updatedCargo = cargo.replace(
|
|
/(\[package\][\s\S]*?^version\s*=\s*")([^"]+)(")/m,
|
|
`$1${nextVersion}$3`,
|
|
);
|
|
if (updatedCargo === cargo) {
|
|
throw new Error('Failed to update [package].version in src-tauri/Cargo.toml');
|
|
}
|
|
fs.writeFileSync(cargoPath, updatedCargo);
|
|
|
|
fs.appendFileSync(process.env.GITHUB_OUTPUT, `version=${nextVersion}\n`);
|
|
fs.appendFileSync(process.env.GITHUB_OUTPUT, `tag=v${nextVersion}\n`);
|
|
NODE
|
|
|
|
- name: Ensure tag does not already exist
|
|
env:
|
|
TAG: ${{ steps.bump.outputs.tag }}
|
|
run: |
|
|
if git rev-parse "$TAG" >/dev/null 2>&1; then
|
|
echo "Tag already exists locally: $TAG"
|
|
exit 1
|
|
fi
|
|
|
|
if git ls-remote --tags origin "refs/tags/$TAG" | grep -q .; then
|
|
echo "Tag already exists on origin: $TAG"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Commit, push and tag
|
|
id: push
|
|
env:
|
|
VERSION: ${{ steps.bump.outputs.version }}
|
|
TAG: ${{ steps.bump.outputs.tag }}
|
|
run: |
|
|
git add package.json src-tauri/tauri.conf.json src-tauri/Cargo.toml
|
|
git commit -m "chore(release): v${VERSION}"
|
|
git push origin main
|
|
|
|
git tag -a "$TAG" -m "Release $TAG"
|
|
git push origin "$TAG"
|
|
|
|
echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
|
|
|
create-release:
|
|
name: Create GitHub release
|
|
runs-on: ubuntu-latest
|
|
environment: Production
|
|
needs: prepare-release
|
|
outputs:
|
|
release_id: ${{ steps.create.outputs.release_id }}
|
|
upload_url: ${{ steps.create.outputs.upload_url }}
|
|
steps:
|
|
- name: Create published release with generated notes
|
|
id: create
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
const tag = '${{ needs.prepare-release.outputs.tag }}';
|
|
const version = '${{ needs.prepare-release.outputs.version }}';
|
|
const target = '${{ needs.prepare-release.outputs.sha }}';
|
|
|
|
const { owner, repo } = context.repo;
|
|
|
|
try {
|
|
await github.rest.repos.getReleaseByTag({ owner, repo, tag });
|
|
core.setFailed(`Release already exists for ${tag}`);
|
|
return;
|
|
} catch (error) {
|
|
if (error.status !== 404) {
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
const release = await github.rest.repos.createRelease({
|
|
owner,
|
|
repo,
|
|
tag_name: tag,
|
|
target_commitish: target,
|
|
name: `OpenHuman v${version}`,
|
|
draft: false,
|
|
prerelease: false,
|
|
generate_release_notes: true,
|
|
});
|
|
|
|
core.setOutput('release_id', String(release.data.id));
|
|
core.setOutput('upload_url', release.data.upload_url);
|
|
|
|
build-artifacts:
|
|
name: Build and upload artifacts
|
|
needs: [prepare-release, create-release]
|
|
runs-on: ${{ matrix.settings.platform }}
|
|
environment: Production
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
settings:
|
|
- platform: macos-latest
|
|
args: --target aarch64-apple-darwin
|
|
- platform: macos-latest
|
|
args: --target x86_64-apple-darwin
|
|
- platform: ubuntu-22.04
|
|
args: ""
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
steps:
|
|
- name: Checkout tag
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ needs.prepare-release.outputs.tag }}
|
|
fetch-depth: 1
|
|
submodules: true
|
|
|
|
- name: Set Xcode version
|
|
if: matrix.settings.platform == 'macos-latest'
|
|
uses: maxim-lobanov/setup-xcode@v1
|
|
with:
|
|
xcode-version: latest-stable
|
|
|
|
- name: Setup Node.js 24.x
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 24.x
|
|
cache: yarn
|
|
|
|
- name: Install Rust stable
|
|
uses: dtolnay/rust-toolchain@stable
|
|
with:
|
|
targets: ${{ matrix.settings.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
|
|
|
|
- name: Install Tauri dependencies (ubuntu only)
|
|
if: matrix.settings.platform == 'ubuntu-22.04'
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
|
|
|
- name: Install dependencies
|
|
run: yarn install --frozen-lockfile
|
|
|
|
- name: Install skills dependencies
|
|
run: cd skills && yarn install --frozen-lockfile
|
|
|
|
- name: Build skills
|
|
run: cd skills && yarn build
|
|
|
|
- name: Define Tauri configuration overrides
|
|
id: config-overrides
|
|
uses: actions/github-script@v7
|
|
env:
|
|
BASE_URL: ${{ vars.BASE_URL }}
|
|
UPDATER_PUBLIC_KEY: ${{ secrets.UPDATER_PUBLIC_KEY }}
|
|
WITH_UPDATER: "true"
|
|
with:
|
|
script: |
|
|
const workspacePath = process.env.GITHUB_WORKSPACE.replace(/\\/g, '/');
|
|
const prefix = workspacePath.startsWith('/') ? 'file://' : 'file:///';
|
|
const moduleUrl = `${prefix}${workspacePath}/scripts/prepareTauriConfig.js`;
|
|
const { default: prepareTauriConfig } = await import(moduleUrl);
|
|
const config = prepareTauriConfig();
|
|
core.setOutput('json', JSON.stringify(config));
|
|
|
|
- name: Build frontend
|
|
run: yarn build
|
|
env:
|
|
NODE_ENV: production
|
|
VITE_BACKEND_URL: ${{ vars.VITE_BACKEND_URL }}
|
|
VITE_SENTRY_DSN: ${{ vars.VITE_SENTRY_DSN }}
|
|
VITE_DEBUG: ${{ vars.VITE_DEBUG }}
|
|
|
|
- name: Build, package and upload to release
|
|
uses: tauri-apps/tauri-action@v0.6.2
|
|
env:
|
|
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
|
|
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
|
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
|
APPLE_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
|
BASE_URL: ${{ vars.BASE_URL }}
|
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
|
WITH_UPDATER: "true"
|
|
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.settings.platform == 'macos-latest' && '10.15' || '' }}
|
|
with:
|
|
args: -c ${{ steps.config-overrides.outputs.json }} ${{ matrix.settings.args }}
|
|
includeDebug: false
|
|
includeRelease: true
|
|
releaseId: ${{ needs.create-release.outputs.release_id }}
|
|
owner: tinyhumansai
|
|
repo: openhuman
|
|
|
|
cleanup-failed-release:
|
|
name: Remove release and tag if build failed
|
|
runs-on: ubuntu-latest
|
|
environment: Production
|
|
needs: [prepare-release, create-release, build-artifacts]
|
|
if: always() && needs.create-release.result == 'success' && (needs.build-artifacts.result == 'failure' || needs.build-artifacts.result == 'cancelled')
|
|
steps:
|
|
- name: Delete GitHub release
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
const owner = context.repo.owner;
|
|
const repo = context.repo.repo;
|
|
const releaseId = Number('${{ needs.create-release.outputs.release_id }}');
|
|
|
|
if (!Number.isFinite(releaseId) || releaseId <= 0) {
|
|
core.setFailed('Invalid or missing release_id; cannot delete release.');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await github.rest.repos.deleteRelease({ owner, repo, release_id: releaseId });
|
|
core.info(`Deleted release ${releaseId}`);
|
|
} catch (e) {
|
|
core.warning(`deleteRelease failed: ${e.message}`);
|
|
}
|
|
|
|
- name: Delete remote tag
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
const owner = context.repo.owner;
|
|
const repo = context.repo.repo;
|
|
const tag = '${{ needs.prepare-release.outputs.tag }}';
|
|
|
|
try {
|
|
await github.rest.git.deleteRef({ owner, repo, ref: `tags/${tag}` });
|
|
core.info(`Deleted remote tag ${tag}`);
|
|
} catch (e) {
|
|
if (e.status === 404) {
|
|
core.info(`Tag ${tag} already absent on remote`);
|
|
} else {
|
|
throw e;
|
|
}
|
|
}
|