diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ee6084b5..52c3f232 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,6 +3,8 @@ updates: - package-ecosystem: docker open-pull-requests-limit: 20 directory: "/" + cooldown: + default-days: 7 schedule: interval: weekly day: friday @@ -17,6 +19,8 @@ updates: directories: - "/" - "/actions/**/*" + cooldown: + default-days: 7 schedule: interval: weekly day: friday @@ -29,6 +33,8 @@ updates: - package-ecosystem: npm open-pull-requests-limit: 20 directory: "/actions/parse-ci-reports" + cooldown: + default-days: 7 schedule: interval: weekly day: friday @@ -41,6 +47,8 @@ updates: - package-ecosystem: "devcontainers" open-pull-requests-limit: 20 directory: "/" + cooldown: + default-days: 7 schedule: interval: weekly day: friday diff --git a/.github/workflows/greetings.yml b/.github/workflows/greetings.yml index f7112292..04af4f1f 100644 --- a/.github/workflows/greetings.yml +++ b/.github/workflows/greetings.yml @@ -9,7 +9,8 @@ name: Greetings on: issues: types: [opened] - pull_request_target: + pull_request: + types: [opened] branches: [main] workflow_call: diff --git a/.github/workflows/semantic-pull-request.yml b/.github/workflows/semantic-pull-request.yml index 9db246d6..07cd7ba4 100644 --- a/.github/workflows/semantic-pull-request.yml +++ b/.github/workflows/semantic-pull-request.yml @@ -7,7 +7,7 @@ name: "Semantic Pull Request" on: - pull_request_target: + pull_request: types: - opened - edited diff --git a/actions/checkout/action.yml b/actions/checkout/action.yml index 642d3c8e..f20726d6 100644 --- a/actions/checkout/action.yml +++ b/actions/checkout/action.yml @@ -39,7 +39,7 @@ runs: fetch-depth: ${{ inputs.fetch-depth }} lfs: ${{ inputs.lfs }} token: ${{ inputs.token || github.token }} - persist-credentials: ${{ inputs.persist-credentials }} + persist-credentials: ${{ inputs.persist-credentials == 'true' && 'true' || 'false' }} - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 if: github.event_name != 'issue_comment' @@ -47,4 +47,4 @@ runs: fetch-depth: ${{ inputs.fetch-depth }} lfs: ${{ inputs.lfs }} token: ${{ inputs.token || github.token }} - persist-credentials: ${{ inputs.persist-credentials }} + persist-credentials: ${{ inputs.persist-credentials == 'true' && 'true' || 'false' }} diff --git a/actions/create-and-merge-pull-request/action.yml b/actions/create-and-merge-pull-request/action.yml index 728af114..6f102815 100644 --- a/actions/create-and-merge-pull-request/action.yml +++ b/actions/create-and-merge-pull-request/action.yml @@ -62,16 +62,25 @@ runs: - id: wait-for-pull-request-mergeable-by-admin if: steps.create-pull-request.outputs.pull-request-number && steps.create-pull-request.outputs.pull-request-operation != 'closed' uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + PULL_REQUEST_NUMBER: ${{ steps.create-pull-request.outputs.pull-request-number }} with: github-token: ${{ inputs.github-token }} script: | let attemps = 0; const maxAttemps = 10; + + const pullNumberRaw = process.env.PULL_REQUEST_NUMBER ?? ''; + if (!/^[0-9]+$/.test(pullNumberRaw)) { + throw new Error(`Invalid pull request number: ${pullNumberRaw}`); + } + const pullNumber = Number.parseInt(pullNumberRaw, 10); + while (attemps < maxAttemps) { const { data: { mergeable, mergeable_state } } = await github.rest.pulls.get({ owner: context.repo.owner, repo: context.repo.repo, - pull_number: ${{ steps.create-pull-request.outputs.pull-request-number }}, + pull_number: pullNumber, }); if (mergeable === true) { @@ -92,16 +101,22 @@ runs: shell: bash env: GH_TOKEN: ${{ inputs.github-token }} + PULL_REQUEST_NUMBER: ${{ steps.create-pull-request.outputs.pull-request-number }} run: | set +e + if ! [[ "$PULL_REQUEST_NUMBER" =~ ^[0-9]+$ ]]; then + echo "::error::Invalid pull request number: $PULL_REQUEST_NUMBER" + exit 1 + fi + ATTEMPS=0 MAX_ATTEMPS=10 REQUIRED_WORKFLOWS_ERROR="Required workflow" while [ $ATTEMPS -lt $MAX_ATTEMPS ]; do - echo "::debug::Merging pull request #${{ steps.create-pull-request.outputs.pull-request-number }} for repository ${{ github.repository }}..." - MERGE_OUTPUTS=$(gh pr merge -R "${{ github.repository }}" --rebase --admin "${{ steps.create-pull-request.outputs.pull-request-number }}" 2>&1) + echo "::debug::Merging pull request #${PULL_REQUEST_NUMBER} for repository ${{ github.repository }}..." + MERGE_OUTPUTS=$(gh pr merge -R "${{ github.repository }}" --rebase --admin "${PULL_REQUEST_NUMBER}" 2>&1) MERGE_EXIT_CODE=$? echo "::debug::Merge outputs: $MERGE_OUTPUTS" echo "::debug::Merge exit code: $MERGE_EXIT_CODE" diff --git a/actions/get-matrix-outputs/action.yml b/actions/get-matrix-outputs/action.yml index 54ce92d8..c28f8623 100644 --- a/actions/get-matrix-outputs/action.yml +++ b/actions/get-matrix-outputs/action.yml @@ -24,14 +24,22 @@ runs: using: "composite" steps: - id: prepare-download - shell: bash - run: | - # Forge the unique artifact name for the current workflow - ARTIFACT_NAME="${{ github.run_id }}-${{ github.run_number }}-${{ inputs.artifact-name }}" - echo "artifact-name=$ARTIFACT_NAME" >> "$GITHUB_OUTPUT" + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + ARTIFACT_NAME_INPUT: ${{ inputs.artifact-name }} + with: + script: | + const path = require('node:path'); + const runId = process.env.GITHUB_RUN_ID ?? ''; + const runNumber = process.env.GITHUB_RUN_NUMBER ?? ''; + const artifactNameInput = process.env.ARTIFACT_NAME_INPUT ?? ''; + + // Forge the unique artifact name for the current workflow + const artifactName = `${runId}-${runNumber}-${artifactNameInput}`; + core.setOutput('artifact-name', artifactName); - ARTIFACT_PATH="/tmp/$ARTIFACT_NAME" - echo "artifact-path=$ARTIFACT_PATH" >> "$GITHUB_OUTPUT" + const artifactPath = path.join(process.env.RUNNER_TEMP, artifactName); + core.setOutput('artifact-path', artifactPath); - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: @@ -41,11 +49,14 @@ runs: - id: read-artifacts uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + ARTIFACT_PATH: ${{ steps.prepare-download.outputs.artifact-path }} + REMOVE_ARTIFACT: ${{ inputs.remove-artifact }} with: script: | const { readFileSync } = require('fs'); - const artifactPath = `${{ steps.prepare-download.outputs.artifact-path }}`; + const artifactPath = process.env.ARTIFACT_PATH; const globber = await glob.create(`${artifactPath}/*.json`, {followSymbolicLinks: false}); const artifactFiles = await globber.glob(); @@ -58,7 +69,7 @@ runs: core.setOutput('artifacts',`[${result}]`); - const shouldRemoveArtifact = `${{ inputs.remove-artifact }}` === 'true'; + const shouldRemoveArtifact = (process.env.REMOVE_ARTIFACT || '').toLowerCase() === 'true'; if(shouldRemoveArtifact) { await io.rmRF(artifactPath); } diff --git a/actions/parse-ci-reports/src/formatters/SummaryFormatter.js b/actions/parse-ci-reports/src/formatters/SummaryFormatter.js index 2cbc841c..4ec66def 100644 --- a/actions/parse-ci-reports/src/formatters/SummaryFormatter.js +++ b/actions/parse-ci-reports/src/formatters/SummaryFormatter.js @@ -125,7 +125,7 @@ export class SummaryFormatter extends MarkdownFormatter { // Overall percentage with visual indicator const overall = coverage.getOverallPercentage(); output += `**Overall Coverage: ${this._formatPercentage(overall)}**\n`; - output += this._getCoverageBar(overall) + "\n"; + output += `${this._getCoverageBar(overall)}\n`; return output; } diff --git a/actions/parse-ci-reports/src/parsers/AstroCheckParser.js b/actions/parse-ci-reports/src/parsers/AstroCheckParser.js index 626cf085..47e7f205 100644 --- a/actions/parse-ci-reports/src/parsers/AstroCheckParser.js +++ b/actions/parse-ci-reports/src/parsers/AstroCheckParser.js @@ -12,7 +12,7 @@ const ANSI_PATTERN = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g"); * Parser for output generated by `astro check` */ export class AstroCheckParser extends BaseParser { - canParse(filePath, content) { + canParse(_filePath, content) { if (!content) { return false; } @@ -90,7 +90,7 @@ export class AstroCheckParser extends BaseParser { const severity = locationMatch[3].toLowerCase(); const remainderStart = locationMatch.index + locationMatch[0].length; - let remainder = line.slice(remainderStart).trim(); + const remainder = line.slice(remainderStart).trim(); let rule = "astro-check"; let message = remainder; diff --git a/actions/parse-ci-reports/src/parsers/BaseParser.js b/actions/parse-ci-reports/src/parsers/BaseParser.js index e908645b..876f2186 100644 --- a/actions/parse-ci-reports/src/parsers/BaseParser.js +++ b/actions/parse-ci-reports/src/parsers/BaseParser.js @@ -19,7 +19,7 @@ export class BaseParser { * @returns {ReportData} Parsed report data */ // eslint-disable-next-line no-unused-vars - parse(content, filePath) { + parse(_content, _filePath) { throw new Error("parse() must be implemented by subclass"); } @@ -30,7 +30,7 @@ export class BaseParser { * @returns {boolean} True if this parser can handle the file */ // eslint-disable-next-line no-unused-vars - canParse(filePath, content) { + canParse(_filePath, _content) { throw new Error("canParse() must be implemented by subclass"); } diff --git a/actions/parse-ci-reports/src/parsers/CheckStyleParser.js b/actions/parse-ci-reports/src/parsers/CheckStyleParser.js index 8c77407d..099da99f 100644 --- a/actions/parse-ci-reports/src/parsers/CheckStyleParser.js +++ b/actions/parse-ci-reports/src/parsers/CheckStyleParser.js @@ -74,8 +74,8 @@ export class CheckStyleParser extends BaseParser { for (const error of errors) { const issue = new LintIssue({ file: fileName, - line: parseInt(error["@_line"] || 0), - column: parseInt(error["@_column"] || 0), + line: parseInt(error["@_line"] || 0, 10), + column: parseInt(error["@_column"] || 0, 10), severity: this._mapSeverity(error["@_severity"]), rule: error["@_source"] || "unknown", message: error["@_message"] || "", diff --git a/actions/parse-ci-reports/src/parsers/CoberturaParser.js b/actions/parse-ci-reports/src/parsers/CoberturaParser.js index 0676d6ef..81ec2404 100644 --- a/actions/parse-ci-reports/src/parsers/CoberturaParser.js +++ b/actions/parse-ci-reports/src/parsers/CoberturaParser.js @@ -61,7 +61,7 @@ export class CoberturaParser extends BaseParser { let totalBranches = 0; let coveredBranches = 0; - if (coverage.packages && coverage.packages.package) { + if (coverage.packages?.package) { const packages = Array.isArray(coverage.packages.package) ? coverage.packages.package : [coverage.packages.package]; @@ -114,20 +114,20 @@ export class CoberturaParser extends BaseParser { let totalBranches = 0; let coveredBranches = 0; - if (pkg.classes && pkg.classes.class) { + if (pkg.classes?.class) { const classes = Array.isArray(pkg.classes.class) ? pkg.classes.class : [pkg.classes.class]; for (const cls of classes) { - if (cls.lines && cls.lines.line) { + if (cls.lines?.line) { const lines = Array.isArray(cls.lines.line) ? cls.lines.line : [cls.lines.line]; for (const line of lines) { totalLines++; - const hits = parseInt(line["@_hits"] || 0); + const hits = parseInt(line["@_hits"] || 0, 10); if (hits > 0) { coveredLines++; } @@ -138,8 +138,8 @@ export class CoberturaParser extends BaseParser { const conditionCoverage = line["@_condition-coverage"] || "0%"; const match = conditionCoverage.match(/(\d+)\/(\d+)/); if (match) { - const covered = parseInt(match[1]); - const total = parseInt(match[2]); + const covered = parseInt(match[1], 10); + const total = parseInt(match[2], 10); totalBranches += total; coveredBranches += covered; } diff --git a/actions/parse-ci-reports/src/parsers/ESLintParser.js b/actions/parse-ci-reports/src/parsers/ESLintParser.js index eeb28f49..c17d71ee 100644 --- a/actions/parse-ci-reports/src/parsers/ESLintParser.js +++ b/actions/parse-ci-reports/src/parsers/ESLintParser.js @@ -6,15 +6,15 @@ import { ReportData, LintIssue } from "../models/ReportData.js"; * Standard format for ESLint output */ export class ESLintParser extends BaseParser { - canParse(filePath, content) { + canParse(_filePath, content) { try { const data = JSON.parse(content); // ESLint format is an array of file results return ( Array.isArray(data) && data.length > 0 && - Object.prototype.hasOwnProperty.call(data[0], "filePath") && - Object.prototype.hasOwnProperty.call(data[0], "messages") + Object.hasOwn(data[0], "filePath") && + Object.hasOwn(data[0], "messages") ); } catch { return false; diff --git a/actions/parse-ci-reports/src/parsers/LCOVParser.js b/actions/parse-ci-reports/src/parsers/LCOVParser.js index c1869881..dff7beb1 100644 --- a/actions/parse-ci-reports/src/parsers/LCOVParser.js +++ b/actions/parse-ci-reports/src/parsers/LCOVParser.js @@ -45,7 +45,7 @@ export class LCOVParser extends BaseParser { if (trimmed.startsWith("DA:")) { totalLines++; const parts = trimmed.substring(3).split(","); - const hits = parseInt(parts[1] || 0); + const hits = parseInt(parts[1] || 0, 10); if (hits > 0) { coveredLines++; } @@ -54,7 +54,7 @@ export class LCOVParser extends BaseParser { else if (trimmed.startsWith("FNDA:")) { totalFunctions++; const parts = trimmed.substring(5).split(","); - const hits = parseInt(parts[0] || 0); + const hits = parseInt(parts[0] || 0, 10); if (hits > 0) { coveredFunctions++; } @@ -64,7 +64,7 @@ export class LCOVParser extends BaseParser { totalBranches++; const parts = trimmed.substring(5).split(","); const taken = parts[3]; - if (taken !== "-" && parseInt(taken) > 0) { + if (taken !== "-" && parseInt(taken, 10) > 0) { coveredBranches++; } } diff --git a/actions/parse-ci-reports/src/parsers/PrettierParser.js b/actions/parse-ci-reports/src/parsers/PrettierParser.js index f699a689..f35ef679 100644 --- a/actions/parse-ci-reports/src/parsers/PrettierParser.js +++ b/actions/parse-ci-reports/src/parsers/PrettierParser.js @@ -26,7 +26,7 @@ export class PrettierParser extends BaseParser { return true; } - if (filePath && filePath.toLowerCase().includes("prettier")) { + if (filePath?.toLowerCase().includes("prettier")) { return /\[(warn|error)\]\s+.+/i.test(content); } diff --git a/actions/parse-ci-reports/src/parsers/TAPParser.js b/actions/parse-ci-reports/src/parsers/TAPParser.js index 66550d34..27514954 100644 --- a/actions/parse-ci-reports/src/parsers/TAPParser.js +++ b/actions/parse-ci-reports/src/parsers/TAPParser.js @@ -6,7 +6,7 @@ import { ReportData, TestResult } from "../models/ReportData.js"; * Supports TAP versions 12, 13, and 14 */ export class TAPParser extends BaseParser { - canParse(filePath, content) { + canParse(_filePath, content) { const lines = content.split("\n"); // TAP files typically start with TAP version or test plan return lines.some( @@ -54,7 +54,7 @@ export class TAPParser extends BaseParser { const match = line.match(/^(ok|not ok)\s+(\d+)?\s*-?\s*(.*)/); if (match) { const [, status, number, description] = match; - testNumber = number ? parseInt(number) : testNumber + 1; + testNumber = number ? parseInt(number, 10) : testNumber + 1; const test = this._parseTestLine( status, diff --git a/actions/repository-owner-is-organization/action.yml b/actions/repository-owner-is-organization/action.yml index 1d098a53..3f2fc743 100644 --- a/actions/repository-owner-is-organization/action.yml +++ b/actions/repository-owner-is-organization/action.yml @@ -20,12 +20,13 @@ runs: using: "composite" steps: - id: check-org - run: | - OWNER_TYPE=$(curl -s -H "Authorization: token ${{ inputs.github-token }}" \ - "https://api.github.com/users/${{ github.repository_owner }}" | jq -r .type) - - if [ "$OWNER_TYPE" = "Organization" ]; then - echo "is-organization=true" >> "$GITHUB_OUTPUT" - fi - - shell: bash + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + github-token: ${{ inputs.github-token }} + script: | + const owner = context.repo.owner; + const { data } = await github.rest.users.getByUsername({ username: owner }); + const isOrganization = (data?.type ?? '').toLowerCase() === 'organization'; + if (isOrganization) { + core.setOutput('is-organization', true); + } diff --git a/actions/set-matrix-output/action.yml b/actions/set-matrix-output/action.yml index fce05ff6..acfe761e 100644 --- a/actions/set-matrix-output/action.yml +++ b/actions/set-matrix-output/action.yml @@ -24,13 +24,16 @@ runs: steps: - id: prepare-upload uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + ARTIFACT_NAME_INPUT: ${{ inputs.artifact-name }} + MATRIX_VALUE: ${{ inputs.value }} with: script: | const { join } = require('path'); const { writeFileSync } = require('fs'); const { randomUUID } = require('crypto'); - const artifactName = `${{ github.run_id }}-${{ github.run_number }}-${{ inputs.artifact-name }}`; + const artifactName = `${process.env.GITHUB_RUN_ID}-${process.env.GITHUB_RUN_NUMBER}-${process.env.ARTIFACT_NAME_INPUT}`; core.setOutput("artifact-name", artifactName); const uniquid = randomUUID(); @@ -43,7 +46,7 @@ runs: const artifactPath = join(artifactDirPath, `${artifactUniqueName}.json`); core.setOutput("artifact-path", artifactPath); - writeFileSync(artifactPath, `${{ inputs.value }}`); + writeFileSync(artifactPath, process.env.MATRIX_VALUE ?? ""); - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: diff --git a/actions/slugify/action.yml b/actions/slugify/action.yml index 0768e92b..e83dd9ac 100644 --- a/actions/slugify/action.yml +++ b/actions/slugify/action.yml @@ -13,13 +13,31 @@ inputs: outputs: result: description: "The slugified value" - value: ${{ steps.slugifier.outputs.result }} + value: ${{ steps.slugifier.outputs.slug }} runs: using: "composite" steps: - id: slugifier - shell: bash - run: | - SLUGIFIED_VALUE=$(echo "${{ inputs.value }}" | iconv -t ascii//TRANSLIT | sed -E -e 's/[^[:alnum:]]+/-/g' -e 's/^-+|-+$//g' | tr '[:upper:]' '[:lower:]') - echo "result=${SLUGIFIED_VALUE}" >> $GITHUB_OUTPUT + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + SLUGIFY_INPUT: ${{ inputs.value }} + with: + script: | + const input = process.env.SLUGIFY_INPUT ?? ''; + const slug = input + // Normalize common dash variants to hyphen + .replace(/[\u2010-\u2015\u2212]/g, "-") + .normalize("NFKD") + .toLowerCase() + .trim() + // Remove combining diacritical marks + .replace(/[\u0300-\u036f]/g, "") + // Treat separators as hyphen (NOW includes dot ".") + .replace(/[\s_/\\.-]+/g, "-") + // Keep letters/numbers (any script) + hyphen + .replace(/[^\p{Letter}\p{Number}-]+/gu, "") + // Collapse + trim hyphens + .replace(/-+/g, "-").replace(/^-|-$/g, ""); + + core.setOutput('slug', slug);