diff options
| author | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-04 19:47:58 +0300 |
|---|---|---|
| committer | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-04 19:47:58 +0300 |
| commit | 8d0d919fbf43230148da7533519ed0ffdfaa4197 (patch) | |
| tree | 27e352d6ca09910e577ec27a10659814e88b15b9 /.github | |
| parent | fce202465d4fede9e19d4d057eebbaa702291652 (diff) | |
| download | Project-Tick-8d0d919fbf43230148da7533519ed0ffdfaa4197.tar.gz Project-Tick-8d0d919fbf43230148da7533519ed0ffdfaa4197.zip | |
NOISSUE add GitHub Actions scripts for PR preparation and review management
- Introduced `prepare.js` to validate PR mergeability and branch targeting.
- Added `reviews.js` for automated review dismissal and posting.
- Created `run` script to execute actions with GitHub context.
- Implemented rate limiting in `withRateLimit.js` to manage API requests.
- Added `supportedBranches.js` for branch classification logic.
- Created `update-pinned.sh` for updating pinned dependencies.
- Added `pinned.json` to manage pinned Nix dependencies.
- Updated `libnbtplusplus` version from 2.3 to 3.0 and adjusted README accordingly.
Signed-off-by: Mehmet Samet Duman <yongdohyun@projecttick.org>
Diffstat (limited to '.github')
40 files changed, 1408 insertions, 289 deletions
diff --git a/.github/actions/change-analysis/action.yml b/.github/actions/change-analysis/action.yml new file mode 100644 index 0000000000..84494fa821 --- /dev/null +++ b/.github/actions/change-analysis/action.yml @@ -0,0 +1,289 @@ +# Copyright (C) Project Tick Contributors +# SPDX-License-Identifier: MIT +# +# Composite action — analyzes changed directories and parses commit messages. +# Exposes per-project boolean outputs + parsed commit info for the caller. + +name: "Change Analysis" +description: > + Detects which project directories changed in this commit/PR and parses the + commit message (Conventional Commits). Outputs per-project flags and a + summary report. + +# ── Inputs ────────────────────────────────────────────────────── +inputs: + event-name: + description: "github.event_name (push, pull_request, workflow_dispatch)" + required: true + base-sha: + description: "Base SHA for diff (PR base or push before)" + required: false + default: "" + head-sha: + description: "Head SHA for diff" + required: false + default: "" + before-sha: + description: "Push event before SHA" + required: false + default: "" + current-sha: + description: "Current commit SHA (github.sha)" + required: true + pr-title: + description: "Pull request title (for commit message parsing on PRs)" + required: false + default: "" + +# ── Outputs ───────────────────────────────────────────────────── +outputs: + # Per-project change flags + archived_changed: + description: "true if archived/ was modified" + value: ${{ steps.detect.outputs.archived_changed }} + cgit_changed: + description: "true if cgit/ was modified" + value: ${{ steps.detect.outputs.cgit_changed }} + cmark_changed: + description: "true if cmark/ was modified" + value: ${{ steps.detect.outputs.cmark_changed }} + corebinutils_changed: + description: "true if corebinutils/ was modified" + value: ${{ steps.detect.outputs.corebinutils_changed }} + forgewrapper_changed: + description: "true if forgewrapper/ was modified" + value: ${{ steps.detect.outputs.forgewrapper_changed }} + genqrcode_changed: + description: "true if genqrcode/ was modified" + value: ${{ steps.detect.outputs.genqrcode_changed }} + hooks_changed: + description: "true if hooks/ was modified" + value: ${{ steps.detect.outputs.hooks_changed }} + images4docker_changed: + description: "true if images4docker/ was modified" + value: ${{ steps.detect.outputs.images4docker_changed }} + json4cpp_changed: + description: "true if json4cpp/ was modified" + value: ${{ steps.detect.outputs.json4cpp_changed }} + libnbtplusplus_changed: + description: "true if libnbtplusplus/ was modified" + value: ${{ steps.detect.outputs.libnbtplusplus_changed }} + meshmc_changed: + description: "true if meshmc/ was modified" + value: ${{ steps.detect.outputs.meshmc_changed }} + meta_changed: + description: "true if meta/ was modified" + value: ${{ steps.detect.outputs.meta_changed }} + mnv_changed: + description: "true if mnv/ was modified" + value: ${{ steps.detect.outputs.mnv_changed }} + neozip_changed: + description: "true if neozip/ was modified" + value: ${{ steps.detect.outputs.neozip_changed }} + tomlplusplus_changed: + description: "true if tomlplusplus/ was modified" + value: ${{ steps.detect.outputs.tomlplusplus_changed }} + ci_changed: + description: "true if ci/ was modified" + value: ${{ steps.detect.outputs.ci_changed }} + github_changed: + description: "true if .github/ was modified" + value: ${{ steps.detect.outputs.github_changed }} + root_changed: + description: "true if root-level files were modified" + value: ${{ steps.detect.outputs.root_changed }} + # Aggregate + changed_projects: + description: "Comma-separated list of changed project names" + value: ${{ steps.detect.outputs.changed_projects }} + changed_count: + description: "Number of changed projects" + value: ${{ steps.detect.outputs.changed_count }} + # Parsed commit + commit_type: + description: "Conventional commit type (feat, fix, chore, …)" + value: ${{ steps.parse-commit.outputs.type }} + commit_scope: + description: "Conventional commit scope" + value: ${{ steps.parse-commit.outputs.scope }} + commit_subject: + description: "Commit subject line" + value: ${{ steps.parse-commit.outputs.subject }} + commit_breaking: + description: "true if this is a breaking change" + value: ${{ steps.parse-commit.outputs.breaking }} + commit_message: + description: "Full commit message / PR title" + value: ${{ steps.parse-commit.outputs.full_message }} + +# ── Steps ─────────────────────────────────────────────────────── +runs: + using: "composite" + steps: + - name: Detect changed directories + id: detect + shell: bash + run: | + set -euo pipefail + + EVENT_NAME="${{ inputs.event-name }}" + BASE_SHA="${{ inputs.base-sha }}" + HEAD_SHA="${{ inputs.head-sha }}" + BEFORE_SHA="${{ inputs.before-sha }}" + CURRENT_SHA="${{ inputs.current-sha }}" + + # Determine the list of changed files + if [[ "$EVENT_NAME" == "pull_request" ]]; then + CHANGED_FILES=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA" --) + elif [[ "$EVENT_NAME" == "push" ]]; then + if [[ "$BEFORE_SHA" == "0000000000000000000000000000000000000000" ]]; then + CHANGED_FILES=$(git diff-tree --no-commit-id --name-only -r HEAD) + else + CHANGED_FILES=$(git diff --name-only "$BEFORE_SHA" "$CURRENT_SHA" --) + fi + else + # workflow_dispatch / other: compare with parent + CHANGED_FILES=$(git diff-tree --no-commit-id --name-only -r HEAD) + fi + + echo "::group::Changed files" + echo "$CHANGED_FILES" + echo "::endgroup::" + + # All projects to detect + PROJECTS=( + archived cgit ci cmark corebinutils forgewrapper genqrcode + hooks images4docker json4cpp libnbtplusplus meshmc meta + mnv neozip tomlplusplus + ) + + CHANGED_PROJECTS=() + + for proj in "${PROJECTS[@]}"; do + if echo "$CHANGED_FILES" | grep -q "^${proj}/"; then + echo "${proj}_changed=true" >> "$GITHUB_OUTPUT" + CHANGED_PROJECTS+=("$proj") + else + echo "${proj}_changed=false" >> "$GITHUB_OUTPUT" + fi + done + + # .github/ changes + if echo "$CHANGED_FILES" | grep -q "^\.github/"; then + echo "github_changed=true" >> "$GITHUB_OUTPUT" + CHANGED_PROJECTS+=(".github") + else + echo "github_changed=false" >> "$GITHUB_OUTPUT" + fi + + # Root-level files (CODEOWNERS, README, SECURITY, etc.) + ROOT_MATCHES=$(echo "$CHANGED_FILES" | grep -vE "^(archived|cgit|ci|cmark|corebinutils|forgewrapper|genqrcode|hooks|images4docker|json4cpp|libnbtplusplus|meshmc|meta|mnv|neozip|tomlplusplus|LICENSES|\.github)/" || true) + if [[ -n "$ROOT_MATCHES" ]]; then + echo "root_changed=true" >> "$GITHUB_OUTPUT" + CHANGED_PROJECTS+=("root") + else + echo "root_changed=false" >> "$GITHUB_OUTPUT" + fi + + # Build comma-separated list + if [[ ${#CHANGED_PROJECTS[@]} -gt 0 ]]; then + PROJECTS_CSV=$(IFS=','; echo "${CHANGED_PROJECTS[*]}") + else + PROJECTS_CSV="" + fi + echo "changed_projects=${PROJECTS_CSV}" >> "$GITHUB_OUTPUT" + echo "changed_count=${#CHANGED_PROJECTS[@]}" >> "$GITHUB_OUTPUT" + + echo "::notice::Changed projects (${#CHANGED_PROJECTS[@]}): ${PROJECTS_CSV}" + + - name: Parse commit message + id: parse-commit + shell: bash + run: | + set -euo pipefail + + EVENT_NAME="${{ inputs.event-name }}" + PR_TITLE="${{ inputs.pr-title }}" + + if [[ "$EVENT_NAME" == "pull_request" && -n "$PR_TITLE" ]]; then + FULL_MSG="$PR_TITLE" + else + FULL_MSG=$(git log -1 --format='%s' HEAD) + fi + + echo "full_message<<COMMIT_MSG_EOF" >> "$GITHUB_OUTPUT" + echo "$FULL_MSG" >> "$GITHUB_OUTPUT" + echo "COMMIT_MSG_EOF" >> "$GITHUB_OUTPUT" + + # Parse Conventional Commits: type(scope): subject + # Also handles: type: subject, type!: subject, type(scope)!: subject + if [[ "$FULL_MSG" =~ ^([a-zA-Z]+)(\(([^)]*)\))?(!)?\:\ (.+)$ ]]; then + TYPE="${BASH_REMATCH[1]}" + SCOPE="${BASH_REMATCH[3]:-}" + BREAKING="${BASH_REMATCH[4]:-}" + SUBJECT="${BASH_REMATCH[5]}" + + echo "type=${TYPE}" >> "$GITHUB_OUTPUT" + echo "scope=${SCOPE}" >> "$GITHUB_OUTPUT" + echo "subject=${SUBJECT}" >> "$GITHUB_OUTPUT" + + if [[ "$BREAKING" == "!" ]]; then + echo "breaking=true" >> "$GITHUB_OUTPUT" + else + echo "breaking=false" >> "$GITHUB_OUTPUT" + fi + else + echo "type=" >> "$GITHUB_OUTPUT" + echo "scope=" >> "$GITHUB_OUTPUT" + echo "subject=${FULL_MSG}" >> "$GITHUB_OUTPUT" + echo "breaking=false" >> "$GITHUB_OUTPUT" + fi + + - name: Generate summary report + shell: bash + run: | + set -euo pipefail + + cat >> "$GITHUB_STEP_SUMMARY" <<'HEADER' + # Change Analysis Report + + HEADER + + cat >> "$GITHUB_STEP_SUMMARY" <<EOF + ## Commit Information + + | Field | Value | + |-------|-------| + | **SHA** | \`${{ inputs.current-sha }}\` | + | **Event** | \`${{ inputs.event-name }}\` | + | **Type** | \`${{ steps.parse-commit.outputs.type || 'N/A' }}\` | + | **Scope** | \`${{ steps.parse-commit.outputs.scope || 'N/A' }}\` | + | **Subject** | ${{ steps.parse-commit.outputs.subject }} | + | **Breaking** | ${{ steps.parse-commit.outputs.breaking }} | + + ## Changed Projects (${{ steps.detect.outputs.changed_count }}) + + | Project | Changed | + |---------|---------| + | archived | ${{ steps.detect.outputs.archived_changed == 'true' && '✅' || '—' }} | + | cgit | ${{ steps.detect.outputs.cgit_changed == 'true' && '✅' || '—' }} | + | cmark | ${{ steps.detect.outputs.cmark_changed == 'true' && '✅' || '—' }} | + | corebinutils | ${{ steps.detect.outputs.corebinutils_changed == 'true' && '✅' || '—' }} | + | forgewrapper | ${{ steps.detect.outputs.forgewrapper_changed == 'true' && '✅' || '—' }} | + | genqrcode | ${{ steps.detect.outputs.genqrcode_changed == 'true' && '✅' || '—' }} | + | hooks | ${{ steps.detect.outputs.hooks_changed == 'true' && '✅' || '—' }} | + | images4docker | ${{ steps.detect.outputs.images4docker_changed == 'true' && '✅' || '—' }} | + | json4cpp | ${{ steps.detect.outputs.json4cpp_changed == 'true' && '✅' || '—' }} | + | libnbtplusplus | ${{ steps.detect.outputs.libnbtplusplus_changed == 'true' && '✅' || '—' }} | + | meshmc | ${{ steps.detect.outputs.meshmc_changed == 'true' && '✅' || '—' }} | + | meta | ${{ steps.detect.outputs.meta_changed == 'true' && '✅' || '—' }} | + | mnv | ${{ steps.detect.outputs.mnv_changed == 'true' && '✅' || '—' }} | + | neozip | ${{ steps.detect.outputs.neozip_changed == 'true' && '✅' || '—' }} | + | tomlplusplus | ${{ steps.detect.outputs.tomlplusplus_changed == 'true' && '✅' || '—' }} | + | ci | ${{ steps.detect.outputs.ci_changed == 'true' && '✅' || '—' }} | + | .github | ${{ steps.detect.outputs.github_changed == 'true' && '✅' || '—' }} | + | root files | ${{ steps.detect.outputs.root_changed == 'true' && '✅' || '—' }} | + + --- + > **Changed:** \`${{ steps.detect.outputs.changed_projects }}\` + EOF diff --git a/.github/workflows/cgit-ci.yml b/.github/workflows/cgit-ci.yml index 9248d2bcef..37241312ef 100644 --- a/.github/workflows/cgit-ci.yml +++ b/.github/workflows/cgit-ci.yml @@ -1,15 +1,8 @@ name: "cgit: CI" on: - push: - paths: - - 'cgit/**' - - '.github/workflows/cgit-ci.yml' - pull_request: - paths: - - 'cgit/**' - - '.github/workflows/cgit-ci.yml' workflow_dispatch: + workflow_call: permissions: contents: read diff --git a/.github/workflows/ci-lint.yml b/.github/workflows/ci-lint.yml new file mode 100644 index 0000000000..0e01b66811 --- /dev/null +++ b/.github/workflows/ci-lint.yml @@ -0,0 +1,122 @@ +# Copyright (C) Project Tick Contributors +# SPDX-License-Identifier: MIT +# +# Fast lint & commit checks — called from ci.yml before builds start. + +name: "Lint & Checks" + +on: + workflow_call: + inputs: + run-level: + description: "minimal | standard | full" + required: true + type: string + changed-projects: + description: "Comma-separated list of changed projects" + required: false + type: string + default: "" + +permissions: + contents: read + +jobs: + # ── Commit message lint (Conventional Commits) ────────────────── + commit-lint: + name: "Commit Messages" + runs-on: ubuntu-latest + steps: + - name: Harden runner + uses: step-security/harden-runner@v2 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: 22 + + - name: Install dependencies + working-directory: ci/github-script + run: npm ci + + - name: Lint commit messages + uses: actions/github-script@v7 + with: + script: | + const lint = require('./ci/github-script/lint-commits.js') + await lint({ github, context, core, repoPath: '.' }) + + # ── REUSE / license compliance ────────────────────────────────── + reuse: + name: "REUSE Compliance" + runs-on: ubuntu-latest + steps: + - name: Harden runner + uses: step-security/harden-runner@v2 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@v6 + + - name: Check REUSE compliance + uses: fsfe/reuse-action@v6 + + # ── Whitespace & formatting checks ───────────────────────────── + whitespace: + name: "Whitespace" + runs-on: ubuntu-latest + steps: + - name: Harden runner + uses: step-security/harden-runner@v2 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@v6 + + - name: Check trailing whitespace + run: | + set -euo pipefail + ERRORS=$(git diff --check HEAD~1 HEAD -- \ + ':!*.patch' \ + ':!*/test/data/*' \ + ':!*.ico' \ + ':!*.png' \ + ':!*.jpg' \ + ':!*.gif' \ + ':!*.bin' \ + ':!*.7z' \ + ':!*.zip' \ + ':!*.gz' \ + ':!*.lock' \ + 2>/dev/null || true) + if [[ -n "$ERRORS" ]]; then + echo "::warning::Whitespace issues found:" + echo "$ERRORS" + fi + + # ── Actionlint (validate all workflow YAML) ───────────────────── + actionlint: + name: "Actionlint" + runs-on: ubuntu-latest + steps: + - name: Harden runner + uses: step-security/harden-runner@v2 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@v6 + + - name: Run actionlint + uses: raven-actions/actionlint@v2 + with: + matcher: true diff --git a/.github/workflows/ci-schedule.yml b/.github/workflows/ci-schedule.yml new file mode 100644 index 0000000000..1c933d6b26 --- /dev/null +++ b/.github/workflows/ci-schedule.yml @@ -0,0 +1,233 @@ +# Copyright (C) Project Tick Contributors +# SPDX-License-Identifier: MIT +# +# ╔══════════════════════════════════════════════════════════════════╗ +# ║ Project Tick — Scheduled Maintenance Orchestrator ║ +# ║ ║ +# ║ Consolidates ALL scheduled jobs into one workflow with a ║ +# ║ matrix dispatch. Each cron entry triggers its own subset. ║ +# ╚══════════════════════════════════════════════════════════════════╝ + +name: "Scheduled CI" + +on: + schedule: + # ── Daily (03:00 UTC) ───────────────────────────────────────── + - cron: "0 3 * * *" # [0] daily: stale, coverity, docker + # ── Weekly Sunday (04:00 UTC) ───────────────────────────────── + - cron: "0 4 * * 0" # [1] weekly-sun: link-check, scorecard, flake-update, codeql + # ── Weekly Wednesday (14:00 UTC) ────────────────────────────── + - cron: "0 14 * * 3" # [2] weekly-wed: flawfinder + # ── Weekly Thursday (02:00 UTC) ─────────────────────────────── + - cron: "0 2 * * 4" # [3] weekly-thu: semgrep + workflow_dispatch: + inputs: + schedule-group: + description: "Which schedule group to run" + type: choice + options: [daily, weekly-sun, weekly-wed, weekly-thu, all] + default: all + +permissions: + contents: read + +env: + # Map cron index → group name + SCHEDULE_GROUP: >- + ${{ + github.event.inputs.schedule-group == 'all' && 'all' || + github.event.inputs.schedule-group || + (github.event.schedule == '0 3 * * *' && 'daily') || + (github.event.schedule == '0 4 * * 0' && 'weekly-sun') || + (github.event.schedule == '0 14 * * 3' && 'weekly-wed') || + (github.event.schedule == '0 2 * * 4' && 'weekly-thu') || + 'all' + }} + +jobs: + # ╔════════════════════════════════════════════════════════════════╗ + # ║ Daily Jobs ║ + # ╚════════════════════════════════════════════════════════════════╝ + + stale: + name: "Stale Issues & PRs" + if: contains(fromJSON('["daily","all"]'), env.SCHEDULE_GROUP) + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - name: Harden runner + uses: step-security/harden-runner@v2 + with: + egress-policy: audit + + - name: Mark stale + uses: actions/stale@v10 + with: + stale-issue-message: > + This issue has been automatically marked as stale because it has not had + activity in 90 days. It will be closed in 10 days if no further activity occurs. + stale-pr-message: > + This PR has been automatically marked as stale because it has not had + activity in 30 days. Please rebase or update to keep it alive. + days-before-stale: 90 + days-before-pr-stale: 30 + days-before-close: 10 + days-before-pr-close: -1 + exempt-issue-labels: "pinned,security" + exempt-pr-labels: "pinned,security,status: blocking" + stale-issue-label: "state: stale" + stale-pr-label: "state: stale" + + docker-images: + name: "Docker Image Rebuild" + if: contains(fromJSON('["daily","all"]'), env.SCHEDULE_GROUP) + uses: ./.github/workflows/images4docker-build.yml + permissions: + contents: read + packages: write + secrets: inherit + + mnv-coverity: + name: "MNV Coverity Scan" + if: contains(fromJSON('["daily","all"]'), env.SCHEDULE_GROUP) + uses: ./.github/workflows/mnv-coverity.yml + secrets: inherit + + # ╔════════════════════════════════════════════════════════════════╗ + # ║ Weekly Sunday Jobs ║ + # ╚════════════════════════════════════════════════════════════════╝ + + mnv-link-check: + name: "MNV Link Check" + if: contains(fromJSON('["weekly-sun","all"]'), env.SCHEDULE_GROUP) + uses: ./.github/workflows/mnv-link-check.yml + secrets: inherit + + scorecard: + name: "OpenSSF Scorecard" + if: contains(fromJSON('["weekly-sun","all"]'), env.SCHEDULE_GROUP) + uses: ./.github/workflows/repo-scorecards.yml + permissions: + contents: read + security-events: write + id-token: write + actions: read + issues: read + pull-requests: read + checks: read + secrets: inherit + + meshmc-flake-update: + name: "MeshMC Nix Flake Update" + if: >- + contains(fromJSON('["weekly-sun","all"]'), env.SCHEDULE_GROUP) && + github.repository_owner == 'Project-Tick' + uses: ./.github/workflows/meshmc-flake-update.yml + permissions: + contents: write + pull-requests: write + secrets: inherit + + mnv-codeql: + name: "MNV CodeQL" + if: contains(fromJSON('["weekly-sun","all"]'), env.SCHEDULE_GROUP) + uses: ./.github/workflows/mnv-codeql.yml + permissions: + contents: read + security-events: write + secrets: inherit + + neozip-codeql: + name: "NeoZip CodeQL" + if: contains(fromJSON('["weekly-sun","all"]'), env.SCHEDULE_GROUP) + uses: ./.github/workflows/neozip-codeql.yml + permissions: + contents: read + security-events: write + secrets: inherit + + # ╔════════════════════════════════════════════════════════════════╗ + # ║ Weekly Wednesday Jobs ║ + # ╚════════════════════════════════════════════════════════════════╝ + + json4cpp-flawfinder: + name: "JSON4CPP Flawfinder" + if: contains(fromJSON('["weekly-wed","all"]'), env.SCHEDULE_GROUP) + uses: ./.github/workflows/json4cpp-flawfinder.yml + permissions: + contents: read + security-events: write + actions: read + secrets: inherit + + # ╔════════════════════════════════════════════════════════════════╗ + # ║ Weekly Thursday Jobs ║ + # ╚════════════════════════════════════════════════════════════════╝ + + json4cpp-semgrep: + name: "JSON4CPP Semgrep" + if: contains(fromJSON('["weekly-thu","all"]'), env.SCHEDULE_GROUP) + uses: ./.github/workflows/json4cpp-semgrep.yml + permissions: + contents: read + security-events: write + actions: read + secrets: inherit + + # ╔════════════════════════════════════════════════════════════════╗ + # ║ Schedule Verdict ║ + # ╚════════════════════════════════════════════════════════════════╝ + + verdict: + name: "Schedule Verdict" + if: always() + needs: + - stale + - docker-images + - mnv-coverity + - mnv-link-check + - scorecard + - meshmc-flake-update + - mnv-codeql + - neozip-codeql + - json4cpp-flawfinder + - json4cpp-semgrep + runs-on: ubuntu-latest + steps: + - name: Report + run: | + set -euo pipefail + + echo "## Scheduled CI Report — ${{ env.SCHEDULE_GROUP }}" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "| Job | Result |" >> "$GITHUB_STEP_SUMMARY" + echo "|-----|--------|" >> "$GITHUB_STEP_SUMMARY" + + FAILED=false + report() { + local name="$1" result="$2" icon="⬜" + case "$result" in + success) icon="✅" ;; failure) icon="❌"; FAILED=true ;; + cancelled) icon="⏹️" ;; skipped) icon="⏭️" ;; + esac + echo "| $name | $icon $result |" >> "$GITHUB_STEP_SUMMARY" + } + + report "Stale" "${{ needs.stale.result }}" + report "Docker Images" "${{ needs.docker-images.result }}" + report "MNV Coverity" "${{ needs.mnv-coverity.result }}" + report "MNV Link Check" "${{ needs.mnv-link-check.result }}" + report "Scorecard" "${{ needs.scorecard.result }}" + report "Flake Update" "${{ needs.meshmc-flake-update.result }}" + report "MNV CodeQL" "${{ needs.mnv-codeql.result }}" + report "NeoZip CodeQL" "${{ needs.neozip-codeql.result }}" + report "Flawfinder" "${{ needs.json4cpp-flawfinder.result }}" + report "Semgrep" "${{ needs.json4cpp-semgrep.result }}" + + if [[ "$FAILED" == "true" ]]; then + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "**FAILED** — check individual jobs above." >> "$GITHUB_STEP_SUMMARY" + exit 1 + fi diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..c0dde03ab0 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,717 @@ +# Copyright (C) Project Tick Contributors +# SPDX-License-Identifier: MIT +# +# ╔══════════════════════════════════════════════════════════════════╗ +# ║ Project Tick — Monolithic CI Orchestrator ║ +# ║ ║ +# ║ Every push, pull request, merge queue entry, tag push, and ║ +# ║ manual dispatch flows through this single gate. Nothing runs ║ +# ║ unless this file says so. ║ +# ╚══════════════════════════════════════════════════════════════════╝ + +name: CI + +on: + push: + branches: ["**"] + tags: ["*"] + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + pull_request_target: + types: [closed, labeled] + merge_group: + types: [checks_requested] + workflow_dispatch: + inputs: + force-all: + description: "Force run all project CI pipelines" + type: boolean + default: false + build-type: + description: "Build configuration for meshmc/forgewrapper" + type: choice + options: [Debug, Release] + default: Debug + +permissions: + contents: read + +concurrency: + group: >- + ci-${{ + github.event_name == 'merge_group' && github.event.merge_group.head_ref || + github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || + github.ref + }} + cancel-in-progress: ${{ github.event_name != 'merge_group' }} + +# ════════════════════════════════════════════════════════════════════ +# Environment — shared across all jobs +# ════════════════════════════════════════════════════════════════════ +env: + CI: true + FORCE_ALL: ${{ github.event.inputs.force-all == 'true' || github.event_name == 'merge_group' }} + +jobs: + # ╔════════════════════════════════════════════════════════════════╗ + # ║ STAGE 0 — Gate & Triage ║ + # ╚════════════════════════════════════════════════════════════════╝ + + gate: + name: "Gate" + runs-on: ubuntu-latest + if: >- + !(github.event_name == 'pull_request_target' && !github.event.pull_request.merged) && + !(github.event_name == 'pull_request' && github.event.pull_request.draft) + outputs: + # ── Event classification ──────────────────────────────────── + is_push: ${{ steps.classify.outputs.is_push }} + is_pr: ${{ steps.classify.outputs.is_pr }} + is_merge_queue: ${{ steps.classify.outputs.is_merge_queue }} + is_tag: ${{ steps.classify.outputs.is_tag }} + is_release_tag: ${{ steps.classify.outputs.is_release_tag }} + is_backport: ${{ steps.classify.outputs.is_backport }} + is_dependabot: ${{ steps.classify.outputs.is_dependabot }} + is_master: ${{ steps.classify.outputs.is_master }} + is_scheduled: ${{ steps.classify.outputs.is_scheduled }} + run_level: ${{ steps.classify.outputs.run_level }} + # ── Per-project change flags ──────────────────────────────── + archived_changed: ${{ steps.changes.outputs.archived_changed }} + cgit_changed: ${{ steps.changes.outputs.cgit_changed }} + ci_changed: ${{ steps.changes.outputs.ci_changed }} + cmark_changed: ${{ steps.changes.outputs.cmark_changed }} + corebinutils_changed: ${{ steps.changes.outputs.corebinutils_changed }} + forgewrapper_changed: ${{ steps.changes.outputs.forgewrapper_changed }} + genqrcode_changed: ${{ steps.changes.outputs.genqrcode_changed }} + hooks_changed: ${{ steps.changes.outputs.hooks_changed }} + images4docker_changed: ${{ steps.changes.outputs.images4docker_changed }} + json4cpp_changed: ${{ steps.changes.outputs.json4cpp_changed }} + libnbtplusplus_changed: ${{ steps.changes.outputs.libnbtplusplus_changed }} + meshmc_changed: ${{ steps.changes.outputs.meshmc_changed }} + meta_changed: ${{ steps.changes.outputs.meta_changed }} + mnv_changed: ${{ steps.changes.outputs.mnv_changed }} + neozip_changed: ${{ steps.changes.outputs.neozip_changed }} + tomlplusplus_changed: ${{ steps.changes.outputs.tomlplusplus_changed }} + github_changed: ${{ steps.changes.outputs.github_changed }} + root_changed: ${{ steps.changes.outputs.root_changed }} + changed_projects: ${{ steps.changes.outputs.changed_projects }} + changed_count: ${{ steps.changes.outputs.changed_count }} + # ── Commit parsing ────────────────────────────────────────── + commit_type: ${{ steps.changes.outputs.commit_type }} + commit_scope: ${{ steps.changes.outputs.commit_scope }} + commit_subject: ${{ steps.changes.outputs.commit_subject }} + commit_breaking: ${{ steps.changes.outputs.commit_breaking }} + commit_message: ${{ steps.changes.outputs.commit_message }} + # ── Build config ──────────────────────────────────────────── + build_type: ${{ steps.classify.outputs.build_type }} + + steps: + - name: Harden runner + uses: step-security/harden-runner@v2 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Classify event + id: classify + run: | + set -euo pipefail + + REF="${GITHUB_REF:-}" + EVENT="${{ github.event_name }}" + ACTOR="${{ github.actor }}" + HEAD_REF="${{ github.head_ref || '' }}" + BASE_REF="${{ github.base_ref || '' }}" + + # ── Booleans ────────────────────────────────────────── + echo "is_push=$([[ "$EVENT" == "push" ]] && echo true || echo false)" >> "$GITHUB_OUTPUT" + echo "is_pr=$([[ "$EVENT" == "pull_request" || "$EVENT" == "pull_request_target" ]] && echo true || echo false)" >> "$GITHUB_OUTPUT" + echo "is_merge_queue=$([[ "$EVENT" == "merge_group" ]] && echo true || echo false)" >> "$GITHUB_OUTPUT" + echo "is_tag=$([[ "$REF" == refs/tags/* ]] && echo true || echo false)" >> "$GITHUB_OUTPUT" + echo "is_release_tag=$([[ "$REF" =~ ^refs/tags/(meshmc|neozip|mnv|cmark|forgewrapper)- ]] && echo true || echo false)" >> "$GITHUB_OUTPUT" + echo "is_backport=$([[ "$HEAD_REF" == backport-* || "$HEAD_REF" == backport/* ]] && echo true || echo false)" >> "$GITHUB_OUTPUT" + echo "is_dependabot=$([[ "$ACTOR" == "dependabot[bot]" ]] && echo true || echo false)" >> "$GITHUB_OUTPUT" + echo "is_master=$([[ "$REF" == "refs/heads/master" || "$REF" == "refs/heads/main" ]] && echo true || echo false)" >> "$GITHUB_OUTPUT" + echo "is_scheduled=false" >> "$GITHUB_OUTPUT" + + # ── Run level ───────────────────────────────────────── + # full = merge queue, tags, master push, manual force-all + # standard = normal PR, branch push + # minimal = dependabot, backport, draft + if [[ "$EVENT" == "merge_group" ]] || \ + [[ "$REF" == refs/tags/* ]] || \ + [[ "$REF" == "refs/heads/master" ]] || \ + [[ "${{ env.FORCE_ALL }}" == "true" ]]; then + echo "run_level=full" >> "$GITHUB_OUTPUT" + elif [[ "$ACTOR" == "dependabot[bot]" ]]; then + echo "run_level=minimal" >> "$GITHUB_OUTPUT" + elif [[ "$HEAD_REF" == backport-* ]]; then + echo "run_level=standard" >> "$GITHUB_OUTPUT" + else + echo "run_level=standard" >> "$GITHUB_OUTPUT" + fi + + # ── Build type ──────────────────────────────────────── + if [[ "$REF" == refs/tags/* ]]; then + echo "build_type=Release" >> "$GITHUB_OUTPUT" + elif [[ -n "${{ github.event.inputs.build-type || '' }}" ]]; then + echo "build_type=${{ github.event.inputs.build-type }}" >> "$GITHUB_OUTPUT" + else + echo "build_type=Debug" >> "$GITHUB_OUTPUT" + fi + + - name: Detect changes + id: changes + if: github.event_name != 'pull_request_target' + uses: ./.github/actions/change-analysis + with: + event-name: ${{ github.event_name }} + base-sha: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha || '' }} + head-sha: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha || '' }} + before-sha: ${{ github.event.before || '' }} + current-sha: ${{ github.sha }} + pr-title: ${{ github.event.pull_request.title || '' }} + + # ╔════════════════════════════════════════════════════════════════╗ + # ║ STAGE 1 — Lint & Commit Checks (fast, blocks everything) ║ + # ╚════════════════════════════════════════════════════════════════╝ + + lint: + name: "Lint" + needs: gate + if: needs.gate.outputs.is_pr == 'true' + uses: ./.github/workflows/ci-lint.yml + with: + run-level: ${{ needs.gate.outputs.run_level }} + changed-projects: ${{ needs.gate.outputs.changed_projects }} + secrets: inherit + + dependency-review: + name: "Dependency Review" + needs: gate + if: needs.gate.outputs.is_pr == 'true' + uses: ./.github/workflows/repo-dependency-review.yml + secrets: inherit + + # ╔════════════════════════════════════════════════════════════════╗ + # ║ STAGE 2 — Per-Project Build & Test Matrices ║ + # ║ ║ + # ║ Each project runs only when its directory changed, or when ║ + # ║ force-all / merge-queue / master push triggers them all. ║ + # ║ Inner reusable workflows handle their own matrices. ║ + # ╚════════════════════════════════════════════════════════════════╝ + + # ── C / System Projects ───────────────────────────────────────── + mnv: + name: "MNV" + needs: [gate, lint] + if: >- + always() && + !cancelled() && + (needs.lint.result == 'success' || needs.lint.result == 'skipped') && + (needs.gate.outputs.mnv_changed == 'true' || needs.gate.outputs.run_level == 'full') + uses: ./.github/workflows/mnv-ci.yml + permissions: + contents: read + secrets: inherit + + cgit: + name: "CGit" + needs: [gate, lint] + if: >- + always() && + !cancelled() && + (needs.lint.result == 'success' || needs.lint.result == 'skipped') && + (needs.gate.outputs.cgit_changed == 'true' || needs.gate.outputs.run_level == 'full') + uses: ./.github/workflows/cgit-ci.yml + permissions: + contents: read + secrets: inherit + + cmark: + name: "CMark" + needs: [gate, lint] + if: >- + always() && + !cancelled() && + (needs.lint.result == 'success' || needs.lint.result == 'skipped') && + (needs.gate.outputs.cmark_changed == 'true' || needs.gate.outputs.run_level == 'full') + uses: ./.github/workflows/cmark-ci.yml + permissions: + contents: read + secrets: inherit + + corebinutils: + name: "CoreBinutils" + needs: [gate, lint] + if: >- + always() && + !cancelled() && + (needs.lint.result == 'success' || needs.lint.result == 'skipped') && + (needs.gate.outputs.corebinutils_changed == 'true' || needs.gate.outputs.run_level == 'full') + uses: ./.github/workflows/corebinutils-ci.yml + permissions: + contents: read + secrets: inherit + + genqrcode: + name: "GenQRCode" + needs: [gate, lint] + if: >- + always() && + !cancelled() && + (needs.lint.result == 'success' || needs.lint.result == 'skipped') && + (needs.gate.outputs.genqrcode_changed == 'true' || needs.gate.outputs.run_level == 'full') + uses: ./.github/workflows/genqrcode-ci.yml + permissions: + contents: read + secrets: inherit + + neozip: + name: "NeoZip" + needs: [gate, lint] + if: >- + always() && + !cancelled() && + (needs.lint.result == 'success' || needs.lint.result == 'skipped') && + (needs.gate.outputs.neozip_changed == 'true' || needs.gate.outputs.run_level == 'full') + uses: ./.github/workflows/neozip-ci.yml + permissions: + contents: read + secrets: inherit + + # ── C++ / Library Projects ────────────────────────────────────── + json4cpp: + name: "JSON4CPP" + needs: [gate, lint] + if: >- + always() && + !cancelled() && + (needs.lint.result == 'success' || needs.lint.result == 'skipped') && + (needs.gate.outputs.json4cpp_changed == 'true' || needs.gate.outputs.run_level == 'full') + uses: ./.github/workflows/json4cpp-ci.yml + permissions: + contents: read + security-events: write + secrets: inherit + + libnbtplusplus: + name: "libNBT++" + needs: [gate, lint] + if: >- + always() && + !cancelled() && + (needs.lint.result == 'success' || needs.lint.result == 'skipped') && + (needs.gate.outputs.libnbtplusplus_changed == 'true' || needs.gate.outputs.run_level == 'full') + uses: ./.github/workflows/libnbtplusplus-ci.yml + permissions: + contents: read + secrets: inherit + + tomlplusplus: + name: "TOML++" + needs: [gate, lint] + if: >- + always() && + !cancelled() && + (needs.lint.result == 'success' || needs.lint.result == 'skipped') && + (needs.gate.outputs.tomlplusplus_changed == 'true' || needs.gate.outputs.run_level == 'full') + uses: ./.github/workflows/tomlplusplus-ci.yml + permissions: + contents: read + secrets: inherit + + # ── Java / Minecraft Projects ─────────────────────────────────── + meshmc: + name: "MeshMC" + needs: [gate, lint] + if: >- + always() && + !cancelled() && + (needs.lint.result == 'success' || needs.lint.result == 'skipped') && + (needs.gate.outputs.meshmc_changed == 'true' || needs.gate.outputs.run_level == 'full') + uses: ./.github/workflows/meshmc-build.yml + with: + build-type: ${{ needs.gate.outputs.build_type }} + permissions: + contents: read + id-token: write + packages: write + secrets: inherit + + forgewrapper: + name: "ForgeWrapper" + needs: [gate, lint] + if: >- + always() && + !cancelled() && + (needs.lint.result == 'success' || needs.lint.result == 'skipped') && + (needs.gate.outputs.forgewrapper_changed == 'true' || needs.gate.outputs.run_level == 'full') + uses: ./.github/workflows/forgewrapper-build.yml + permissions: + contents: read + secrets: inherit + + # ── Container & Docker ────────────────────────────────────────── + images4docker: + name: "Docker Images" + needs: [gate, lint] + if: >- + always() && + !cancelled() && + (needs.lint.result == 'success' || needs.lint.result == 'skipped') && + (needs.gate.outputs.images4docker_changed == 'true' || needs.gate.outputs.run_level == 'full') + uses: ./.github/workflows/images4docker-build.yml + permissions: + contents: read + packages: write + secrets: inherit + + # ╔════════════════════════════════════════════════════════════════╗ + # ║ STAGE 3 — Extended Analysis (only on full runs) ║ + # ║ ║ + # ║ Fuzz testing, CodeQL, static analysis — expensive jobs that ║ + # ║ run on merge queue, master, or manual dispatch only. ║ + # ╚════════════════════════════════════════════════════════════════╝ + + cmark-fuzz: + name: "CMark Fuzz" + needs: [gate, cmark] + if: >- + always() && + !cancelled() && + needs.cmark.result == 'success' && + needs.gate.outputs.run_level == 'full' && + (needs.gate.outputs.cmark_changed == 'true' || needs.gate.outputs.run_level == 'full') + uses: ./.github/workflows/cmark-fuzz.yml + permissions: + contents: read + secrets: inherit + + json4cpp-fuzz: + name: "JSON4CPP Fuzz" + needs: [gate, json4cpp] + if: >- + always() && + !cancelled() && + needs.json4cpp.result == 'success' && + needs.gate.outputs.run_level == 'full' && + (needs.gate.outputs.json4cpp_changed == 'true' || needs.gate.outputs.run_level == 'full') + uses: ./.github/workflows/json4cpp-fuzz.yml + permissions: + contents: read + secrets: inherit + + json4cpp-amalgam: + name: "JSON4CPP Amalgamation" + needs: [gate, json4cpp] + if: >- + always() && + !cancelled() && + needs.json4cpp.result == 'success' && + (needs.gate.outputs.json4cpp_changed == 'true' || needs.gate.outputs.run_level == 'full') + uses: ./.github/workflows/json4cpp-amalgam.yml + permissions: + contents: read + secrets: inherit + + tomlplusplus-fuzz: + name: "TOML++ Fuzz" + needs: [gate, tomlplusplus] + if: >- + always() && + !cancelled() && + needs.tomlplusplus.result == 'success' && + needs.gate.outputs.run_level == 'full' && + (needs.gate.outputs.tomlplusplus_changed == 'true' || needs.gate.outputs.run_level == 'full') + uses: ./.github/workflows/tomlplusplus-fuzz.yml + permissions: + contents: read + security-events: write + secrets: inherit + + neozip-fuzz: + name: "NeoZip Fuzz" + needs: [gate, neozip] + if: >- + always() && + !cancelled() && + needs.neozip.result == 'success' && + needs.gate.outputs.run_level == 'full' && + (needs.gate.outputs.neozip_changed == 'true' || needs.gate.outputs.run_level == 'full') + uses: ./.github/workflows/neozip-fuzz.yml + permissions: + contents: read + secrets: inherit + + meshmc-codeql: + name: "MeshMC CodeQL" + needs: [gate, meshmc] + if: >- + always() && + !cancelled() && + needs.meshmc.result == 'success' && + needs.gate.outputs.run_level == 'full' && + (needs.gate.outputs.meshmc_changed == 'true' || needs.gate.outputs.run_level == 'full') + uses: ./.github/workflows/meshmc-codeql.yml + permissions: + contents: read + security-events: write + secrets: inherit + + mnv-codeql: + name: "MNV CodeQL" + needs: [gate, mnv] + if: >- + always() && + !cancelled() && + needs.mnv.result == 'success' && + needs.gate.outputs.run_level == 'full' && + (needs.gate.outputs.mnv_changed == 'true' || needs.gate.outputs.run_level == 'full') + uses: ./.github/workflows/mnv-codeql.yml + permissions: + contents: read + security-events: write + secrets: inherit + + neozip-codeql: + name: "NeoZip CodeQL" + needs: [gate, neozip] + if: >- + always() && + !cancelled() && + needs.neozip.result == 'success' && + needs.gate.outputs.run_level == 'full' && + (needs.gate.outputs.neozip_changed == 'true' || needs.gate.outputs.run_level == 'full') + uses: ./.github/workflows/neozip-codeql.yml + permissions: + contents: read + security-events: write + secrets: inherit + + # ╔════════════════════════════════════════════════════════════════╗ + # ║ STAGE 4 — Containers & Nix (only on full + meshmc changes) ║ + # ╚════════════════════════════════════════════════════════════════╝ + + meshmc-container: + name: "MeshMC Container" + needs: [gate, meshmc] + if: >- + always() && + !cancelled() && + needs.meshmc.result == 'success' && + needs.gate.outputs.run_level == 'full' && + (needs.gate.outputs.meshmc_changed == 'true' || needs.gate.outputs.run_level == 'full') + uses: ./.github/workflows/meshmc-container.yml + permissions: + contents: read + packages: write + secrets: inherit + + meshmc-nix: + name: "MeshMC Nix" + needs: [gate, meshmc] + if: >- + always() && + !cancelled() && + needs.meshmc.result == 'success' && + needs.gate.outputs.run_level == 'full' && + (needs.gate.outputs.meshmc_changed == 'true' || needs.gate.outputs.run_level == 'full') + uses: ./.github/workflows/meshmc-nix.yml + permissions: + contents: read + secrets: inherit + + # ╔════════════════════════════════════════════════════════════════╗ + # ║ STAGE 5 — Documentation (master push only) ║ + # ╚════════════════════════════════════════════════════════════════╝ + + json4cpp-docs: + name: "JSON4CPP Docs" + needs: [gate, json4cpp] + if: >- + always() && + !cancelled() && + needs.json4cpp.result == 'success' && + needs.gate.outputs.is_master == 'true' && + needs.gate.outputs.is_push == 'true' && + needs.gate.outputs.json4cpp_changed == 'true' + uses: ./.github/workflows/json4cpp-publish-docs.yml + permissions: + contents: write + secrets: inherit + + tomlplusplus-docs: + name: "TOML++ Docs" + needs: [gate, tomlplusplus] + if: >- + always() && + !cancelled() && + needs.tomlplusplus.result == 'success' && + needs.gate.outputs.is_master == 'true' && + needs.gate.outputs.is_push == 'true' && + needs.gate.outputs.tomlplusplus_changed == 'true' + uses: ./.github/workflows/tomlplusplus-gh-pages.yml + permissions: + contents: write + secrets: inherit + + # ╔════════════════════════════════════════════════════════════════╗ + # ║ STAGE 6 — Backport Automation (PR merge events) ║ + # ╚════════════════════════════════════════════════════════════════╝ + + backport: + name: "Backport" + needs: gate + if: >- + github.event_name == 'pull_request_target' && + github.event.pull_request.merged == true && + contains(toJSON(github.event.pull_request.labels.*.name), 'backport') + uses: ./.github/workflows/meshmc-backport.yml + permissions: + contents: write + pull-requests: write + actions: write + secrets: inherit + + merge-blocking: + name: "Merge Blocking PR" + needs: gate + if: >- + github.event_name == 'pull_request_target' && + github.event.pull_request.merged == true && + contains(toJSON(github.event.pull_request.labels.*.name), 'status: blocking') + uses: ./.github/workflows/meshmc-merge-blocking-pr.yml + secrets: inherit + + # ╔════════════════════════════════════════════════════════════════╗ + # ║ STAGE 7 — Release (tag pushes only) ║ + # ╚════════════════════════════════════════════════════════════════╝ + + meshmc-release: + name: "MeshMC Release" + needs: [gate, meshmc] + if: >- + always() && + !cancelled() && + needs.gate.outputs.is_tag == 'true' && + startsWith(github.ref, 'refs/tags/meshmc-') + uses: ./.github/workflows/meshmc-build.yml + with: + build-type: Release + environment: Release + permissions: + contents: read + id-token: write + packages: write + secrets: inherit + + neozip-release: + name: "NeoZip Release" + needs: gate + if: >- + needs.gate.outputs.is_tag == 'true' && + startsWith(github.ref, 'refs/tags/neozip-') + uses: ./.github/workflows/neozip-release.yml + permissions: + contents: read + secrets: inherit + + # ╔════════════════════════════════════════════════════════════════╗ + # ║ STAGE 8 — Final Verdicts ║ + # ║ ║ + # ║ Merge queue and branch protection rules check this job. ║ + # ║ It collects results from all stages and reports pass/fail. ║ + # ╚════════════════════════════════════════════════════════════════╝ + + verdict: + name: "CI Verdict" + if: always() + needs: + - gate + - lint + - mnv + - cgit + - cmark + - corebinutils + - genqrcode + - neozip + - json4cpp + - libnbtplusplus + - tomlplusplus + - meshmc + - forgewrapper + - images4docker + - cmark-fuzz + - json4cpp-fuzz + - json4cpp-amalgam + - tomlplusplus-fuzz + - neozip-fuzz + - meshmc-codeql + - mnv-codeql + - neozip-codeql + - meshmc-container + - meshmc-nix + runs-on: ubuntu-latest + steps: + - name: Evaluate results + run: | + set -euo pipefail + + echo "## CI Verdict" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "| Job | Result |" >> "$GITHUB_STEP_SUMMARY" + echo "|-----|--------|" >> "$GITHUB_STEP_SUMMARY" + + FAILED=false + + check_job() { + local name="$1" + local result="$2" + local icon="⬜" + case "$result" in + success) icon="✅" ;; + failure) icon="❌"; FAILED=true ;; + cancelled) icon="⏹️" ;; + skipped) icon="⏭️" ;; + esac + echo "| $name | $icon $result |" >> "$GITHUB_STEP_SUMMARY" + } + + check_job "Gate" "${{ needs.gate.result }}" + check_job "Lint" "${{ needs.lint.result }}" + check_job "MNV" "${{ needs.mnv.result }}" + check_job "CGit" "${{ needs.cgit.result }}" + check_job "CMark" "${{ needs.cmark.result }}" + check_job "CoreBinutils" "${{ needs.corebinutils.result }}" + check_job "GenQRCode" "${{ needs.genqrcode.result }}" + check_job "NeoZip" "${{ needs.neozip.result }}" + check_job "JSON4CPP" "${{ needs.json4cpp.result }}" + check_job "libNBT++" "${{ needs.libnbtplusplus.result }}" + check_job "TOML++" "${{ needs.tomlplusplus.result }}" + check_job "MeshMC" "${{ needs.meshmc.result }}" + check_job "ForgeWrapper" "${{ needs.forgewrapper.result }}" + check_job "Docker Images" "${{ needs.images4docker.result }}" + check_job "CMark Fuzz" "${{ needs.cmark-fuzz.result }}" + check_job "JSON4CPP Fuzz" "${{ needs.json4cpp-fuzz.result }}" + check_job "JSON4CPP Amalg" "${{ needs.json4cpp-amalgam.result }}" + check_job "TOML++ Fuzz" "${{ needs.tomlplusplus-fuzz.result }}" + check_job "NeoZip Fuzz" "${{ needs.neozip-fuzz.result }}" + check_job "MeshMC CodeQL" "${{ needs.meshmc-codeql.result }}" + check_job "MNV CodeQL" "${{ needs.mnv-codeql.result }}" + check_job "NeoZip CodeQL" "${{ needs.neozip-codeql.result }}" + check_job "MeshMC Docker" "${{ needs.meshmc-container.result }}" + check_job "MeshMC Nix" "${{ needs.meshmc-nix.result }}" + + echo "" >> "$GITHUB_STEP_SUMMARY" + + if [[ "$FAILED" == "true" ]]; then + echo "**Result: FAILED** — one or more required jobs failed." >> "$GITHUB_STEP_SUMMARY" + exit 1 + else + echo "**Result: PASSED** — all executed jobs succeeded." >> "$GITHUB_STEP_SUMMARY" + fi diff --git a/.github/workflows/cmark-ci.yml b/.github/workflows/cmark-ci.yml index 45daf6082f..4d1fdb2180 100644 --- a/.github/workflows/cmark-ci.yml +++ b/.github/workflows/cmark-ci.yml @@ -1,17 +1,8 @@ name: "cmark: CI" on: - push: - branches-ignore: - - 'dependabot/**' - paths: - - 'cmark/**' - - '.github/workflows/cmark-ci.yml' - pull_request: - paths: - - 'cmark/**' - - '.github/workflows/cmark-ci.yml' workflow_dispatch: + workflow_call: jobs: linter: diff --git a/.github/workflows/cmark-fuzz.yml b/.github/workflows/cmark-fuzz.yml index 28f14b8244..10d2b48ac0 100644 --- a/.github/workflows/cmark-fuzz.yml +++ b/.github/workflows/cmark-fuzz.yml @@ -1,16 +1,8 @@ name: "cmark: Fuzz" on: - push: - branches: [master] - paths: - - 'cmark/**' - - '.github/workflows/cmark-fuzz.yml' - pull_request: - paths: - - 'cmark/**' - - '.github/workflows/cmark-fuzz.yml' workflow_dispatch: + workflow_call: concurrency: group: cmark-fuzz-${{ github.ref }} diff --git a/.github/workflows/corebinutils-ci.yml b/.github/workflows/corebinutils-ci.yml index 34c9b3fcfd..74f49f4a51 100644 --- a/.github/workflows/corebinutils-ci.yml +++ b/.github/workflows/corebinutils-ci.yml @@ -1,15 +1,8 @@ name: "corebinutils: CI" on: - push: - paths: - - 'corebinutils/**' - - '.github/workflows/corebinutils-ci.yml' - pull_request: - paths: - - 'corebinutils/**' - - '.github/workflows/corebinutils-ci.yml' workflow_dispatch: + workflow_call: permissions: contents: read diff --git a/.github/workflows/forgewrapper-build.yml b/.github/workflows/forgewrapper-build.yml index 30010f6e1c..5f9f701965 100644 --- a/.github/workflows/forgewrapper-build.yml +++ b/.github/workflows/forgewrapper-build.yml @@ -1,16 +1,8 @@ name: "ForgeWrapper: Build" on: - push: - branches: [master] - paths: - - 'forgewrapper/**' - - '.github/workflows/forgewrapper-build.yml' - pull_request: - branches: [master] - paths: - - 'forgewrapper/**' - - '.github/workflows/forgewrapper-build.yml' + workflow_dispatch: + workflow_call: jobs: build: diff --git a/.github/workflows/genqrcode-ci.yml b/.github/workflows/genqrcode-ci.yml index 9931f66ec9..202eba4e43 100644 --- a/.github/workflows/genqrcode-ci.yml +++ b/.github/workflows/genqrcode-ci.yml @@ -1,14 +1,8 @@ name: "genqrcode: CI" on: - push: - paths: - - 'genqrcode/**' - - '.github/workflows/genqrcode-ci.yml' - pull_request: - paths: - - 'genqrcode/**' - - '.github/workflows/genqrcode-ci.yml' + workflow_dispatch: + workflow_call: jobs: cmake: diff --git a/.github/workflows/images4docker-build.yml b/.github/workflows/images4docker-build.yml index e91b2a5368..36778194bc 100644 --- a/.github/workflows/images4docker-build.yml +++ b/.github/workflows/images4docker-build.yml @@ -1,14 +1,8 @@ name: "images4docker: Build" on: - push: - branches: ["trunk", "master"] - paths: - - "images4docker/dockerfiles/**" - - ".github/workflows/images4docker-build.yml" - schedule: - - cron: "17 3 * * *" workflow_dispatch: + workflow_call: permissions: contents: read diff --git a/.github/workflows/json4cpp-amalgam.yml b/.github/workflows/json4cpp-amalgam.yml index c25550487c..036532b2d0 100644 --- a/.github/workflows/json4cpp-amalgam.yml +++ b/.github/workflows/json4cpp-amalgam.yml @@ -1,9 +1,8 @@ name: "json4cpp: Check amalgamation" on: - pull_request: - paths: - - 'json4cpp/**' + workflow_dispatch: + workflow_call: permissions: contents: read diff --git a/.github/workflows/json4cpp-ci.yml b/.github/workflows/json4cpp-ci.yml index 2646c1b5c9..21b1e7bfca 100644 --- a/.github/workflows/json4cpp-ci.yml +++ b/.github/workflows/json4cpp-ci.yml @@ -1,19 +1,8 @@ name: "json4cpp: CI" on: - push: - branches: - - develop - - master - - release/* - paths: - - 'json4cpp/**' - - '.github/workflows/json4cpp-ci.yml' - pull_request: - paths: - - 'json4cpp/**' - - '.github/workflows/json4cpp-ci.yml' workflow_dispatch: + workflow_call: permissions: contents: read diff --git a/.github/workflows/json4cpp-flawfinder.yml b/.github/workflows/json4cpp-flawfinder.yml index c496ab8d27..ca78ee817a 100644 --- a/.github/workflows/json4cpp-flawfinder.yml +++ b/.github/workflows/json4cpp-flawfinder.yml @@ -4,16 +4,8 @@ permissions: contents: read on: - push: - branches: ["develop"] - paths: - - 'json4cpp/**' - pull_request: - branches: ["develop"] - paths: - - 'json4cpp/**' - schedule: - - cron: '41 14 * * 3' + workflow_dispatch: + workflow_call: jobs: flawfinder: diff --git a/.github/workflows/json4cpp-fuzz.yml b/.github/workflows/json4cpp-fuzz.yml index 893435c953..dffe6adffc 100644 --- a/.github/workflows/json4cpp-fuzz.yml +++ b/.github/workflows/json4cpp-fuzz.yml @@ -1,16 +1,8 @@ name: "json4cpp: Fuzz" on: - push: - branches: [master, develop] - paths: - - 'json4cpp/**' - - '.github/workflows/json4cpp-fuzz.yml' - pull_request: - paths: - - 'json4cpp/**' - - '.github/workflows/json4cpp-fuzz.yml' workflow_dispatch: + workflow_call: concurrency: group: json4cpp-fuzz-${{ github.ref }} diff --git a/.github/workflows/json4cpp-publish-docs.yml b/.github/workflows/json4cpp-publish-docs.yml index c68dcccbd1..6bbe27d33f 100644 --- a/.github/workflows/json4cpp-publish-docs.yml +++ b/.github/workflows/json4cpp-publish-docs.yml @@ -1,13 +1,8 @@ name: "json4cpp: Publish documentation" on: - push: - branches: - - develop - paths: - - 'json4cpp/docs/mkdocs/**' - - 'json4cpp/docs/examples/**' workflow_dispatch: + workflow_call: concurrency: group: json4cpp-documentation diff --git a/.github/workflows/json4cpp-semgrep.yml b/.github/workflows/json4cpp-semgrep.yml index 6b594e6bab..0e728b3830 100644 --- a/.github/workflows/json4cpp-semgrep.yml +++ b/.github/workflows/json4cpp-semgrep.yml @@ -1,16 +1,8 @@ name: "json4cpp: Semgrep" on: - push: - branches: ["develop"] - paths: - - 'json4cpp/**' - pull_request: - branches: ["develop"] - paths: - - 'json4cpp/**' - schedule: - - cron: '23 2 * * 4' + workflow_dispatch: + workflow_call: permissions: contents: read @@ -31,10 +23,9 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: returntocorp/semgrep-action@713efdd345f3035192eaa63f56867b88e63e4e5d + - uses: semgrep/semgrep@caad1d53f48a40fa55cec9e41bf4820f115889b3 with: publishToken: ${{ secrets.SEMGREP_APP_TOKEN }} - publishDeployment: ${{ secrets.SEMGREP_DEPLOYMENT_ID }} generateSarif: "1" - name: Upload SARIF file diff --git a/.github/workflows/libnbtplusplus-ci.yml b/.github/workflows/libnbtplusplus-ci.yml index ed255a5802..3657d211a3 100644 --- a/.github/workflows/libnbtplusplus-ci.yml +++ b/.github/workflows/libnbtplusplus-ci.yml @@ -1,15 +1,8 @@ name: "libnbtplusplus: CI" on: - push: - paths: - - 'libnbtplusplus/**' - - '.github/workflows/libnbtplusplus-ci.yml' - pull_request: - paths: - - 'libnbtplusplus/**' - - '.github/workflows/libnbtplusplus-ci.yml' workflow_dispatch: + workflow_call: permissions: contents: read diff --git a/.github/workflows/meshmc-backport.yml b/.github/workflows/meshmc-backport.yml index d468fd6986..3c739a4a26 100644 --- a/.github/workflows/meshmc-backport.yml +++ b/.github/workflows/meshmc-backport.yml @@ -1,8 +1,8 @@ name: "MeshMC: Backport" on: - pull_request_target: - types: [closed, labeled] + workflow_dispatch: + workflow_call: permissions: {} diff --git a/.github/workflows/meshmc-build.yml b/.github/workflows/meshmc-build.yml index d7b0a2335e..5468afc409 100644 --- a/.github/workflows/meshmc-build.yml +++ b/.github/workflows/meshmc-build.yml @@ -5,20 +5,6 @@ concurrency: cancel-in-progress: true on: - push: - branches: - - 'master' - paths: - - 'meshmc/**' - - '.github/workflows/meshmc-build.yml' - - '.github/actions/meshmc/**' - merge_group: - types: [checks_requested] - pull_request: - paths: - - 'meshmc/**' - - '.github/workflows/meshmc-build.yml' - - '.github/actions/meshmc/**' workflow_call: inputs: build-type: diff --git a/.github/workflows/meshmc-codeql.yml b/.github/workflows/meshmc-codeql.yml index 6dd764849a..4aff63060b 100644 --- a/.github/workflows/meshmc-codeql.yml +++ b/.github/workflows/meshmc-codeql.yml @@ -5,13 +5,8 @@ concurrency: cancel-in-progress: true on: - merge_group: - types: [checks_requested] - pull_request: - paths: - - 'meshmc/**' - - '.github/workflows/meshmc-codeql.yml' workflow_dispatch: + workflow_call: permissions: {} diff --git a/.github/workflows/meshmc-container.yml b/.github/workflows/meshmc-container.yml index 3a41e15bcd..dc9d3520db 100644 --- a/.github/workflows/meshmc-container.yml +++ b/.github/workflows/meshmc-container.yml @@ -5,19 +5,8 @@ concurrency: cancel-in-progress: true on: - push: - branches: - - 'master' - paths: - - 'meshmc/Containerfile' - - '.github/workflows/meshmc-container.yml' - merge_group: - types: [checks_requested] - pull_request: - paths: - - 'meshmc/Containerfile' - - '.github/workflows/meshmc-container.yml' workflow_dispatch: + workflow_call: permissions: {} diff --git a/.github/workflows/meshmc-flake-update.yml b/.github/workflows/meshmc-flake-update.yml index c0be2756d4..758a471ee4 100644 --- a/.github/workflows/meshmc-flake-update.yml +++ b/.github/workflows/meshmc-flake-update.yml @@ -1,9 +1,8 @@ name: "MeshMC: Update Flake" on: - schedule: - - cron: "0 0 * * 0" workflow_dispatch: + workflow_call: permissions: {} diff --git a/.github/workflows/meshmc-merge-blocking-pr.yml b/.github/workflows/meshmc-merge-blocking-pr.yml index 3542a470e0..978d3e2677 100644 --- a/.github/workflows/meshmc-merge-blocking-pr.yml +++ b/.github/workflows/meshmc-merge-blocking-pr.yml @@ -10,6 +10,7 @@ on: description: Local Pull Request number to work on required: true type: number + workflow_call: permissions: {} diff --git a/.github/workflows/meshmc-nix.yml b/.github/workflows/meshmc-nix.yml index 21af121a34..a764fa70ff 100644 --- a/.github/workflows/meshmc-nix.yml +++ b/.github/workflows/meshmc-nix.yml @@ -5,48 +5,8 @@ concurrency: cancel-in-progress: true on: - push: - branches: - - "master" - - "release-*" - tags: - - "*" - paths: - - "meshmc/**.cpp" - - "meshmc/**.h" - - "meshmc/**.java" - - "meshmc/**.ui" - - "meshmc/**.md" - - "meshmc/**.nix" - - "meshmc/nix/**" - - "meshmc/flake.lock" - - "meshmc/buildconfig/**" - - "meshmc/cmake/**" - - "meshmc/launcher/**" - - "meshmc/libraries/**" - - "meshmc/branding/**" - - "meshmc/tests/**" - - "meshmc/CMakeLists.txt" - - ".github/workflows/meshmc-nix.yml" - pull_request: - paths: - - "meshmc/**.cpp" - - "meshmc/**.h" - - "meshmc/**.java" - - "meshmc/**.ui" - - "meshmc/**.md" - - "meshmc/**.nix" - - "meshmc/nix/**" - - "meshmc/flake.lock" - - "meshmc/buildconfig/**" - - "meshmc/cmake/**" - - "meshmc/launcher/**" - - "meshmc/libraries/**" - - "meshmc/branding/**" - - "meshmc/tests/**" - - "meshmc/CMakeLists.txt" - - ".github/workflows/meshmc-nix.yml" workflow_dispatch: + workflow_call: permissions: {} diff --git a/.github/workflows/mnv-ci.yml b/.github/workflows/mnv-ci.yml index b588d03df2..89d592eec3 100644 --- a/.github/workflows/mnv-ci.yml +++ b/.github/workflows/mnv-ci.yml @@ -1,17 +1,8 @@ name: "mnv: CI" on: - push: - branches: ['**'] - paths: - - 'mnv/**' - - '.github/workflows/mnv-ci.yml' - - '.github/actions/mnv/**' - pull_request: - paths: - - 'mnv/**' - - '.github/workflows/mnv-ci.yml' - - '.github/actions/mnv/**' + workflow_dispatch: + workflow_call: concurrency: group: mnv-${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} diff --git a/.github/workflows/mnv-codeql.yml b/.github/workflows/mnv-codeql.yml index 0cad996fa1..ed3adae7c1 100644 --- a/.github/workflows/mnv-codeql.yml +++ b/.github/workflows/mnv-codeql.yml @@ -1,16 +1,8 @@ name: "mnv: CodeQL" on: - push: - branches: [master] - paths: - - 'mnv/**' - pull_request: - branches: [master] - paths: - - 'mnv/**' - schedule: - - cron: '0 18 * * 1' + workflow_dispatch: + workflow_call: concurrency: group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} diff --git a/.github/workflows/mnv-coverity.yml b/.github/workflows/mnv-coverity.yml index 6ebb70125b..b0de61694c 100644 --- a/.github/workflows/mnv-coverity.yml +++ b/.github/workflows/mnv-coverity.yml @@ -1,9 +1,8 @@ name: "mnv: Coverity" on: - schedule: - - cron: '42 0 * * *' workflow_dispatch: + workflow_call: permissions: contents: read diff --git a/.github/workflows/mnv-link-check.yml b/.github/workflows/mnv-link-check.yml index 6b9d16ca59..8495c6df32 100644 --- a/.github/workflows/mnv-link-check.yml +++ b/.github/workflows/mnv-link-check.yml @@ -2,8 +2,7 @@ name: "mnv: Check Links" on: workflow_dispatch: - schedule: - - cron: '0 3 * * 0' + workflow_call: jobs: lychee: diff --git a/.github/workflows/neozip-ci.yml b/.github/workflows/neozip-ci.yml index 32c12a2c8d..2a0532fd21 100644 --- a/.github/workflows/neozip-ci.yml +++ b/.github/workflows/neozip-ci.yml @@ -1,15 +1,8 @@ name: "neozip: CI" on: - push: - paths: - - 'neozip/**' - - '.github/workflows/neozip-*.yml' - pull_request: - paths: - - 'neozip/**' - - '.github/workflows/neozip-*.yml' workflow_dispatch: + workflow_call: concurrency: group: neozip-${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/neozip-codeql.yml b/.github/workflows/neozip-codeql.yml index 2bc5f489bd..48bcae3607 100644 --- a/.github/workflows/neozip-codeql.yml +++ b/.github/workflows/neozip-codeql.yml @@ -3,8 +3,6 @@ name: "neozip: CodeQL" on: workflow_call: workflow_dispatch: - schedule: - - cron: "27 17 * * 0" jobs: analyze: diff --git a/.github/workflows/neozip-fuzz.yml b/.github/workflows/neozip-fuzz.yml index 59b74debab..2530386d59 100644 --- a/.github/workflows/neozip-fuzz.yml +++ b/.github/workflows/neozip-fuzz.yml @@ -1,16 +1,8 @@ name: "neozip: Fuzz" on: - push: - branches: [master, develop] - paths: - - 'neozip/**' - - '.github/workflows/neozip-fuzz.yml' - pull_request: - paths: - - 'neozip/**' - - '.github/workflows/neozip-fuzz.yml' workflow_dispatch: + workflow_call: concurrency: group: neozip-fuzz-${{ github.ref }} diff --git a/.github/workflows/neozip-lint.yml b/.github/workflows/neozip-lint.yml index 775856874c..1e25739b2d 100644 --- a/.github/workflows/neozip-lint.yml +++ b/.github/workflows/neozip-lint.yml @@ -1,10 +1,8 @@ name: "neozip: Lint" on: - pull_request: - paths: - - 'neozip/**' workflow_dispatch: + workflow_call: jobs: lint: diff --git a/.github/workflows/neozip-release.yml b/.github/workflows/neozip-release.yml index 6f9610eaf3..77f23d4150 100644 --- a/.github/workflows/neozip-release.yml +++ b/.github/workflows/neozip-release.yml @@ -1,9 +1,8 @@ name: "neozip: Release" on: - push: - tags: - - 'neozip-*' + workflow_dispatch: + workflow_call: defaults: run: diff --git a/.github/workflows/repo-dependency-review.yml b/.github/workflows/repo-dependency-review.yml index 469ecfbdfb..6b65fadd71 100644 --- a/.github/workflows/repo-dependency-review.yml +++ b/.github/workflows/repo-dependency-review.yml @@ -3,7 +3,8 @@ name: "Dependency Review" # Scans dependency manifest changes in every pull request. # Blocks merging of PRs that introduce known-vulnerable packages. on: - pull_request: + workflow_dispatch: + workflow_call: permissions: contents: read diff --git a/.github/workflows/repo-scorecards.yml b/.github/workflows/repo-scorecards.yml index 06291bb004..2dc2ca7a96 100644 --- a/.github/workflows/repo-scorecards.yml +++ b/.github/workflows/repo-scorecards.yml @@ -1,9 +1,9 @@ name: "Scorecard supply-chain security" on: + workflow_dispatch: + workflow_call: branch_protection_rule: - schedule: - - cron: '20 7 * * 2' push: branches: [master, main, develop] diff --git a/.github/workflows/repo-stale.yml b/.github/workflows/repo-stale.yml index 9919ce314a..4973acd172 100644 --- a/.github/workflows/repo-stale.yml +++ b/.github/workflows/repo-stale.yml @@ -1,8 +1,8 @@ name: "Stale: Comment and close stale issues and PRs" on: - schedule: - - cron: '0 0 * * *' + workflow_dispatch: + workflow_call: permissions: contents: read diff --git a/.github/workflows/tomlplusplus-ci.yml b/.github/workflows/tomlplusplus-ci.yml index 974601fdf9..829c985e4d 100644 --- a/.github/workflows/tomlplusplus-ci.yml +++ b/.github/workflows/tomlplusplus-ci.yml @@ -1,25 +1,8 @@ name: "tomlplusplus: CI" on: - push: - paths: - - "tomlplusplus/**.h" - - "tomlplusplus/**.hpp" - - "tomlplusplus/**.cpp" - - "tomlplusplus/**.inl" - - "tomlplusplus/**.py" - - "tomlplusplus/**/meson.build" - - ".github/workflows/tomlplusplus-ci.yml" - pull_request: - paths: - - "tomlplusplus/**.h" - - "tomlplusplus/**.hpp" - - "tomlplusplus/**.cpp" - - "tomlplusplus/**.inl" - - "tomlplusplus/**.py" - - "tomlplusplus/**/meson.build" - - ".github/workflows/tomlplusplus-ci.yml" workflow_dispatch: + workflow_call: concurrency: group: tomlplusplus-${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/tomlplusplus-fuzz.yml b/.github/workflows/tomlplusplus-fuzz.yml index 7d346a172c..f326b6301b 100644 --- a/.github/workflows/tomlplusplus-fuzz.yml +++ b/.github/workflows/tomlplusplus-fuzz.yml @@ -1,16 +1,8 @@ name: "tomlplusplus: Fuzz" on: - push: - branches: [master] - paths: - - 'tomlplusplus/**' - - '.github/workflows/tomlplusplus-fuzz.yml' - pull_request: - paths: - - 'tomlplusplus/**' - - '.github/workflows/tomlplusplus-fuzz.yml' workflow_dispatch: + workflow_call: concurrency: group: tomlplusplus-fuzz-${{ github.ref }} diff --git a/.github/workflows/tomlplusplus-gh-pages.yml b/.github/workflows/tomlplusplus-gh-pages.yml index f25064b6bb..33161dbf2d 100644 --- a/.github/workflows/tomlplusplus-gh-pages.yml +++ b/.github/workflows/tomlplusplus-gh-pages.yml @@ -1,17 +1,8 @@ name: "tomlplusplus: gh-pages" on: - push: - branches: - - master - paths: - - 'tomlplusplus/**.h' - - 'tomlplusplus/**.hpp' - - 'tomlplusplus/**.dox' - - 'tomlplusplus/**.md' - - 'tomlplusplus/docs/**' - - '.github/workflows/tomlplusplus-gh-pages.yml' workflow_dispatch: + workflow_call: jobs: gh-pages: |
