ci: enhance GitHub Actions workflow for performance and caching
Some checks failed
CI/CD Pipeline / Quick Checks (pull_request) Failing after 9s
CI/CD Pipeline / ESLint (pull_request) Has been skipped
CI/CD Pipeline / Test & Coverage (pull_request) Has been skipped
CI/CD Pipeline / Build Application (pull_request) Has been skipped
CI/CD Pipeline / Security Audit (pull_request) Has been skipped

This commit is contained in:
2025-05-27 13:02:05 +02:00
parent 76dd9cd838
commit 9498026e71
5 changed files with 192 additions and 37 deletions

View File

@ -6,10 +6,63 @@ on:
pull_request:
branches: [main, develop]
# Concurrency control to cancel previous runs
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
BUN_VERSION: '1.2.5' # Pin version for consistency
jobs:
lint-and-test:
name: Lint, Test & Build
# Job 1: Quick checks that can fail fast
quick-checks:
name: Quick Checks
runs-on: ubuntu-latest
outputs:
cache-key: ${{ steps.cache-key.outputs.key }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Generate cache key
id: cache-key
run: echo "key=bun-${{ hashFiles('bun.lock') }}" >> $GITHUB_OUTPUT
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: ${{ env.BUN_VERSION }}
- name: Cache dependencies
uses: actions/cache@v4
id: cache-deps
with:
path: ~/.bun/install/cache
key: ${{ steps.cache-key.outputs.key }}
restore-keys: bun-
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Cache node_modules
uses: actions/cache@v4
with:
path: node_modules
key: node-modules-${{ steps.cache-key.outputs.key }}
- name: Run TypeScript type check
run: bun run type-check
- name: Check Prettier formatting
run: bun run format:check
# Job 2: Linting (can run in parallel with type checking)
lint:
name: ESLint
runs-on: ubuntu-latest
needs: quick-checks
steps:
- name: Checkout repository
@ -18,35 +71,114 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: latest
bun-version: ${{ env.BUN_VERSION }}
- name: Install dependencies
- name: Restore node_modules cache
uses: actions/cache@v4
with:
path: node_modules
key: node-modules-${{ needs.quick-checks.outputs.cache-key }}
- name: Install dependencies (if cache miss)
run: bun install
- name: Run TypeScript type check
run: bun run type-check
- name: Run ESLint
run: bun run lint
- name: Check Prettier formatting
run: bun run format:check
# Job 3: Testing with optimizations
test:
name: Test & Coverage
runs-on: ubuntu-latest
needs: quick-checks
- name: Run tests
run: bun run test:ci
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: ${{ env.BUN_VERSION }}
- name: Restore node_modules cache
uses: actions/cache@v4
with:
path: node_modules
key: node-modules-${{ needs.quick-checks.outputs.cache-key }}
- name: Install dependencies (if cache miss)
run: bun install
- name: Cache Jest cache
uses: actions/cache@v4
with:
path: .jest-cache
key: jest-cache-${{ hashFiles('jest.config.js', 'src/**/*.{ts,tsx}') }}
restore-keys: jest-cache-
- name: Run tests with optimizations
run: bun run test:ci --maxWorkers=2 --cacheDirectory=.jest-cache
env:
NODE_OPTIONS: --max_old_space_size=4096
- name: Upload coverage reports
uses: actions/upload-artifact@v4
if: always()
with:
name: coverage-report
path: coverage/
retention-days: 7
# Job 4: Build (depends on tests passing)
build:
name: Build Application
runs-on: ubuntu-latest
needs: [quick-checks, lint, test]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: ${{ env.BUN_VERSION }}
- name: Restore node_modules cache
uses: actions/cache@v4
with:
path: node_modules
key: node-modules-${{ needs.quick-checks.outputs.cache-key }}
- name: Install dependencies (if cache miss)
run: bun install
- name: Cache Next.js build
uses: actions/cache@v4
with:
path: |
.next/cache
.next/static
key: nextjs-${{ hashFiles('next.config.ts', 'src/**/*.{ts,tsx}', 'public/**/*') }}
restore-keys: nextjs-
- name: Build application
run: bun run build
env:
NODE_OPTIONS: --max_old_space_size=4096
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build-files
path: .next/
retention-days: 7
# Job 5: Security audit (can run in parallel)
security-audit:
name: Security Audit
runs-on: ubuntu-latest
needs: quick-checks
steps:
- name: Checkout repository
@ -55,13 +187,19 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: latest
bun-version: ${{ env.BUN_VERSION }}
- name: Install dependencies
- name: Restore node_modules cache
uses: actions/cache@v4
with:
path: node_modules
key: node-modules-${{ needs.quick-checks.outputs.cache-key }}
- name: Install dependencies (if cache miss)
run: bun install
- name: Run security audit
run: bun audit
run: bun audit --audit-level moderate
- name: Run dependency check
run: bunx audit-ci --moderate
- name: Run dependency vulnerability check
run: bunx audit-ci --moderate --report-type summary

1
.gitignore vendored
View File

@ -12,6 +12,7 @@
# testing
/coverage
.jest-cache
# next.js
/.next/

View File

@ -32,6 +32,20 @@ const customJestConfig = {
'^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', { presets: ['next/babel'] }],
},
transformIgnorePatterns: ['/node_modules/', '^.+\\.module\\.(css|sass|scss)$'],
// Performance optimizations
maxWorkers: '50%',
cache: true,
cacheDirectory: '.jest-cache',
clearMocks: true,
collectCoverage: false, // Only collect coverage when explicitly requested
coverageReporters: ['text', 'lcov'],
errorOnDeprecated: true,
// Reduce memory usage
logHeapUsage: true,
// Faster test discovery
testLocationInResults: true,
// Skip coverage for faster runs in watch mode
watchPathIgnorePatterns: ['<rootDir>/coverage/', '<rootDir>/.next/'],
};
// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async

View File

@ -13,7 +13,10 @@
"type-check": "tsc --noEmit",
"test": "jest",
"test:watch": "jest --watch",
"test:ci": "jest --ci --coverage --watchAll=false",
"test:ci": "jest --ci --coverage --watchAll=false --maxWorkers=2",
"test:fast": "jest --watchAll=false --maxWorkers=50%",
"test:coverage": "jest --coverage --watchAll=false",
"ci:all": "bun run type-check && bun run lint && bun run test:ci && bun run build",
"prepare": "husky"
},
"dependencies": {

View File

@ -1,4 +1,4 @@
import { render } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import RootLayout, { metadata } from '../layout';
@ -17,20 +17,19 @@ jest.mock('../globals.css', () => ({}));
describe('RootLayout', () => {
it('renders children correctly', () => {
const testContent = <div data-testid="test-content">Test content</div>;
const { getByTestId } = render(<RootLayout>{testContent}</RootLayout>);
const testContent = <div data-testid='test-content'>Test content</div>;
render(<RootLayout>{testContent}</RootLayout>);
expect(getByTestId('test-content')).toBeInTheDocument();
expect(getByTestId('test-content')).toHaveTextContent('Test content');
expect(screen.getByTestId('test-content')).toBeInTheDocument();
expect(screen.getByTestId('test-content')).toHaveTextContent('Test content');
});
it('creates proper HTML structure with lang attribute', () => {
const testContent = <div>Test</div>;
const { container } = render(<RootLayout>{testContent}</RootLayout>);
// The component returns JSX with html and body elements
// We can test that the component renders without errors
expect(container.firstChild).toBeInTheDocument();
it('creates proper HTML structure', () => {
const testContent = <div data-testid='layout-child'>Test</div>;
render(<RootLayout>{testContent}</RootLayout>);
// Verify the child component is rendered correctly
expect(screen.getByTestId('layout-child')).toBeInTheDocument();
});
it('has correct metadata export', () => {
@ -39,12 +38,12 @@ describe('RootLayout', () => {
expect(metadata.description).toBe('Generated by create next app');
});
it('applies font classes correctly', () => {
// Test that the component can be instantiated and called
const testContent = <div>Test</div>;
const renderResult = render(<RootLayout>{testContent}</RootLayout>);
// Just ensure it renders without throwing errors
expect(renderResult.container).toBeInTheDocument();
it('renders without errors', () => {
const testContent = <div data-testid='render-test'>Test</div>;
const view = render(<RootLayout>{testContent}</RootLayout>);
// Verify the component renders successfully
expect(screen.getByTestId('render-test')).toBeInTheDocument();
expect(view.container).toBeInTheDocument();
});
});
});