mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
* fix(release): sync root Cargo version in bump flow (#449) Add root Cargo.toml to release version bumping and introduce a version-sync verifier for all four release version sources. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * ci(release): enforce version sync before tagging (#449) Run release version consistency verification and include root Cargo.toml in the release commit staging list. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore(release): add local dmg preflight version dry-run (#449) Add a local release dry-run script that builds frontend + release sidecar, bundles app/dmg, and validates packaged core.version. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
dc5e7adeb6
commit
4639913ddf
@@ -95,6 +95,9 @@ jobs:
|
||||
id: bump
|
||||
run: node scripts/release/bump-version.js "${{ inputs.release_type }}"
|
||||
|
||||
- name: Verify release version sync
|
||||
run: node scripts/release/verify-version-sync.js "${{ steps.bump.outputs.version }}"
|
||||
|
||||
- name: Ensure tag does not already exist
|
||||
env:
|
||||
TAG: ${{ steps.bump.outputs.tag }}
|
||||
@@ -115,7 +118,7 @@ jobs:
|
||||
VERSION: ${{ steps.bump.outputs.version }}
|
||||
TAG: ${{ steps.bump.outputs.tag }}
|
||||
run: |
|
||||
git add app/package.json app/src-tauri/tauri.conf.json app/src-tauri/Cargo.toml
|
||||
git add app/package.json app/src-tauri/tauri.conf.json app/src-tauri/Cargo.toml Cargo.toml
|
||||
git commit -m "chore(release): v${VERSION}"
|
||||
git push origin main
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "openhuman"
|
||||
version = "0.49.17"
|
||||
version = "0.51.19"
|
||||
edition = "2021"
|
||||
description = "OpenHuman core business logic and RPC server"
|
||||
autobins = false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
// Bump version in package.json, tauri.conf.json, and Cargo.toml.
|
||||
// Bump version in package.json, tauri.conf.json, and both Cargo.toml manifests.
|
||||
//
|
||||
// Usage:
|
||||
// node scripts/release/bump-version.js <patch|minor|major>
|
||||
@@ -25,7 +25,8 @@ if (!allowed.has(RELEASE_TYPE)) {
|
||||
const root = path.resolve(__dirname, '..', '..');
|
||||
const packagePath = path.join(root, 'app/package.json');
|
||||
const tauriPath = path.join(root, 'app/src-tauri/tauri.conf.json');
|
||||
const cargoPath = path.join(root, 'app/src-tauri/Cargo.toml');
|
||||
const tauriCargoPath = path.join(root, 'app/src-tauri/Cargo.toml');
|
||||
const coreCargoPath = path.join(root, 'Cargo.toml');
|
||||
|
||||
// ── Read current version ────────────────────────────────────────────────────
|
||||
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
||||
@@ -57,16 +58,21 @@ const tauri = JSON.parse(fs.readFileSync(tauriPath, 'utf8'));
|
||||
tauri.version = nextVersion;
|
||||
fs.writeFileSync(tauriPath, `${JSON.stringify(tauri, null, 2)}\n`);
|
||||
|
||||
// ── Write Cargo.toml ────────────────────────────────────────────────────────
|
||||
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 app/src-tauri/Cargo.toml');
|
||||
function bumpCargoVersion(filePath, nextVersion) {
|
||||
const cargo = fs.readFileSync(filePath, '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 ${path.relative(root, filePath)}`);
|
||||
}
|
||||
fs.writeFileSync(filePath, updatedCargo);
|
||||
}
|
||||
fs.writeFileSync(cargoPath, updatedCargo);
|
||||
|
||||
// ── Write Cargo.toml files ──────────────────────────────────────────────────
|
||||
bumpCargoVersion(tauriCargoPath, nextVersion);
|
||||
bumpCargoVersion(coreCargoPath, nextVersion);
|
||||
|
||||
// ── Output ──────────────────────────────────────────────────────────────────
|
||||
const lines = `version=${nextVersion}\ntag=v${nextVersion}\n`;
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
APP_DIR="$REPO_ROOT/app"
|
||||
APP_BUNDLE="$REPO_ROOT/app/src-tauri/target/release/bundle/macos/OpenHuman.app"
|
||||
DMG_DIR="$REPO_ROOT/app/src-tauri/target/release/bundle/dmg"
|
||||
TEMP_ENV_CREATED=0
|
||||
TMP_TAURI_CONF=""
|
||||
|
||||
cleanup() {
|
||||
if [[ "$TEMP_ENV_CREATED" -eq 1 ]]; then
|
||||
rm -f "$REPO_ROOT/.env"
|
||||
fi
|
||||
if [[ -n "$TMP_TAURI_CONF" ]]; then
|
||||
rm -f "$TMP_TAURI_CONF"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
echo "[dry-run] Verifying release version files are synced"
|
||||
node "$REPO_ROOT/scripts/release/verify-version-sync.js"
|
||||
|
||||
EXPECTED_VERSION="$(
|
||||
node -e 'const fs=require("fs");const p=JSON.parse(fs.readFileSync(process.argv[1], "utf8"));process.stdout.write(p.version);' \
|
||||
"$APP_DIR/package.json"
|
||||
)"
|
||||
echo "[dry-run] Expected version: $EXPECTED_VERSION"
|
||||
|
||||
if [[ ! -f "$REPO_ROOT/.env" ]]; then
|
||||
if [[ -f "$REPO_ROOT/.env.example" ]]; then
|
||||
cp "$REPO_ROOT/.env.example" "$REPO_ROOT/.env"
|
||||
TEMP_ENV_CREATED=1
|
||||
echo "[dry-run] Created temporary .env from .env.example for local build"
|
||||
else
|
||||
echo "[dry-run] ERROR: missing .env and .env.example at repo root" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
HOST_TRIPLE="$(rustc -vV | awk '/^host:/ {print $2}')"
|
||||
|
||||
echo "[dry-run] Building frontend bundle"
|
||||
(
|
||||
cd "$APP_DIR"
|
||||
npm run build:app
|
||||
)
|
||||
|
||||
echo "[dry-run] Building release openhuman-core for $HOST_TRIPLE"
|
||||
cargo build --release --manifest-path "$REPO_ROOT/Cargo.toml" --bin openhuman-core
|
||||
|
||||
echo "[dry-run] Staging sidecar"
|
||||
bash "$REPO_ROOT/scripts/release/stage-sidecar.sh" \
|
||||
"$HOST_TRIPLE" \
|
||||
"target/release" \
|
||||
"openhuman-core" \
|
||||
"openhuman-core"
|
||||
|
||||
TMP_TAURI_CONF="$(mktemp "${TMPDIR:-/tmp}/openhuman-tauri-dry-run.XXXXXX").json"
|
||||
node -e '
|
||||
const fs = require("fs");
|
||||
const input = process.argv[1];
|
||||
const output = process.argv[2];
|
||||
const config = JSON.parse(fs.readFileSync(input, "utf8"));
|
||||
config.build = config.build || {};
|
||||
config.build.beforeBuildCommand = "echo \"[dry-run] beforeBuildCommand handled externally\"";
|
||||
fs.writeFileSync(output, `${JSON.stringify(config, null, 2)}\n`);
|
||||
' "$APP_DIR/src-tauri/tauri.conf.json" "$TMP_TAURI_CONF"
|
||||
|
||||
echo "[dry-run] Building local DMG with staged sidecar"
|
||||
(
|
||||
cd "$APP_DIR"
|
||||
source ../scripts/load-dotenv.sh
|
||||
yarn tauri build --bundles app,dmg --config "$TMP_TAURI_CONF"
|
||||
)
|
||||
|
||||
if [[ ! -d "$APP_BUNDLE" ]]; then
|
||||
echo "[dry-run] ERROR: app bundle not found at $APP_BUNDLE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -d "$DMG_DIR" ]]; then
|
||||
echo "[dry-run] ERROR: DMG directory not found at $DMG_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DMG_PATH="$(find "$DMG_DIR" -maxdepth 1 -type f -name '*.dmg' | sort | tail -n 1)"
|
||||
if [[ -z "$DMG_PATH" ]]; then
|
||||
echo "[dry-run] ERROR: no DMG artifact produced in $DMG_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CORE_BIN="$(
|
||||
find "$APP_BUNDLE/Contents" -maxdepth 4 -type f -name 'openhuman-core*' ! -name '*.sig' | head -n 1
|
||||
)"
|
||||
if [[ -z "$CORE_BIN" ]]; then
|
||||
echo "[dry-run] ERROR: packaged openhuman-core binary not found in app bundle" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CORE_VERSION_OUTPUT="$("$CORE_BIN" call --method core.version)"
|
||||
if ! grep -q "\"version\": \"$EXPECTED_VERSION\"" <<<"$CORE_VERSION_OUTPUT"; then
|
||||
echo "[dry-run] ERROR: packaged core version does not match expected version" >&2
|
||||
echo "[dry-run] core output: $CORE_VERSION_OUTPUT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[dry-run] PASS"
|
||||
echo "[dry-run] DMG: $DMG_PATH"
|
||||
echo "[dry-run] Core binary: $CORE_BIN"
|
||||
echo "[dry-run] Core version output: $CORE_VERSION_OUTPUT"
|
||||
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env node
|
||||
// Verify release version consistency across all authoritative files.
|
||||
//
|
||||
// Usage:
|
||||
// node scripts/release/verify-version-sync.js [expected-version]
|
||||
//
|
||||
// If expected-version is provided, every source must match it.
|
||||
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const root = path.resolve(__dirname, '..', '..');
|
||||
const expectedVersion = process.argv[2] || process.env.EXPECTED_VERSION || null;
|
||||
|
||||
function readJsonVersion(filePath, field = 'version') {
|
||||
const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
const value = data[field];
|
||||
if (!value || typeof value !== 'string') {
|
||||
throw new Error(`Missing string "${field}" in ${path.relative(root, filePath)}`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function readCargoPackageVersion(filePath) {
|
||||
const cargo = fs.readFileSync(filePath, 'utf8');
|
||||
const match = cargo.match(/^\[package\][\s\S]*?^version\s*=\s*"([^"]+)"/m);
|
||||
if (!match) {
|
||||
throw new Error(`Failed to read [package].version in ${path.relative(root, filePath)}`);
|
||||
}
|
||||
return match[1];
|
||||
}
|
||||
|
||||
const versions = {
|
||||
'app/package.json': readJsonVersion(path.join(root, 'app/package.json')),
|
||||
'app/src-tauri/tauri.conf.json': readJsonVersion(path.join(root, 'app/src-tauri/tauri.conf.json')),
|
||||
'app/src-tauri/Cargo.toml': readCargoPackageVersion(path.join(root, 'app/src-tauri/Cargo.toml')),
|
||||
'Cargo.toml': readCargoPackageVersion(path.join(root, 'Cargo.toml')),
|
||||
};
|
||||
|
||||
const values = Object.values(versions);
|
||||
const unique = [...new Set(values)];
|
||||
|
||||
if (expectedVersion && values.some((value) => value !== expectedVersion)) {
|
||||
console.error('[verify-version-sync] Expected version mismatch.');
|
||||
for (const [file, version] of Object.entries(versions)) {
|
||||
console.error(` ${file}: ${version}`);
|
||||
}
|
||||
console.error(` expected: ${expectedVersion}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (unique.length !== 1) {
|
||||
console.error('[verify-version-sync] Version mismatch detected.');
|
||||
for (const [file, version] of Object.entries(versions)) {
|
||||
console.error(` ${file}: ${version}`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`[verify-version-sync] OK ${unique[0]}`);
|
||||
Reference in New Issue
Block a user