chore: update workflows and configuration

- Added `tauri.key` and `tauri.key.pub` to .gitignore.
- Removed Telegram-related environment variables from CLAUDE.md and documentation.
- Updated GitHub Actions workflows to use the latest version of `tauri-action`.
- Deleted legacy version bump workflow and added a new changelog update workflow.
- Introduced a script for testing release workflows locally.
This commit is contained in:
Steven Enamakel
2026-03-26 14:57:29 -07:00
parent 036e91c45f
commit 2b157615a6
11 changed files with 153 additions and 278 deletions
+1 -1
View File
@@ -291,7 +291,7 @@ jobs:
VITE_DEBUG: ${{ vars.VITE_DEBUG }}
- name: Build, package and publish
uses: tauri-apps/tauri-action@v0
uses: tauri-apps/tauri-action@v1
id: build-tauri
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
-176
View File
@@ -1,176 +0,0 @@
name: PR Protection - Main Branch
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
pull-requests: write
contents: read
jobs:
check-pr-protection:
runs-on: ubuntu-latest
steps:
- name: Check PR target branch
id: check-target
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const pr = context.payload.pull_request;
const baseBranch = pr.base.ref;
const headBranch = pr.head.ref;
const prAuthor = pr.user.login;
const prTitle = pr.title;
const prBody = pr.body || '';
// Check if PR is targeting main
const isTargetingMain = baseBranch === 'main';
if (!isTargetingMain) {
console.log(`PR is targeting ${baseBranch}, not main. Skipping protection check.`);
core.setOutput('needs_check', 'false');
return;
}
console.log(`PR #${pr.number} is targeting ${baseBranch}`);
console.log(`PR author: ${prAuthor}`);
console.log(`PR title: ${prTitle}`);
console.log(`Head branch: ${headBranch}`);
// Check if PR title follows conventional commit format
// Allowed prefixes: feat, fix, chore, refactor, docs, test, style, perf, revert
const conventionalCommitPattern = /^(feat|fix|chore|refactor|docs|test|style|perf|revert)(\(.+\))?: .+/;
const followsConventionalFormat = conventionalCommitPattern.test(prTitle);
console.log(`Conventional commit format check: ${followsConventionalFormat ? 'PASSED' : 'FAILED'}`);
if (!followsConventionalFormat) {
console.log(`PR title must follow conventional commit format: <type>: <description>`);
console.log(`Allowed types: feat, fix, chore, refactor, docs, test, style, perf, revert`);
}
// Check if this is the automated PR from create-develop-to-main-pr workflow
// The automated PR has these characteristics:
// 1. Created by github-actions[bot]
// 2. From develop branch to main
// 3. Title matches pattern: "chore: merge develop into main (vX.Y.Z)"
// 4. Body contains "This PR automatically merges changes from develop into main"
// Validate PR title matches expected pattern: "chore: merge develop into main" followed by optional version
const expectedTitlePattern = /^chore: merge develop into main( \(v\d+\.\d+\.\d+\))?$/;
const titleMatches = expectedTitlePattern.test(prTitle);
console.log(`Automated PR title validation: ${titleMatches ? 'PASSED' : 'FAILED'}`);
console.log(`Expected pattern: "chore: merge develop into main" or "chore: merge develop into main (vX.Y.Z)"`);
const isAutomatedPR =
prAuthor === 'github-actions[bot]' &&
headBranch === 'develop' &&
titleMatches &&
prBody.includes('This PR automatically merges changes from develop into main');
if (isAutomatedPR) {
console.log('This PR is from the automated workflow. Allowing it.');
core.setOutput('needs_check', 'false');
core.setOutput('is_allowed', 'true');
} else {
console.log('This PR is NOT from the automated workflow.');
// Even if not automated, check if title follows conventional commit format
if (!followsConventionalFormat) {
console.log('PR title does not follow conventional commit format. Blocking it.');
core.setOutput('needs_check', 'true');
core.setOutput('is_allowed', 'false');
core.setOutput('conventional_format_failed', 'true');
} else {
console.log('PR title follows conventional commit format, but PR is not from automated workflow. Blocking it.');
core.setOutput('needs_check', 'true');
core.setOutput('is_allowed', 'false');
core.setOutput('conventional_format_failed', 'false');
}
}
// Store the conventional format check result
core.setOutput('conventional_format', followsConventionalFormat ? 'true' : 'false');
- name: Block unauthorized PR to main
if: steps.check-target.outputs.needs_check == 'true' && steps.check-target.outputs.is_allowed == 'false'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const pr = context.payload.pull_request;
const baseBranch = pr.base.ref;
const prAuthor = pr.user.login;
const prTitle = pr.title;
const headBranch = pr.head.ref;
const prBody = pr.body || '';
// Check which validation criteria failed
const conventionalCommitPattern = /^(feat|fix|chore|refactor|docs|test|style|perf|revert)(\(.+\))?: .+/;
const followsConventionalFormat = conventionalCommitPattern.test(prTitle);
const checks = {
author: prAuthor === 'github-actions[bot]',
branch: headBranch === 'develop',
automatedTitle: /^chore: merge develop into main( \(v\d+\.\d+\.\d+\))?$/.test(prTitle),
body: prBody.includes('This PR automatically merges changes from develop into main'),
conventionalFormat: followsConventionalFormat
};
const failedChecks = Object.entries(checks)
.filter(([_, passed]) => !passed)
.map(([check]) => check);
console.log('Validation checks:', JSON.stringify(checks, null, 2));
console.log('Failed checks:', failedChecks.join(', '));
// Build detailed error message
let errorDetails = '';
if (failedChecks.length > 0) {
errorDetails = `\n\n**Failed validation checks:**\n`;
if (!checks.author) errorDetails += `- ❌ Author must be \`github-actions[bot]\` (found: \`${prAuthor}\`)\n`;
if (!checks.branch) errorDetails += `- ❌ Head branch must be \`develop\` (found: \`${headBranch}\`)\n`;
if (!checks.automatedTitle) errorDetails += `- ❌ Title must match pattern: \`chore: merge develop into main\` or \`chore: merge develop into main (vX.Y.Z)\` (found: \`${prTitle}\`)\n`;
if (!checks.body) errorDetails += `- ❌ Body must contain: "This PR automatically merges changes from develop into main"\n`;
if (!checks.conventionalFormat) errorDetails += `- ❌ PR title must follow conventional commit format: \`<type>: <description>\`\n`;
if (!checks.conventionalFormat) errorDetails += ` Allowed types: \`feat\`, \`fix\`, \`chore\`, \`refactor\`, \`docs\`, \`test\`, \`style\`, \`perf\`, \`revert\`\n`;
if (!checks.conventionalFormat) errorDetails += ` Example: \`feat: add new authentication endpoint\`\n`;
}
// Comment on the PR explaining why it was blocked
const codeBlock = '`';
const commentBody = [
'## PR Protection Rule Violation',
'',
`This PR targets the ${codeBlock}${baseBranch}${codeBlock} branch, which is protected.`,
'',
`**Only automated PRs from the ${codeBlock}create-develop-to-main-pr.yml${codeBlock} workflow are allowed to target ${codeBlock}${baseBranch}${codeBlock}.**${errorDetails}`,
'',
`**To merge changes into ${codeBlock}${baseBranch}${codeBlock}:**`,
`1. Create your PR targeting the ${codeBlock}develop${codeBlock} branch instead`,
`2. Once merged to ${codeBlock}develop${codeBlock}, the automated workflow will create a PR from ${codeBlock}develop${codeBlock} to ${codeBlock}${baseBranch}${codeBlock}`,
'',
`Please update this PR to target ${codeBlock}develop${codeBlock} instead, or close it and create a new PR targeting ${codeBlock}develop${codeBlock}.`
].join('\n');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: commentBody
});
// Fail the workflow to block the PR
let failureMessage = `PR #${pr.number} cannot target ${baseBranch} branch. `;
if (!checks.conventionalFormat) {
failureMessage += `PR title does not follow conventional commit format. `;
}
failureMessage += `Failed checks: ${failedChecks.join(', ')}. `;
failureMessage += `Only automated PRs from create-develop-to-main-pr.yml workflow are allowed.`;
core.setFailed(failureMessage);
+3 -3
View File
@@ -277,7 +277,7 @@ jobs:
VITE_DEBUG: ${{ vars.VITE_DEBUG }}
- name: Build, package and upload to release
uses: tauri-apps/tauri-action@v0
uses: tauri-apps/tauri-action@v1
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
@@ -286,8 +286,8 @@ jobs:
APPLE_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
BASE_URL: ${{ vars.BASE_URL }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.UPDATER_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.UPDATER_PRIVATE_KEY_PASSWORD }}
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:
@@ -3,7 +3,7 @@ name: Update Changelog
on:
push:
branches:
- develop
- main
permissions:
contents: write
@@ -155,7 +155,7 @@ jobs:
while i < len(lines):
line = lines[i]
output_lines.append(line)
# Check if this is the [Unreleased] header line
if line.strip() == "## [Unreleased]":
# Find the end of the [Unreleased] section (next ## header or end of file)
@@ -169,7 +169,7 @@ jobs:
inserted = True
# Continue with remaining lines
continue
i += 1
# If [Unreleased] not found, prepend
-71
View File
@@ -1,71 +0,0 @@
name: Version Bump (Legacy)
on:
workflow_dispatch:
permissions:
contents: write
jobs:
version-bump:
runs-on: ubuntu-latest
environment: Production
# Skip if this is a version bump commit to avoid infinite loop
if: "!contains(github.event.head_commit.message, 'chore: bump version')"
steps:
- 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 code
uses: actions/checkout@v4
with:
# Use GitHub App token for checkout and push operations
# This token has bypass permissions if the GitHub App is configured with them
token: ${{ steps.app-token.outputs.token }}
fetch-depth: 1
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20.x
- name: Configure Git
env:
GITHUB_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"
# Update remote URL to use GitHub App token for all git operations
git remote set-url origin https://${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git
- name: Pull latest changes
run: |
# Ensure we're on main and pull latest changes to avoid push conflicts
git checkout main
git pull origin main --ff-only
- name: Bump version
id: version
run: |
# Get current version
CURRENT_VERSION=$(node -p "require('./package.json').version")
echo "Current version: $CURRENT_VERSION"
# Bump minor version (creates commit and tag)
# The commit message includes [skip ci] to prevent triggering this workflow again
NEW_VERSION=$(npm version minor -m "chore: bump version to %s" | sed 's/v//')
echo "New version: $NEW_VERSION"
echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
- name: Push changes
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
# Push to main and tags using GitHub App token (remote URL already configured)
git push origin main
git push origin --tags
+3
View File
@@ -48,3 +48,6 @@ e2e-results/
wdio-logs/
test-results/
coverage/
tauri.key
tauri.key.pub
-4
View File
@@ -212,10 +212,6 @@ Set in `.env` (Vite exposes `VITE_*` prefixed vars):
| Variable | Purpose |
| ---------------------------- | ------------------------------------------------------------------- |
| `VITE_BACKEND_URL` | Backend API URL (default: `http://localhost:5005`) |
| `VITE_TELEGRAM_API_ID` | Telegram MTProto API ID |
| `VITE_TELEGRAM_API_HASH` | Telegram MTProto API hash |
| `VITE_TELEGRAM_BOT_USERNAME` | Telegram bot username |
| `VITE_TELEGRAM_BOT_ID` | Telegram bot numeric ID |
| `VITE_SENTRY_DSN` | Sentry DSN for error reporting (optional) |
| `VITE_DEBUG` | Debug mode flag |
| `ALPHAHUMAN_DAEMON_INTERNAL` | Force internal daemon mode (default: false, uses external services) |
-6
View File
@@ -173,12 +173,6 @@ Environment variable access with defaults.
// Backend URL
export const BACKEND_URL = import.meta.env.VITE_BACKEND_URL || 'https://api.example.com';
// Telegram configuration
export const TELEGRAM_API_ID = import.meta.env.VITE_TELEGRAM_API_ID;
export const TELEGRAM_API_HASH = import.meta.env.VITE_TELEGRAM_API_HASH;
export const TELEGRAM_BOT_USERNAME = import.meta.env.VITE_TELEGRAM_BOT_USERNAME;
export const TELEGRAM_BOT_ID = import.meta.env.VITE_TELEGRAM_BOT_ID;
// Debug mode
export const DEBUG = import.meta.env.VITE_DEBUG === 'true';
```
+4 -4
View File
@@ -1,5 +1,7 @@
{
"secrets": {
"XGITHUB_APP_ID": "",
"XGITHUB_APP_PRIVATE_KEY": "",
"XGH_TOKEN": "",
"GITHUB_TOKEN": "",
"UPDATER_GIST_URL": "",
@@ -12,15 +14,13 @@
"APPLE_SIGNING_IDENTITY": "",
"APPLE_ID": "",
"APPLE_APP_SPECIFIC_PASSWORD": "",
"APPLE_TEAM_ID": "",
"VITE_TELEGRAM_BOT_USERNAME": "",
"VITE_TELEGRAM_BOT_ID": "",
"VITE_SENTRY_DSN": ""
"APPLE_TEAM_ID": ""
},
"vars": {
"BASE_URL": "https://localhost",
"VITE_BACKEND_URL": "https://localhost:5005",
"VITE_SKILLS_GITHUB_REPO": "",
"VITE_SENTRY_DSN": "",
"VITE_DEBUG": "true"
}
}
+139
View File
@@ -0,0 +1,139 @@
#!/usr/bin/env bash
# Test the Release workflow locally using act.
#
# Defaults are safe:
# - Uses scripts/ci-secrets.example.json for secrets/vars.
# - Runs in dry-run mode unless --run is passed.
#
# Usage:
# ./scripts/test-release-act.sh
# ./scripts/test-release-act.sh --run
# ./scripts/test-release-act.sh --list
# ./scripts/test-release-act.sh --job prepare-release
# ./scripts/test-release-act.sh --release-type minor
# ./scripts/test-release-act.sh --secrets-json scripts/ci-secrets.json --run
set -euo pipefail
cd "$(git rev-parse --show-toplevel)"
WORKFLOW=".github/workflows/release.yml"
SECRETS_JSON="scripts/ci-secrets.json"
RELEASE_TYPE="patch"
RUN_MODE="dryrun"
JOB_NAME=""
while [[ $# -gt 0 ]]; do
case "$1" in
--run)
RUN_MODE="run"
shift
;;
--dryrun)
RUN_MODE="dryrun"
shift
;;
--list)
RUN_MODE="list"
shift
;;
--job)
JOB_NAME="${2:-}"
shift 2
;;
--release-type)
RELEASE_TYPE="${2:-patch}"
shift 2
;;
--secrets-json)
SECRETS_JSON="${2:-}"
shift 2
;;
*)
echo "Unknown argument: $1" >&2
exit 1
;;
esac
done
if [[ ! -f "$SECRETS_JSON" ]]; then
echo "Secrets JSON not found: $SECRETS_JSON" >&2
exit 1
fi
if ! command -v act >/dev/null 2>&1; then
echo "act is required. Install with: brew install act" >&2
exit 1
fi
if ! command -v jq >/dev/null 2>&1; then
echo "jq is required. Install with: brew install jq" >&2
exit 1
fi
case "$RELEASE_TYPE" in
major|minor|patch) ;;
*)
echo "--release-type must be one of: major, minor, patch" >&2
exit 1
;;
esac
if [[ "$RUN_MODE" == "list" ]]; then
act -W "$WORKFLOW" --list
exit 0
fi
SECRETS_FILE="$(mktemp)"
VARS_FILE="$(mktemp)"
EVENT_JSON="$(mktemp)"
trap 'rm -f "$SECRETS_FILE" "$VARS_FILE" "$EVENT_JSON"' EXIT
jq -r '.secrets // {} | to_entries[] | "\(.key)=\(.value)"' "$SECRETS_JSON" > "$SECRETS_FILE"
jq -r '.vars // {} | to_entries[] | "\(.key)=\(.value)"' "$SECRETS_JSON" > "$VARS_FILE"
cat > "$EVENT_JSON" <<EOF
{
"ref": "refs/heads/main",
"inputs": {
"release_type": "$RELEASE_TYPE"
},
"repository": {
"full_name": "local/openhuman",
"default_branch": "main",
"name": "openhuman",
"owner": { "login": "local" }
},
"sender": { "login": "local-dev" }
}
EOF
echo "Workflow: $WORKFLOW"
echo "Secrets: $SECRETS_JSON"
echo "Input: release_type=$RELEASE_TYPE"
echo "Mode: $RUN_MODE"
if [[ -n "$JOB_NAME" ]]; then
echo "Job: $JOB_NAME"
fi
echo
ACT_ARGS=(
workflow_dispatch
-W "$WORKFLOW"
--eventpath "$EVENT_JSON"
--secret-file "$SECRETS_FILE"
--var-file "$VARS_FILE"
-P ubuntu-latest=catthehacker/ubuntu:act-latest
-P ubuntu-22.04=catthehacker/ubuntu:act-22.04
-P macos-latest=-self-hosted
)
if [[ -n "$JOB_NAME" ]]; then
ACT_ARGS+=(-j "$JOB_NAME")
fi
if [[ "$RUN_MODE" == "dryrun" ]]; then
echo "Dry-run only. Use --run to execute."
act "${ACT_ARGS[@]}" -n
else
act "${ACT_ARGS[@]}"
fi
@@ -125,19 +125,9 @@ pub const ALLOWED_ENV_VARS: &[&str] = &[
"VITE_BACKEND_URL",
"BACKEND_URL",
"JWT_TOKEN",
"VITE_TELEGRAM_BOT_USERNAME",
"VITE_TELEGRAM_BOT_ID",
"NODE_ENV",
];
pub fn check_telegram_skill(skill_id: &str) -> Result<(), String> {
if skill_id != "telegram" {
Err("TDLib operations only available in telegram skill".to_string())
} else {
Ok(())
}
}
/// Sanitize error message for use with QuickJS/rquickjs.
/// Some messages (e.g. from SQLite "Invalid symbol 45, offset 19") can trigger
/// "Invalid symbol" when rquickjs creates the JS exception — avoid comma and hyphen.