mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-30 23:14:37 +00:00
Refactor GitHub Actions build workflow to streamline concurrency handling
- Removed the push trigger from the build workflow, focusing on pull requests for CI/CD processes. - Simplified concurrency group definition by eliminating fallback options, ensuring clearer management of in-progress builds. These changes enhance the clarity and efficiency of the CI/CD pipeline, aligning it more closely with project needs.
This commit is contained in:
@@ -1,8 +1,6 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["*"]
|
||||
pull_request:
|
||||
branches: ["*"]
|
||||
|
||||
@@ -11,7 +9,7 @@ permissions:
|
||||
pull-requests: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.ref }}
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
name: Create PR from Develop to Main
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: develop-to-main-pr
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
create-pr:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Fetch tags and branches
|
||||
run: |
|
||||
git fetch origin --tags
|
||||
git fetch origin main develop
|
||||
|
||||
- name: Check if push is from sync workflow
|
||||
id: check-sync
|
||||
run: |
|
||||
# Check if the latest commit on develop is the same as main (indicating a sync operation)
|
||||
# When sync-main-to-develop runs, it fast-forwards develop to match main's HEAD
|
||||
DEVELOP_HEAD=$(git rev-parse origin/develop 2>/dev/null || echo "")
|
||||
MAIN_HEAD=$(git rev-parse origin/main 2>/dev/null || echo "")
|
||||
|
||||
if [ -z "$DEVELOP_HEAD" ] || [ -z "$MAIN_HEAD" ]; then
|
||||
echo "Could not determine branch heads. Proceeding with PR check."
|
||||
echo "is_sync=false" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Develop HEAD: $DEVELOP_HEAD"
|
||||
echo "Main HEAD: $MAIN_HEAD"
|
||||
|
||||
# If develop HEAD is the same as main HEAD, this is a sync operation and we should skip
|
||||
if [ "$DEVELOP_HEAD" = "$MAIN_HEAD" ]; then
|
||||
echo "Latest commit on develop is the same as main. This is a sync operation from sync-main-to-develop workflow. Skipping PR creation."
|
||||
echo "is_sync=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "This is not a sync operation. Proceeding with PR check."
|
||||
echo "is_sync=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Check for existing PR
|
||||
if: steps.check-sync.outputs.is_sync != 'true'
|
||||
id: check-pr
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const { data: pulls } = await github.rest.pulls.list({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
base: 'main',
|
||||
head: `${context.repo.owner}:develop`,
|
||||
state: 'open'
|
||||
});
|
||||
|
||||
if (pulls.length > 0) {
|
||||
core.setOutput('exists', 'true');
|
||||
core.setOutput('number', pulls[0].number.toString());
|
||||
console.log(`Existing PR found: #${pulls[0].number}. Skipping PR creation.`);
|
||||
} else {
|
||||
core.setOutput('exists', 'false');
|
||||
console.log('No existing PR found');
|
||||
}
|
||||
|
||||
- name: Check if develop is ahead of main
|
||||
id: check-diff
|
||||
if: steps.check-sync.outputs.is_sync != 'true' && steps.check-pr.outputs.exists == 'false'
|
||||
run: |
|
||||
# Get the commit counts
|
||||
DEVELOP_COMMITS=$(git rev-list --count origin/main..origin/develop)
|
||||
MAIN_COMMITS=$(git rev-list --count origin/develop..origin/main)
|
||||
|
||||
echo "Commits in develop ahead of main: $DEVELOP_COMMITS"
|
||||
echo "Commits in main ahead of develop: $MAIN_COMMITS"
|
||||
|
||||
if [ "$DEVELOP_COMMITS" -eq 0 ]; then
|
||||
echo "develop is not ahead of main. No PR needed."
|
||||
echo "ahead=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "develop is ahead of main by $DEVELOP_COMMITS commits. Creating PR..."
|
||||
echo "ahead=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Calculate next version
|
||||
id: next-version
|
||||
if: steps.check-sync.outputs.is_sync != 'true' && steps.check-pr.outputs.exists == 'false' && steps.check-diff.outputs.ahead == 'true'
|
||||
run: |
|
||||
# Get the latest version tag (format: v1.2.3)
|
||||
LATEST_TAG=$(git tag -l "v*" | sort -V | tail -n 1 || echo "")
|
||||
|
||||
if [ -z "$LATEST_TAG" ]; then
|
||||
# No tags found, start with v1.0.0
|
||||
NEXT_VERSION="v1.0.0"
|
||||
LATEST_TAG="(none)"
|
||||
else
|
||||
# Extract version number (remove 'v' prefix)
|
||||
VERSION="${LATEST_TAG#v}"
|
||||
|
||||
# Split into major.minor.patch
|
||||
IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION"
|
||||
|
||||
# Ensure we have valid numbers (default to 0 if empty)
|
||||
MAJOR=${MAJOR:-0}
|
||||
MINOR=${MINOR:-0}
|
||||
PATCH=${PATCH:-0}
|
||||
|
||||
# Increment minor version and reset patch to 0
|
||||
MINOR=$((MINOR + 1))
|
||||
PATCH=0
|
||||
|
||||
NEXT_VERSION="v${MAJOR}.${MINOR}.${PATCH}"
|
||||
fi
|
||||
|
||||
echo "Latest tag: $LATEST_TAG"
|
||||
echo "Predicted next version: $NEXT_VERSION"
|
||||
echo "version=$NEXT_VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Generate PR description
|
||||
id: pr-description
|
||||
if: steps.check-sync.outputs.is_sync != 'true' && steps.check-pr.outputs.exists == 'false' && steps.check-diff.outputs.ahead == 'true'
|
||||
run: |
|
||||
cat <<EOF > pr_body.txt
|
||||
## Overview
|
||||
|
||||
This PR automatically merges changes from develop into main. This merge will trigger a tag action that will bump the minor version to ${{ steps.next-version.outputs.version }}.
|
||||
|
||||
## Notes
|
||||
|
||||
This PR was automatically created by GitHub Actions. Please review the changes before merging.
|
||||
EOF
|
||||
|
||||
- name: Create pull request
|
||||
if: steps.check-sync.outputs.is_sync != 'true' && steps.check-pr.outputs.exists == 'false' && steps.check-diff.outputs.ahead == 'true'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const prBody = fs.readFileSync('pr_body.txt', 'utf8');
|
||||
const nextVersion = '${{ steps.next-version.outputs.version }}';
|
||||
|
||||
const { data: pr } = await github.rest.pulls.create({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
title: `chore: merge develop into main (${nextVersion})`,
|
||||
head: 'develop',
|
||||
base: 'main',
|
||||
body: prBody
|
||||
});
|
||||
|
||||
// Add labels
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
labels: ['chore', 'type: chore']
|
||||
});
|
||||
|
||||
// Request review from senamakel
|
||||
await github.rest.pulls.requestReviewers({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: pr.number,
|
||||
reviewers: ['senamakel']
|
||||
});
|
||||
|
||||
console.log(`Created PR #${pr.number} with version ${nextVersion} and requested review from senamakel`);
|
||||
@@ -0,0 +1,51 @@
|
||||
name: Type Check
|
||||
|
||||
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:
|
||||
typecheck:
|
||||
name: Type Check TypeScript
|
||||
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 }}-typecheck-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-typecheck-
|
||||
${{ runner.os }}-test-
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.yarn-cache.outputs.cache-hit != 'true'
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Type check TypeScript files
|
||||
run: npx tsc
|
||||
env:
|
||||
NODE_ENV: test
|
||||
@@ -0,0 +1,206 @@
|
||||
// ESLint flat config for ESLint 9+
|
||||
// This config is compatible with Prettier and won't conflict with formatting rules
|
||||
|
||||
const js = require('@eslint/js');
|
||||
const tseslint = require('@typescript-eslint/eslint-plugin');
|
||||
const tsparser = require('@typescript-eslint/parser');
|
||||
const reactPlugin = require('eslint-plugin-react');
|
||||
const reactHooksPlugin = require('eslint-plugin-react-hooks');
|
||||
const importPlugin = require('eslint-plugin-import');
|
||||
const prettierConfig = require('eslint-config-prettier');
|
||||
|
||||
module.exports = [
|
||||
// Base recommended rules
|
||||
js.configs.recommended,
|
||||
|
||||
// Ignore patterns
|
||||
{
|
||||
ignores: [
|
||||
'node_modules/**',
|
||||
'dist/**',
|
||||
'coverage/**',
|
||||
'src-tauri/**',
|
||||
'skills/**',
|
||||
'*.config.js',
|
||||
'*.config.ts',
|
||||
'vitest.config.ts',
|
||||
'tsconfig.tsbuildinfo',
|
||||
],
|
||||
},
|
||||
|
||||
// Browser environment globals
|
||||
{
|
||||
files: ['**/*.js', '**/*.ts', '**/*.jsx', '**/*.tsx'],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
// Browser globals
|
||||
window: 'readonly',
|
||||
document: 'readonly',
|
||||
navigator: 'readonly',
|
||||
console: 'readonly',
|
||||
setTimeout: 'readonly',
|
||||
setInterval: 'readonly',
|
||||
clearTimeout: 'readonly',
|
||||
clearInterval: 'readonly',
|
||||
fetch: 'readonly',
|
||||
AbortSignal: 'readonly',
|
||||
// Node.js globals (for Vite/node polyfills)
|
||||
require: 'readonly',
|
||||
process: 'readonly',
|
||||
Buffer: 'readonly',
|
||||
global: 'readonly',
|
||||
__dirname: 'readonly',
|
||||
__filename: 'readonly',
|
||||
module: 'readonly',
|
||||
exports: 'readonly',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// TypeScript files configuration
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
languageOptions: {
|
||||
parser: tsparser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
project: './tsconfig.json',
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
'@typescript-eslint': tseslint,
|
||||
import: importPlugin,
|
||||
},
|
||||
rules: {
|
||||
// Disable base no-unused-vars in favor of TypeScript version
|
||||
'no-unused-vars': 'off',
|
||||
// TypeScript recommended rules (disable base JS rules that TypeScript handles)
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_|^[A-Z_]+$', // Ignore _prefixed vars and ALL_CAPS (enum members)
|
||||
caughtErrorsIgnorePattern: '^_',
|
||||
ignoreRestSiblings: true,
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
|
||||
// Import/export rules
|
||||
// Note: import/order is disabled to let Prettier handle import sorting
|
||||
// ESLint still checks for other import issues
|
||||
'import/order': 'off', // Prettier plugin handles import sorting
|
||||
'import/no-unresolved': 'off', // TypeScript handles this
|
||||
'import/no-cycle': 'warn',
|
||||
'import/no-duplicates': 'error', // Prevent duplicate imports
|
||||
|
||||
// General JavaScript/TypeScript rules
|
||||
'no-console': 'off', // Allow console in frontend code
|
||||
'no-debugger': 'error',
|
||||
'no-duplicate-imports': 'error',
|
||||
'no-unused-expressions': 'off', // Covered by @typescript-eslint version
|
||||
'@typescript-eslint/no-unused-expressions': 'error',
|
||||
|
||||
// Code quality
|
||||
'prefer-const': 'error',
|
||||
'no-var': 'error',
|
||||
'object-shorthand': 'error',
|
||||
'prefer-arrow-callback': 'error',
|
||||
|
||||
// Style: Enforce single-line statements on same line without braces when possible
|
||||
curly: ['error', 'multi', 'consistent'], // Allow single-line without braces, require braces only for multi-statement blocks
|
||||
'nonblock-statement-body-position': ['error', 'beside'], // Enforce single-line statements on same line (prevents braces on single-line)
|
||||
},
|
||||
},
|
||||
|
||||
// React files configuration
|
||||
{
|
||||
files: ['**/*.jsx', '**/*.tsx'],
|
||||
languageOptions: {
|
||||
parser: tsparser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
project: './tsconfig.json',
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
react: reactPlugin,
|
||||
'react-hooks': reactHooksPlugin,
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
...reactPlugin.configs.recommended.rules,
|
||||
...reactHooksPlugin.configs.recommended.rules,
|
||||
'react/react-in-jsx-scope': 'off', // Not needed in React 17+
|
||||
'react/prop-types': 'off', // TypeScript handles prop validation
|
||||
'react/display-name': 'off', // Not needed with TypeScript
|
||||
'react-hooks/rules-of-hooks': 'error',
|
||||
'react-hooks/exhaustive-deps': 'warn',
|
||||
},
|
||||
},
|
||||
|
||||
// Vitest test files and test setup files (must come after TypeScript config to override rules)
|
||||
{
|
||||
files: [
|
||||
'**/*.test.ts',
|
||||
'**/*.test.tsx',
|
||||
'**/*.spec.ts',
|
||||
'**/*.spec.tsx',
|
||||
'**/__tests__/**/*.ts',
|
||||
'**/__tests__/**/*.tsx',
|
||||
],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
// Vitest globals
|
||||
describe: 'readonly',
|
||||
it: 'readonly',
|
||||
test: 'readonly',
|
||||
expect: 'readonly',
|
||||
beforeEach: 'readonly',
|
||||
afterEach: 'readonly',
|
||||
beforeAll: 'readonly',
|
||||
afterAll: 'readonly',
|
||||
vi: 'readonly',
|
||||
vitest: 'readonly',
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'off', // Allow any in tests
|
||||
'@typescript-eslint/no-non-null-assertion': 'off', // Allow non-null assertions in tests
|
||||
'no-undef': 'off', // Vitest provides globals
|
||||
},
|
||||
},
|
||||
|
||||
// JavaScript files configuration
|
||||
{
|
||||
files: ['**/*.js', '**/*.jsx'],
|
||||
languageOptions: { ecmaVersion: 'latest', sourceType: 'module' },
|
||||
rules: {
|
||||
'no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
|
||||
'no-console': 'off',
|
||||
'no-debugger': 'error',
|
||||
'prefer-const': 'error',
|
||||
'no-var': 'error',
|
||||
},
|
||||
},
|
||||
|
||||
// Disable all Prettier-conflicting rules (must be last)
|
||||
prettierConfig,
|
||||
];
|
||||
Reference in New Issue
Block a user