feat: dx improvements #52
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| security-events: write | |
| actions: read | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| FORCE_COLOR: 1 | |
| NODE_OPTIONS: --max-old-space-size=4096 | |
| CI: true | |
| VITEST_MIN_COVERAGE: 80 | |
| jobs: | |
| security-scan: | |
| name: Security Scanning | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0 | |
| with: | |
| fetch-depth: 0 | |
| - name: Run Trivy vulnerability scanner | |
| uses: aquasecurity/trivy-action@a20de5420d57c4102486cdd9578b45609c99d7eb # v0.26.0 | |
| with: | |
| scan-type: 'fs' | |
| scan-ref: '.' | |
| format: 'sarif' | |
| output: 'trivy-results.sarif' | |
| severity: 'CRITICAL,HIGH,MEDIUM' | |
| - name: Upload Trivy scan results to GitHub Security tab | |
| uses: github/codeql-action/upload-sarif@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 | |
| if: always() | |
| with: | |
| sarif_file: 'trivy-results.sarif' | |
| - name: Run secret detection with GitLeaks | |
| run: | | |
| echo "Installing gitleaks for secret detection..." | |
| curl -sSfL https://github.com/gitleaks/gitleaks/releases/latest/download/gitleaks_linux_x64.tar.gz | tar xz | |
| chmod +x gitleaks | |
| echo "Scanning for secrets in repository..." | |
| ./gitleaks detect --source . --verbose --report-format sarif --report-path gitleaks-report.sarif || { | |
| echo "::warning::Secret detection scan completed with findings" | |
| cat gitleaks-report.sarif | |
| } | |
| echo "Secret detection scan completed" | |
| license-compliance: | |
| name: License Compliance | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0 | |
| - name: Setup mise | |
| uses: jdx/mise-action@5ac50f778e26fac95da98d50503682459e86d566 # v3.2.0 | |
| with: | |
| install: true | |
| cache: true | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Check license headers in source files | |
| run: | | |
| echo "Checking for MIT license headers in TypeScript source files..." | |
| missing_headers=() | |
| while IFS= read -r -d '' file; do | |
| if ! head -5 "$file" | grep -q "MIT License\|SPDX-License-Identifier: MIT"; then | |
| missing_headers+=("$file") | |
| fi | |
| done < <(find src/ -name "*.ts" -type f -print0) | |
| if [ ${#missing_headers[@]} -ne 0 ]; then | |
| echo "::warning::The following files are missing license headers:" | |
| printf '%s\n' "${missing_headers[@]}" | |
| echo "::notice::License headers will be auto-added in future commits" | |
| else | |
| echo "All source files have proper license headers" | |
| fi | |
| - name: Validate dependency licenses | |
| run: pnpm run license-check | |
| setup: | |
| name: Setup and Cache | |
| runs-on: ubuntu-latest | |
| outputs: | |
| cache-key: ${{ steps.cache-keys.outputs.cache-key }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0 | |
| - name: Setup mise | |
| uses: jdx/mise-action@5ac50f778e26fac95da98d50503682459e86d566 # v3.2.0 | |
| with: | |
| install: true | |
| cache: true | |
| - name: Generate cache keys | |
| id: cache-keys | |
| run: | | |
| echo "cache-key=node-modules-${{ hashFiles('pnpm-lock.yaml') }}" >> "$GITHUB_OUTPUT" | |
| - name: Cache node modules | |
| uses: actions/cache@638ed79f9dc94c1de1baef91bcab5edaa19451f4 # v4.2.4 | |
| with: | |
| path: ~/.pnpm-store | |
| key: ${{ steps.cache-keys.outputs.cache-key }} | |
| restore-keys: | | |
| node-modules- | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| typecheck: | |
| name: Type Check | |
| runs-on: ubuntu-latest | |
| needs: setup | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0 | |
| - name: Setup mise | |
| uses: jdx/mise-action@5ac50f778e26fac95da98d50503682459e86d566 # v3.2.0 | |
| with: | |
| install: true | |
| cache: true | |
| - name: Cache node modules | |
| uses: actions/cache@638ed79f9dc94c1de1baef91bcab5edaa19451f4 # v4.2.4 | |
| with: | |
| path: ~/.pnpm-store | |
| key: ${{ needs.setup.outputs.cache-key }} | |
| restore-keys: | | |
| node-modules- | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Run type checking | |
| run: pnpm run check | |
| lint: | |
| name: Lint & Format | |
| runs-on: ubuntu-latest | |
| needs: setup | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0 | |
| - name: Setup mise | |
| uses: jdx/mise-action@5ac50f778e26fac95da98d50503682459e86d566 # v3.2.0 | |
| with: | |
| install: true | |
| cache: true | |
| - name: Cache node modules | |
| uses: actions/cache@638ed79f9dc94c1de1baef91bcab5edaa19451f4 # v4.2.4 | |
| with: | |
| path: ~/.pnpm-store | |
| key: ${{ needs.setup.outputs.cache-key }} | |
| restore-keys: | | |
| node-modules- | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Check formatting | |
| run: pnpm run format:check | |
| - name: Run linter | |
| run: pnpm run lint --max-warnings=0 | |
| test: | |
| name: Test | |
| runs-on: ubuntu-latest | |
| needs: setup | |
| strategy: | |
| matrix: | |
| node-version: [22, 24] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0 | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@f2b2b233b538f500472c7274c7012f57857d8ce0 # v4.1.0 | |
| - name: Setup Node.js ${{ matrix.node-version }} | |
| uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 | |
| with: | |
| node-version: ${{ matrix.node-version }} | |
| - name: Cache node modules | |
| uses: actions/cache@638ed79f9dc94c1de1baef91bcab5edaa19451f4 # v4.2.4 | |
| with: | |
| path: ~/.pnpm-store | |
| key: ${{ needs.setup.outputs.cache-key }} | |
| restore-keys: | | |
| node-modules- | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Run tests with coverage | |
| run: pnpm run test:coverage | |
| - name: Upload coverage to Codecov | |
| if: matrix.node-version == 22 | |
| uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 | |
| with: | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| files: ./coverage/lcov.info | |
| fail_ci_if_error: false | |
| verbose: true | |
| build: | |
| name: Build & Package | |
| runs-on: ubuntu-latest | |
| needs: setup | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0 | |
| - name: Setup mise | |
| uses: jdx/mise-action@5ac50f778e26fac95da98d50503682459e86d566 # v3.2.0 | |
| with: | |
| install: true | |
| cache: true | |
| - name: Cache node modules | |
| uses: actions/cache@638ed79f9dc94c1de1baef91bcab5edaa19451f4 # v4.2.4 | |
| with: | |
| path: ~/.pnpm-store | |
| key: ${{ needs.setup.outputs.cache-key }} | |
| restore-keys: | | |
| node-modules- | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Build TypeScript | |
| run: pnpm run build | |
| - name: Package action | |
| run: pnpm run package | |
| - name: Generate and validate SBOM | |
| run: | | |
| echo "Validating SBOM generation and format..." | |
| if [ ! -f "dist/sbom.json" ]; then | |
| echo "::error::SBOM file not found at dist/sbom.json" | |
| exit 1 | |
| fi | |
| # Validate SBOM is valid JSON | |
| if ! jq empty dist/sbom.json; then | |
| echo "::error::SBOM is not valid JSON" | |
| exit 1 | |
| fi | |
| # Validate SBOM has required CycloneDX fields | |
| required_fields=("bomFormat" "specVersion" "metadata" "components") | |
| for field in "${required_fields[@]}"; do | |
| if ! jq -e ".${field}" dist/sbom.json > /dev/null; then | |
| echo "::error::SBOM missing required field: ${field}" | |
| exit 1 | |
| fi | |
| done | |
| echo "SBOM validation passed" | |
| - name: Verify dist is up to date | |
| run: | | |
| if [ "$(git diff --ignore-space-at-eol --text dist/ | wc -l)" -gt "0" ]; then | |
| echo "::error::Detected uncommitted changes after build. Please run 'pnpm run build && pnpm run package' and commit the changes." | |
| echo "Changed files:" | |
| git diff --ignore-space-at-eol --text --name-only dist/ | |
| echo "Diff:" | |
| git diff --ignore-space-at-eol --text dist/ | |
| exit 1 | |
| fi | |
| - name: Upload build artifacts | |
| uses: actions/upload-artifact@de65e23aa2b7e23d713bb51fbfcb6d502f8667d8 # v4.6.2 | |
| with: | |
| name: dist-${{ github.sha }} | |
| path: | | |
| dist/ | |
| coverage/ | |
| retention-days: 30 | |
| - name: Upload SBOM artifact | |
| uses: actions/upload-artifact@de65e23aa2b7e23d713bb51fbfcb6d502f8667d8 # v4.6.2 | |
| with: | |
| name: sbom-${{ github.sha }} | |
| path: dist/sbom.json | |
| retention-days: 90 | |
| integration-test: | |
| name: Integration Test | |
| runs-on: ubuntu-latest | |
| needs: [build] | |
| permissions: | |
| pull-requests: read | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0 | |
| - name: Download build artifacts | |
| uses: actions/download-artifact@abefc31eafcfbdf6c5336127c1346fdae79ff41c # v5.0.0 | |
| with: | |
| name: dist-${{ github.sha }} | |
| path: dist/ | |
| - name: Test action | |
| uses: ./ | |
| id: filter | |
| with: | |
| filters: | | |
| workflows: | |
| - '.github/workflows/**' | |
| src: | |
| - 'src/**' | |
| tests: | |
| - '__tests__/**' | |
| docs: | |
| - '*.md' | |
| - 'docs/**' | |
| - name: Display results | |
| run: | | |
| { | |
| echo "## Integration Test Results"; | |
| echo ""; | |
| echo "| Filter | Changed | Count | Files |"; | |
| echo "|--------|---------|-------|-------|"; | |
| echo "| Workflows | ${{ steps.filter.outputs.workflows }} | ${{ steps.filter.outputs.workflows_count }} | ${{ steps.filter.outputs.workflows_files }} |"; | |
| echo "| Source | ${{ steps.filter.outputs.src }} | ${{ steps.filter.outputs.src_count }} | - |"; | |
| echo "| Tests | ${{ steps.filter.outputs.tests }} | ${{ steps.filter.outputs.tests_count }} | - |"; | |
| echo "| Docs | ${{ steps.filter.outputs.docs }} | ${{ steps.filter.outputs.docs_count }} | - |"; | |
| echo "| Changes | ${{ steps.filter.outputs.changes }} | - | - |"; | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| all-checks: | |
| name: All Checks Passed | |
| runs-on: ubuntu-latest | |
| needs: [security-scan, license-compliance, typecheck, lint, test, build, integration-test] | |
| if: always() | |
| steps: | |
| - name: Check all jobs | |
| run: | | |
| if [[ "${{ needs.security-scan.result }}" != "success" || | |
| "${{ needs.license-compliance.result }}" != "success" || | |
| "${{ needs.typecheck.result }}" != "success" || | |
| "${{ needs.lint.result }}" != "success" || | |
| "${{ needs.test.result }}" != "success" || | |
| "${{ needs.build.result }}" != "success" || | |
| "${{ needs.integration-test.result }}" != "success" ]]; then | |
| echo "One or more jobs failed" | |
| exit 1 | |
| fi | |
| echo "All security and quality checks passed!" |