diff --git a/.github/workflows/ci-and-labels.yml b/.github/workflows/ci-and-labels.yml index 6aaf76e..376b4d1 100644 --- a/.github/workflows/ci-and-labels.yml +++ b/.github/workflows/ci-and-labels.yml @@ -1,19 +1,12 @@ --- -name: CI + Labels Setup +name: CI (2026 Edition) -# yamllint disable-line rule:truthy on: push: - branches: - - main - - develop - - 'release/**' + branches: [main, develop, 'release/**'] pull_request: - branches: - - main - - develop + branches: [main, develop] -# Default permissions for all jobs permissions: contents: read @@ -22,248 +15,83 @@ jobs: name: Detect Changes runs-on: ubuntu-latest outputs: - docs: ${{ steps.filter.outputs.docs }} - agent: ${{ steps.filter.outputs.agent }} frontend: ${{ steps.filter.outputs.frontend }} - cli: ${{ steps.filter.outputs.cli }} - export: ${{ steps.filter.outputs.export }} tooling: ${{ steps.filter.outputs.tooling }} any_code: ${{ steps.filter.outputs.any_code }} steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Path Filter - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 + uses: dorny/paths-filter@de200424a13a299ef0d436c8413210214660e5ba # v3.0.0 id: filter with: filters: | - docs: - - '**.md' - - 'agents-docs/**' - agent: - - '.agents/**' - - 'AGENTS.md' - - 'scripts/validate-skills.sh' - - 'scripts/validate-skill-format.sh' - - 'scripts/validate-links.sh' frontend: - 'src/**' - 'public/**' - - 'index.html' - 'vite.config.ts' - 'package.json' - cli: - - 'cli/**' - - 'package.json' - export: - - 'export/**' - - 'package.json' tooling: - 'scripts/**' - '.github/workflows/**' - 'package.json' - 'tsconfig.json' - - '.eslintrc.cjs' - - '.pre-commit-config.yaml' - - 'vitest.config.ts' - - 'playwright.config.ts' any_code: - - '!(**.md|agents-docs/**|.agents/**|AGENTS.md)' + - '!(**.md|docs/**)' - labels: - name: Initialize GitHub Labels + security: + name: Security Scan runs-on: ubuntu-latest - # Requires write permission to create labels - # See: https://docs.github.com/en/rest/issues/labels#create-a-label permissions: - issues: write + security-events: write steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 with: - token: ${{ secrets.GITHUB_TOKEN }} - persist-credentials: false - - - name: Ensure script is executable - run: chmod +x scripts/gh-labels-creator.sh - - - name: Run label initialization - run: ./scripts/gh-labels-creator.sh --ci - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + languages: javascript-typescript + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 quality-gate: name: Quality Gate runs-on: ubuntu-latest needs: changes - if: >- - needs.changes.outputs.any_code == 'true' || - needs.changes.outputs.docs == 'true' || - needs.changes.outputs.agent == 'true' - timeout-minutes: 10 # Fail fast instead of waiting 6 hours + if: needs.changes.outputs.any_code == 'true' steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - token: ${{ secrets.GITHUB_TOKEN }} - persist-credentials: false - - - name: Setup skills - run: ./scripts/setup-skills.sh - - - name: Setup Node.js (if package.json exists) + - name: Setup Node.js uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version: '20' - if: hashFiles('package.json') != '' - - - name: Setup Rust (if Cargo.toml exists) - uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 # stable - with: - components: rustfmt, clippy - if: hashFiles('Cargo.toml') != '' - - - name: Setup Python (if requirements.txt or pyproject.toml exists) - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: '3.11' - if: >- - hashFiles('requirements.txt') != '' || - hashFiles('pyproject.toml') != '' || - hashFiles('.agents/skills/*/requirements.txt') != '' || - hashFiles('.agents/skills/*/pyproject.toml') != '' - - - name: Setup Go (if go.mod exists) - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 - with: - go-version: '1.21' - if: hashFiles('go.mod') != '' - - - name: Install dependencies (Node) + node-version: '22' + cache: 'npm' + - name: Install dependencies run: npm ci - if: hashFiles('package.json') != '' - - - name: Install dependencies (Python) - run: | - pip install ruff black pytest - if: >- - hashFiles('requirements.txt') != '' || - hashFiles('pyproject.toml') != '' || - hashFiles('.agents/skills/*/requirements.txt') != '' || - hashFiles('.agents/skills/*/pyproject.toml') != '' - - - name: Install shellcheck and bats - run: | - sudo apt-get update - sudo apt-get install -y shellcheck bats - - name: Run quality gate - run: | - SKIP_GLOBAL_HOOKS_CHECK=true ./scripts/quality_gate.sh --changed + run: ./scripts/quality_gate.sh --changed + env: + SKIP_GLOBAL_HOOKS_CHECK: true test: - name: Run Tests + name: Unit & E2E Tests runs-on: ubuntu-latest needs: [changes, quality-gate] if: needs.changes.outputs.any_code == 'true' steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - token: ${{ secrets.GITHUB_TOKEN }} - persist-credentials: false - - - name: Setup Node.js (if package.json exists) + - name: Setup Node.js uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version: '20' - if: hashFiles('package.json') != '' - - - name: Setup Rust (if Cargo.toml exists) - uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 # stable - if: hashFiles('Cargo.toml') != '' - - - name: Setup Python (if requirements.txt or pyproject.toml exists) - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: '3.11' - if: >- - hashFiles('requirements.txt') != '' || - hashFiles('pyproject.toml') != '' || - hashFiles('.agents/skills/*/requirements.txt') != '' || - hashFiles('.agents/skills/*/pyproject.toml') != '' - - - name: Setup Go (if go.mod exists) - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 - with: - go-version: '1.21' - if: hashFiles('go.mod') != '' - - - name: Install dependencies (Node) + node-version: '22' + cache: 'npm' + - name: Install dependencies run: npm ci - if: hashFiles('package.json') != '' - - - name: Install dependencies (Python root) - run: pip install -r requirements.txt - if: hashFiles('requirements.txt') != '' - - - name: Install dependencies (Python skills) - run: | - for dir in .agents/skills/*/; do - if [ -f "$dir/requirements.txt" ]; then - echo "Installing dependencies for $dir..." - pip install -r "$dir/requirements.txt" - fi - done - if: hashFiles('.agents/skills/*/requirements.txt') != '' - - - name: Run tests (Node) + - name: Run Vitest run: npm test - if: hashFiles('package.json') != '' - - - name: Run tests (Rust) - run: cargo test --lib - if: hashFiles('Cargo.toml') != '' - - - name: Run tests (Python root) - run: pytest tests/ -v - if: hashFiles('requirements.txt') != '' - - - name: Run tests (Python skills) - run: | - for dir in .agents/skills/*/; do - if [ -f "$dir/pyproject.toml" ]; then - echo "Running tests for $dir..." - (cd "$dir" && python -m pytest tests/ -v -m "not live") - fi - done - if: hashFiles('.agents/skills/*/pyproject.toml') != '' - - - name: Run tests (Go) - run: go test ./... - if: hashFiles('go.mod') != '' - - - name: Setup BATS - run: | - sudo apt-get update - sudo apt-get install -y bats - - - name: Run BATS tests - run: | - if [ -d "tests" ] && [ -n "$(find tests -name '*.bats' \ - -print -quit 2>/dev/null)" ] - then - echo "Running BATS tests..." - bats tests/*.bats - else - echo "No BATS tests found" - fi - - name: Install Playwright Browsers run: npx playwright install --with-deps - if: hashFiles('package.json') != '' - - - name: Run E2E tests (Production Build) + - name: Run E2E Tests run: npm run test:e2e:ci - if: hashFiles('package.json') != '' diff --git a/eslint.config.js b/eslint.config.js index 8ab1faa..b2a156a 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -41,15 +41,15 @@ export default tseslint.config( ...react.configs.flat['jsx-runtime'].rules, ...reactHooks.configs.recommended.rules, ...jsxA11y.flatConfigs.recommended.rules, - 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], - 'jsx-a11y/click-events-have-key-events': 'warn', - 'jsx-a11y/no-static-element-interactions': 'warn', - 'jsx-a11y/no-noninteractive-element-interactions': 'warn', - 'jsx-a11y/interactive-supports-focus': 'warn', - 'jsx-a11y/role-has-required-aria-props': 'warn', - 'jsx-a11y/role-supports-aria-props': 'warn', - 'jsx-a11y/no-autofocus': 'warn', - 'react/no-unescaped-entities': 'warn', + 'react-refresh/only-export-components': ['error', { allowConstantExport: true }], + 'jsx-a11y/click-events-have-key-events': 'error', + 'jsx-a11y/no-static-element-interactions': 'error', + 'jsx-a11y/no-noninteractive-element-interactions': 'error', + 'jsx-a11y/interactive-supports-focus': 'error', + 'jsx-a11y/role-has-required-aria-props': 'error', + 'jsx-a11y/role-supports-aria-props': 'error', + 'jsx-a11y/no-autofocus': 'error', + 'react/no-unescaped-entities': 'error', }, settings: { react: { @@ -82,9 +82,9 @@ export default tseslint.config( }, }, rules: { - '@typescript-eslint/no-unsafe-assignment': 'warn', - '@typescript-eslint/no-unsafe-call': 'warn', - '@typescript-eslint/no-unsafe-member-access': 'warn', + '@typescript-eslint/no-unsafe-assignment': 'error', + '@typescript-eslint/no-unsafe-call': 'error', + '@typescript-eslint/no-unsafe-member-access': 'error', }, }, diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx index c20707f..83a4c19 100644 --- a/src/components/ErrorBoundary.tsx +++ b/src/components/ErrorBoundary.tsx @@ -20,11 +20,11 @@ class ErrorBoundary extends Component { return { hasError: true, error }; } - componentDidCatch(error: Error, errorInfo: ErrorInfo): void { + override componentDidCatch(error: Error, errorInfo: ErrorInfo): void { console.error('ErrorBoundary caught an error:', error, errorInfo); } - render(): ReactNode { + override render(): ReactNode { if (this.state.hasError) { if (this.props.fallback) { return this.props.fallback; diff --git a/src/lib/errors.ts b/src/lib/errors.ts index 85bfef4..216d253 100644 --- a/src/lib/errors.ts +++ b/src/lib/errors.ts @@ -1,8 +1,8 @@ export class AppError extends Error { constructor( - message: string, + override message: string, public code: string, - public cause?: unknown + public override cause?: unknown ) { super(message); this.name = 'AppError'; diff --git a/tsconfig.base.json b/tsconfig.base.json index e4ddf30..a801fce 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -5,9 +5,10 @@ "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, + "esModuleInterop": true, "allowSyntheticDefaultImports": true, "strict": true, + "noImplicitOverride": true, "forceConsistentCasingInFileNames": true, "noUnusedLocals": true, "noUnusedParameters": true, @@ -19,7 +20,7 @@ "jsx": "react-jsx", "baseUrl": ".", "paths": { - "@/*": ["src/*"] + "@/*": ["./src/*"] } } }