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:
Steven Enamakel
2026-02-02 05:28:11 +05:30
parent 47193efad7
commit 3dcf6d6110
4 changed files with 448 additions and 3 deletions
+1 -3
View File
@@ -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`);
+51
View File
@@ -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
+206
View File
@@ -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,
];