Skip to content

feat: dx improvements #52

feat: dx improvements

feat: dx improvements #52

Workflow file for this run

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!"