mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-30 15:03:57 +00:00
Update skills submodule and add GitHub Actions workflows for CI/CD
- Updated the skills submodule to the latest commit, ensuring alignment with project dependencies. - Introduced new GitHub Actions workflows: - `build.yml` for building the Tauri app. - `test.yml` for running unit tests with coverage. - `pr-protection.yml` to enforce PR title conventions and branch protection. - `version-bump.yml` for automated version bumping on main branch pushes. These changes enhance the CI/CD pipeline, improve code quality checks, and ensure the project remains up-to-date with the latest skills integration.
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["*"]
|
||||
pull_request:
|
||||
branches: ["*"]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test-build:
|
||||
name: Build Tauri App
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- 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: x86_64-unknown-linux-gnu
|
||||
|
||||
- name: Install Tauri dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
|
||||
- name: Cache node modules
|
||||
id: yarn-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-build-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.yarn-cache.outputs.cache-hit != 'true'
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Build frontend
|
||||
run: yarn build
|
||||
|
||||
- name: Build Tauri app
|
||||
run: yarn tauri build -- --target x86_64-unknown-linux-gnu
|
||||
env:
|
||||
NODE_ENV: production
|
||||
@@ -0,0 +1,176 @@
|
||||
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 or master
|
||||
const isTargetingMain = baseBranch === 'main' || baseBranch === 'master';
|
||||
|
||||
if (!isTargetingMain) {
|
||||
console.log(`PR is targeting ${baseBranch}, not main/master. 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);
|
||||
@@ -0,0 +1,58 @@
|
||||
name: Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['*']
|
||||
pull_request:
|
||||
branches: ['*']
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Run Unit Tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Node.js 24.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24.x
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Cache node modules
|
||||
id: yarn-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-test-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-test-
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.yarn-cache.outputs.cache-hit != 'true'
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Run tests with coverage
|
||||
run: yarn test:coverage
|
||||
env:
|
||||
NODE_ENV: test
|
||||
|
||||
- name: Upload coverage reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage-report
|
||||
path: coverage
|
||||
retention-days: 7
|
||||
@@ -0,0 +1,71 @@
|
||||
name: Version Bump
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
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') && !contains(github.event.head_commit.message, '[skip ci]')"
|
||||
|
||||
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 [skip ci]" | sed 's/v//')
|
||||
echo "New version: $NEW_VERSION"
|
||||
echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Push changes
|
||||
run: |
|
||||
# Push to main and tags using GitHub App token (remote URL already configured)
|
||||
git push origin main
|
||||
git push origin --tags
|
||||
+1
-1
Submodule skills updated: 87bb3adab1...1c3082aca4
Reference in New Issue
Block a user