diff --git a/src/core/registry.ts b/src/core/registry.ts index 203c456..c1ec5eb 100644 --- a/src/core/registry.ts +++ b/src/core/registry.ts @@ -118,10 +118,12 @@ export class WorkflowRegistry { const files = await this.safeReadDir(workflowPath); if (!files) continue; - const yamlFiles = files.filter((file) => file.endsWith('.yml') || file.endsWith('.yaml')); - if (yamlFiles.length === 0) continue; + const workflowFiles = files.filter((file) => + file.endsWith('.yml') || file.endsWith('.yaml') || file.endsWith('.properties') + ); + if (workflowFiles.length === 0) continue; - const grouped = this.groupByBaseName(yamlFiles); + const grouped = this.groupByBaseName(workflowFiles); for (const [baseName, variants] of grouped) { const workflow = await this.parseWorkflow({ category, @@ -149,8 +151,10 @@ export class WorkflowRegistry { const files = await this.safeReadDir(typePath); if (!files) continue; - const yamlFiles = files.filter((file) => file.endsWith('.yml') || file.endsWith('.yaml')); - const grouped = this.groupByBaseName(yamlFiles); + const workflowFiles = files.filter((file) => + file.endsWith('.yml') || file.endsWith('.yaml') || file.endsWith('.properties') + ); + const grouped = this.groupByBaseName(workflowFiles); for (const [baseName, variants] of grouped) { const workflowType = this.deriveWorkflowType(baseName, category.id); @@ -215,7 +219,7 @@ export class WorkflowRegistry { return weight !== 0 ? weight : a.name.localeCompare(b.name); }); - const workflowId = `${args.category.id}/${args.workflowType}`; + const workflowId = metadata.id ?? `${args.category.id}/${args.workflowType}`; return { id: workflowId, diff --git a/workflows/ci/sonarqube/sonar-project.properties b/workflows/ci/sonarqube/sonar-project.properties new file mode 100644 index 0000000..9bb63ad --- /dev/null +++ b/workflows/ci/sonarqube/sonar-project.properties @@ -0,0 +1,62 @@ +# --- +# id: ci/sonarqube-config +# category: ci +# type: template +# name: SonarQube Configuration +# description: sonar-project.properties configuration file for PHP/Drupal projects +# targetPath: sonar-project.properties +# secrets: +# - name: SONAR_TOKEN +# description: SonarQube authentication token +# required: true +# - name: SONAR_HOST_URL +# description: SonarQube server URL (e.g., https://sonar.example.com) +# required: true +# triggers: [] +# variants: +# - name: standard +# description: PHP/Drupal project configuration +# --- + +# ============================================================================= +# SonarQube Project Configuration for PHP/Drupal +# ============================================================================= +# Copy this file to your project root and customize the values below. +# Required secrets in GitHub: SONAR_TOKEN, SONAR_HOST_URL + +# Project identification (REQUIRED - change these for your project) +sonar.projectKey=your-project-key +sonar.projectName=Your Project Name +sonar.projectVersion=1.0 + +# SonarQube server URL (set via SONAR_HOST_URL secret, or uncomment below) +# sonar.host.url=https://sonar.example.com + +# Source paths (relative to project root) +sonar.sources=web/modules/custom,web/themes/custom + +# Encoding +sonar.sourceEncoding=UTF-8 + +# Test directories (adjust to match your project structure) +# sonar.tests=web/modules/custom/your_module/tests + +# Exclusions - third-party libraries, generated files, and Drupal core/contrib +sonar.exclusions=**/vendor/**,**/node_modules/**,**/libraries/**,**/dist/** +sonar.exclusions+=**/css/**,**/*.min.js,**/*.min.css +sonar.exclusions+=**/tests/**,**/test/**,**/spec/** +sonar.exclusions+=web/core/**,web/modules/contrib/**,web/themes/contrib/** +sonar.exclusions+=web/profiles/contrib/**,web/libraries/**,**/config/**,**/files/** + +# PHP-specific settings +sonar.php.file.suffixes=php,module,inc,install,profile,theme + +# Coverage exclusions (files that don't need test coverage) +sonar.coverage.exclusions=**/*.xml,**/*.yml,**/*.yaml,**/*.md + +# GitHub integration +sonar.pullrequest.provider=github +sonar.pullrequest.github.repository=your-org/your-repo + +# Analysis scope +sonar.scm.revision=${env.GITHUB_SHA} diff --git a/workflows/ci/sonarqube/sonarqube.yml b/workflows/ci/sonarqube/sonarqube.yml new file mode 100644 index 0000000..6d4a3fa --- /dev/null +++ b/workflows/ci/sonarqube/sonarqube.yml @@ -0,0 +1,103 @@ +# --- +# id: ci/sonarqube +# category: ci +# type: set +# name: SonarQube Analysis +# description: Self-hosted SonarQube scan with SARIF export to GitHub Security Tab +# targetPath: .github/workflows/sonarqube.yml +# secrets: +# - name: SONAR_TOKEN +# description: SonarQube authentication token +# required: true +# - name: SONAR_HOST_URL +# description: SonarQube server URL (e.g., https://sonar.example.com) +# required: true +# triggers: +# - push +# - pull_request +# variants: +# - name: standard +# description: Self-hosted SonarQube with GitHub Security integration +# --- + +name: SonarQube Analysis + +on: + push: + branches: + - main + - master + pull_request: + branches: + - main + - master + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + sonarqube-analysis: + name: SonarQube Analysis + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + checks: write + pull-requests: write + actions: read + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + coverage: none + + - name: Get Composer Cache Directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - name: Cache Composer dependencies + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer- + + - name: Install Composer dependencies + run: composer install --prefer-dist --no-progress --no-suggest --optimize-autoloader + + - name: SonarQube Scan + uses: SonarSource/sonarqube-scan-action@v4 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} + + - name: SonarQube → GitHub Security + uses: vmvarela/sonarqube-ce-sarif-action@v1 + with: + sonar-host-url: ${{ secrets.SONAR_HOST_URL }} + sonar-token: ${{ secrets.SONAR_TOKEN }} + wait-for-processing: false + processing-delay: 60 + + - name: Fix SARIF locations + run: | + jq 'walk(if type == "object" and has("results") then .results |= map(select(.locations != null and (.locations | length) > 0)) else . end)' sonarqube.sarif > sonarqube-fixed.sarif + mv sonarqube-fixed.sarif sonarqube.sarif + echo "Filtered SARIF file - removed results without locations" + + - name: Upload SARIF to GitHub Security Tab + if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master') + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: sonarqube.sarif + category: sonarqube