diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8403e51..920aada 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,103 +1,306 @@ # -# @Project: vitest-runner -# @Filename: /.github/workflows/ci.yml -# @Date: 2026-02-25 16:51:43 -08:00 (1772067103) -# @Author: Nate Hyson +# @Project: @cldmv/.github +# @Filename: /examples/individual-repo-workflows/core-cicd/ci.yml +# @Date: 2026-05-20 00:00:00 -07:00 (1779606000) +# @Author: Nate Corcoran # @Email: -# ----- -# @Last modified by: Nate Hyson (Shinrai@users.noreply.github.com) -# @Last modified time: 2026-02-25 20:55:52 -08:00 (1772081752) -# ----- # @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved. # -name: CI +# Individual repo: .github/workflows/ci.yml +name: ๐Ÿงช CI Tests & Build on: - push: - branches: ["**"] - pull_request: + # Note: do NOT add `paths:` / `paths-ignore:` at the trigger level. Doing + # that makes GitHub skip the workflow entirely for docs-only changes, which + # means `Required PR Check` never posts and the ruleset blocks the merge. + # The reusable workflow's `paths-gate` job does the same job from inside, + # and exposes a `docs_only` output so this workflow can still green-light + # the required check for docs-only PRs (see `required-check` below). The + # ignore globs themselves are passed via the `paths_ignore:` input below + # โ€” override there if your repo needs different rules. + # + # `push` fires for branches in this repo only (forks push to their own remote, + # not ours). Branch protection on the PR reads the status check from the + # commit SHA, so this single trigger covers both pre-PR pushes and PR head + # updates without duplicating runs. + push: + # Bot-managed branches (badges, gh-pages) carry no source to test. + branches-ignore: [badges, gh-pages] + # `pull_request` covers two cases: + # - Fork PRs (push doesn't fire upstream for fork commits). + # - Release PRs from `next` / `hotfixes` โ†’ `master`. Their head SHA is + # a bot `chore: bump version` commit that workflow-ci.yml's + # `commit-gate` job filters out on the push path, so without the + # pull_request fallback the release PR's `Required PR Check` + # status never gets posted and the ruleset blocks the merge. + # `branches:` includes the v4 integration branches so PRs targeting + # `next` / `hotfixes` get CI too โ€” feature PRs from forks would + # otherwise get nothing. Non-fork feature PRs still skip the + # pull_request `ci` job (push covers them); see the `if:` on the job. + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + branches: [master, main, next, hotfixes] + workflow_dispatch: + inputs: + debug: + description: "Enable debug logging for troubleshooting" + type: boolean + required: false + default: false + node_version: + description: "Node.js version to use (default: lts/*)" + type: string + required: false + default: "lts/*" + min_node_version: + description: "Minimum Node.js version for matrix testing (default: 20, oldest non-EOL)" + type: string + required: false + default: "20" + max_node_major: + description: "Override max Node.js major version (default: 24)" + type: string + required: false + default: "24" + lts_only_matrix: + description: "Only include even-numbered (LTS) Node.js major versions in the test matrix" + type: boolean + required: false + default: true + package_manager: + description: "Package manager (npm or yarn)" + type: string + required: false + default: "npm" + test_environment: + description: "Environment for tests (affects NODE_ENV and NODE_OPTIONS --conditions flag)" + type: string + required: false + default: "development" + # โ”€โ”€ Coverage badge โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + enable_coverage_badge: + description: "Run the coverage + badge-push job after CI passes" + type: boolean + required: false + default: true + coverage_command: + description: "Command to run tests and generate coverage data" + type: string + required: false + default: "npm run ci:coverage" + coverage_summary_path: + description: "Path to the coverage-summary.json produced by Jest / c8" + type: string + required: false + default: "coverage/coverage-summary.json" + badges_branch: + description: "Branch where the badge JSON is published" + type: string + required: false + default: "badges" + badge_filename: + description: "Filename for the badge JSON committed to the badges branch" + type: string + required: false + default: "coverage.json" + upload_coverage_artifact: + description: "Upload the full coverage/ directory as a workflow artifact" + type: boolean + required: false + default: true + # โ”€โ”€ Type check โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + type_check_command: + description: "Command to run type checking" + type: string + required: false + default: "npm run types:check" + skip_type_check: + description: "Skip the type-check step in the coverage-badge job" + type: boolean + required: false + default: false + default_branch: + description: "Default branch name โ€” badge is only pushed on pushes to this branch" + type: string + required: false + default: "master" + enable_coverage_pr_comment: + description: "Inject a coverage badge into the PR description on pull request events" + type: boolean + required: false + default: true -jobs: - test: - runs-on: ubuntu-latest - - strategy: - matrix: - node: [20, 22, 24] - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node }} - cache: npm +# Cancel superseded runs on feature branches; keep every master/main run as the +# permanent green record. Keyed on github.ref so push and pull_request events +# for the same branch share a group (the `if:` on the ci job already prevents +# non-fork PR sync from running, but the shared group guards against edge +# cases). +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/master' && github.ref != 'refs/heads/main' }} - - name: Install - run: npm ci +# Workflow-level: matches the broadest write surface the called +# `workflow-ci.yml` reaches across its branches: +# - coverage-badge: contents:write (push to `badges` branch) +# - coverage-pr-comment: pull-requests:write (edit PR description body) +# Jobs that don't need write (CI matrix, commit-gate, the mirror below) +# inherit but never exercise the surface. The mirror job overrides to +# `permissions: {}` since it's pure shell. +permissions: + contents: write + pull-requests: write - - name: Lint - run: npm run lint - - - name: Format check - run: npm run format:check - - - name: Type check - run: npm run types:check - - - name: Tests + coverage - run: npm run ci:coverage +jobs: + ci: + name: ๐Ÿ—๏ธ Continuous Integration + # Run on pull_request when: + # - The PR is from a fork (push doesn't fire upstream for fork commits). + # - The PR is a v4 release PR โ€” head ref is `next` or `hotfixes` + # targeting `master`/`main`. Push-event CI on the head SHA is + # unreliable for these because workflow-ci.yml's `commit-gate` + # filters out the bot's `chore: bump version` commit, so without + # this fallback the release PR's `Required PR Check` never posts. + # Other (in-repo, non-release) PRs skip โ€” the push event on the head + # branch already ran CI and posted status to the SHA. + if: | + github.event_name != 'pull_request' || + github.event.pull_request.head.repo.fork == true || + github.event.pull_request.head.ref == 'next' || + github.event.pull_request.head.ref == 'hotfixes' + uses: CLDMV/.github/.github/workflows/workflow-ci.yml@v4 + with: + package_name: "@cldmv/vitest-runner" # Required: replace with your NPM package name + # Globs that should NOT trigger the heavy CI matrix. When every changed + # file matches one of these, `docs_only=true` flows out of the reusable + # and `required-check` below posts a green Required PR Check without + # running CI. The default in the reusable matches these โ€” override only + # if your repo needs different rules. + paths_ignore: | + **.md + docs/** + *.md + LICENSE + .gitignore + debug: ${{ github.event.inputs.debug == 'true' }} + node_version: ${{ github.event.inputs.node_version || 'lts/*' }} + min_node_version: ${{ github.event.inputs.min_node_version || '20' }} + max_node_major: ${{ github.event.inputs.max_node_major || '24' }} + # LTS-only matrix (even majors: 20, 22, 24, โ€ฆ) on every event. Odd majors + # (21, 23, โ€ฆ) are non-LTS interim releases, and the native-binding test + # toolchain (vitest 4 / rolldown / vite 8) excludes them via `engines` + # (`^20.19.0 || >=22.12.0`), so a "full matrix" on them only re-discovers a + # known toolchain gap ("Cannot find native binding") rather than a real + # per-version regression. workflow_dispatch can still opt out (set false). + lts_only_matrix: ${{ github.event.inputs.lts_only_matrix != 'false' }} + package_manager: ${{ github.event.inputs.package_manager || 'npm' }} + test_command: "npm test" # Use defaults: NODE_ENV=development, NODE_OPTIONS=--conditions=development + # test_command: "NODE_OPTIONS='--conditions=slothlet-dev' npm test" # Override NODE_OPTIONS only + # test_command: "NODE_ENV=test npm test" # Override NODE_ENV only + # test_command: "NODE_ENV=test NODE_OPTIONS='--conditions=slothlet-dev' npm test" # Override both + test_environment: ${{ github.event.inputs.test_environment || 'development' }} # Alternative to setting in test_command + build_command: "npm run build:ci" + skip_performance_tests: false + skip_matrix_tests: false - - name: Compute coverage badge JSON - run: | - node -e " - const s = JSON.parse(require('fs').readFileSync('coverage/coverage-summary.json','utf8')).total; - const avg = ((s.statements.pct + s.branches.pct + s.functions.pct + s.lines.pct) / 4).toFixed(1); - const color = avg >= 90 ? 'brightgreen' : avg >= 75 ? 'green' : avg >= 60 ? 'yellow' : 'red'; - require('fs').writeFileSync('badge.json', JSON.stringify({schemaVersion:1,label:'coverage',message:avg+'%',color})); - console.log('Coverage avg:', avg+'%'); - " + # โ”€โ”€ Coverage badge โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + # Runs after a successful CI build; pushes a Shields.io-compatible badge + # JSON to the `badges` branch (signed commit via bot GPG). + # Only runs on direct pushes to default_branch โ€” PRs and feature branches + # are automatically skipped so coverage always reflects merged master code. + # Requires: the coverage_command produces coverage/coverage-summary.json + enable_coverage_badge: ${{ github.event.inputs.enable_coverage_badge != 'false' }} + default_branch: ${{ github.event.inputs.default_branch || 'master' }} # Badge only pushed when a push lands on this branch + coverage_command: ${{ github.event.inputs.coverage_command || 'npm run ci:coverage' }} + coverage_summary_path: ${{ github.event.inputs.coverage_summary_path || 'coverage/coverage-summary.json' }} + badges_branch: ${{ github.event.inputs.badges_branch || 'badges' }} + badge_filename: ${{ github.event.inputs.badge_filename || 'coverage.json' }} + upload_coverage_artifact: ${{ github.event.inputs.upload_coverage_artifact != 'false' }} - - name: Generate bot token - id: bot-token - uses: actions/create-github-app-token@v1 - with: - app-id: ${{ secrets.CLDMV_BOT_APP_ID }} - private-key: ${{ secrets.CLDMV_BOT_APP_PRIVATE_KEY }} + # โ”€โ”€ Type check (runs inside the coverage-badge job) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + type_check_command: ${{ github.event.inputs.type_check_command || 'npm run types:check' }} + skip_type_check: ${{ github.event.inputs.skip_type_check == 'true' }} - - name: Import bot GPG key - uses: crazy-max/ghaction-import-gpg@v6 - with: - gpg_private_key: ${{ secrets.CLDMV_BOT_GPG_PRIVATE_KEY }} - passphrase: ${{ secrets.CLDMV_BOT_GPG_PASSPHRASE }} - git_user_signingkey: true - git_commit_gpgsign: true + # โ”€โ”€ PR coverage badge โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + # Injects a Shields.io badge + breakdown table directly into the PR body + # on every push to the PR branch. Only fires on pull_request events; + # skipped automatically on push and workflow_dispatch. No files committed. + enable_coverage_pr_comment: ${{ github.event.inputs.enable_coverage_pr_comment != 'false' }} - - name: Push coverage badge to badges branch - env: - BOT_TOKEN: ${{ steps.bot-token.outputs.token }} - run: | - git config user.name "${{ secrets.CLDMV_BOT_NAME }}" - git config user.email "${{ secrets.CLDMV_BOT_EMAIL }}" - cp badge.json /tmp/coverage.json - if git fetch origin badges; then - git checkout badges - else - git checkout --orphan badges - git rm -rf . --quiet 2>/dev/null || true - fi - cp /tmp/coverage.json coverage.json - git add coverage.json - git diff --cached --quiet && echo "Badge unchanged" && exit 0 - git commit -S -m "ci: update coverage badge" - git push "https://x-access-token:${BOT_TOKEN}@github.com/${{ github.repository }}.git" badges - git checkout master + # Authentication & Bot Configuration + # The workflow supports automatic App token detection for enhanced permissions and proper attribution: + # - WITH App secrets: Operations attributed to CLDMV bot, enhanced permissions for workflow repositories + # - WITHOUT App secrets: Falls back to GitHub Actions bot with standard permissions + # Note: CI workflow currently only runs build/test jobs, but App secrets are included for consistency + # To set up App authentication, add these secrets to your repository settings: + secrets: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + # Optional: CLDMV Bot credentials for enhanced permissions and proper attribution + # If not provided, will use default GITHUB_TOKEN with GitHub Actions bot attribution + BOT_APP_CLIENT_ID: ${{ secrets.CLDMV_BOT_APP_CLIENT_ID }} + BOT_APP_PRIVATE_KEY: ${{ secrets.CLDMV_BOT_APP_PRIVATE_KEY }} + # Required when enable_coverage_badge: true + BOT_NAME: ${{ secrets.CLDMV_BOT_NAME }} + BOT_EMAIL: ${{ secrets.CLDMV_BOT_EMAIL }} + BOT_GPG_PRIVATE_KEY: ${{ secrets.CLDMV_BOT_GPG_PRIVATE_KEY }} + BOT_GPG_PASSPHRASE: ${{ secrets.CLDMV_BOT_GPG_PASSPHRASE }} - - name: Upload coverage artifact - uses: actions/upload-artifact@v4 - with: - name: coverage - path: coverage/ + # โœ… Stable check that mirrors the `ci` result so branch protection has a + # single, predictable status name to require. The push event runs on the + # same SHA that becomes the PR head, so the status attaches to the PR + # automatically โ€” no `pull_request` round-trip needed for non-fork + # non-release PRs. + required-check: + name: โœ… Required PR Check + needs: ci + # Mirror the `ci` job's gating exactly. The four cases that run: + # 1. push events (job needs CI run) + # 2. fork PRs (push doesn't cover forks) + # 3. release PRs from `next` โ†’ master/main (push covers SHA but commit-gate skips chore-bump) + # 4. release PRs from `hotfixes` โ†’ master/main (same reason) + # In-repo feature PRs targeting `next` / `hotfixes` skip on + # pull_request โ€” push on the head branch already posted the status + # on the SHA, and mirroring here would overwrite it. + if: | + always() && ( + github.event_name != 'pull_request' || + github.event.pull_request.head.repo.fork == true || + github.event.pull_request.head.ref == 'next' || + github.event.pull_request.head.ref == 'hotfixes' + ) + runs-on: ubuntu-latest + # Pure shell mirror โ€” no GitHub API access. Strip the workflow's + # write defaults to zero for this job. + permissions: {} + steps: + - name: Mirror reusable result + env: + IS_MASTER_SYNC: ${{ needs.ci.outputs.is_master_sync }} + DOCS_ONLY: ${{ needs.ci.outputs.docs_only }} + CI_RESULT: ${{ needs.ci.result }} + run: | + echo "ci.result=$CI_RESULT docs_only=$DOCS_ONLY is_master_sync=$IS_MASTER_SYNC" + # next/hotfixes was force-synced to master โ€” head SHA matches the + # default branch, nothing new to test, green-light without running CI. + if [ "$IS_MASTER_SYNC" = "true" ]; then + echo "Branch tip matches master โ€” Required PR Check passes without running CI." + exit 0 + fi + # Docs-only PR โ€” the reusable skipped the heavy chain and exported + # docs_only=true. Green-light Required PR Check so the ruleset + # doesn't block a docs change. + if [ "$DOCS_ONLY" = "true" ]; then + echo "Docs-only change โ€” Required PR Check passes without running CI." + exit 0 + fi + if [ "$CI_RESULT" = "success" ]; then + echo "Reusable CI passed." + exit 0 + elif [ "$CI_RESULT" = "failure" ] || [ "$CI_RESULT" = "cancelled" ]; then + echo "Reusable CI did not pass." + exit 1 + else + # covers 'skipped' or undefined; force red to avoid silent green + echo "Reusable CI produced no pass/fail; treating as failure." + exit 1 + fi diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..faed0f4 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,70 @@ +# +# @Project: @cldmv/.github +# @Filename: /examples/individual-repo-workflows/security/codeql.yml +# @Date: 2026-05-20 00:00:00 -07:00 (1779606000) +# @Author: Nate Corcoran +# @Email: +# @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved. +# + +# Individual repo: .github/workflows/codeql.yml +# +# REQUIRED REPO SETTING โ€” CodeQL must be in "Advanced" mode for this workflow +# to upload SARIF. If the repo has CodeQL "Default setup" enabled (the +# GitHub-managed alternative), upload runs fail with: +# +# "Code Scanning could not process the submitted SARIF file: CodeQL +# analyses from advanced configurations cannot be processed when the +# default setup is enabled" +# +# The org-bootstrap-repo action automatically disables default setup +# (overwrite-with-warn) on every fanout run, so a freshly-bootstrapped +# repo lands in the right state by default. If you want to KEEP default +# setup (the GitHub-managed config) instead of this workflow, DELETE +# this codeql.yml file โ€” with the conflict gone, the bootstrap leaves +# default setup alone on subsequent runs. +# +# Manual fix when running outside the bootstrap: +# Settings โ†’ Code security and analysis โ†’ Code scanning โ†’ CodeQL +# analysis โ†’ โš™๏ธ โ†’ Switch to advanced. +name: ๐Ÿ” CodeQL + +on: + push: + branches: [master, main] + # Same fork-PR consideration as ci.yml: pull_request fires for forks; SARIF + # upload to base-repo Security tab fails with read-only token. Acceptable โ€” + # push-to-master analysis after merge catches anything missed. DO NOT use + # pull_request_target (runs base-repo workflow with secrets against fork + # code; dangerous). + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + # Include the v4 integration branches (`next`, `hotfixes`) so feature + # and hotfix PRs trigger CodeQL. Without these, branch protection + # rulesets that require the CodeQL check on `next`/`hotfixes` will + # sit on "waiting for results" indefinitely. Branches that don't + # exist in a given repo simply never trigger the workflow โ€” harmless + # for repos that haven't adopted the v4 staging-branch flow. + branches: [master, main, next, hotfixes] + schedule: + - cron: "37 14 * * 1" # weekly Monday 14:37 UTC; GitHub updates queries over time + +permissions: + security-events: write + contents: read + actions: read + +concurrency: + group: codeql-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/master' && github.ref != 'refs/heads/main' }} + +jobs: + analyze: + uses: CLDMV/.github/.github/workflows/reusable-codeql.yml@v4 + with: + languages: "javascript-typescript" + # Override defaults if needed: + # queries: "security-extended,security-and-quality" + # paths_ignore: "node_modules/,dist/,coverage/,**/test/**" + # config_file: ".github/codeql-config.yml" + # build_mode: "autobuild" diff --git a/.github/workflows/feature-pr.yml b/.github/workflows/feature-pr.yml new file mode 100644 index 0000000..fcee49e --- /dev/null +++ b/.github/workflows/feature-pr.yml @@ -0,0 +1,59 @@ +# +# @Project: @cldmv/.github +# @Filename: /examples/individual-repo-workflows/release-flow-v4/feature-pr.yml +# @Author: Nate Corcoran +# @Email: +# @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved. +# + +# Individual repo: .github/workflows/feature-pr.yml +# +# v4 ergonomics: auto-opens (and refreshes) a PR from a code-side branch to the +# right integration branch on every push. +# +# Mapping (matches CLDMV/.github docs/conventions/branch-naming.md): +# feat/*, feature/*, fix/*, release/*, chore/*, refactor/*, +# docs/*, ci/*, perf/*, test/*, style/* โ†’ next +# hotfix/* โ†’ hotfixes +# +# Reserved branches NOT auto-PR'd: dependabot/* and copilot/* (they manage their +# own PRs); badges, gh-pages (bot-only); master/main (the target). +# +# Thin caller: all job logic (target detection, changelog body, PR create/ +# refresh) lives in the reusable, pinned at @v4. Bumping the pin carries fixes +# without editing this file. The `push` trigger and its branch-prefix list stay +# here (GitHub requires the trigger local, and the list is per-repo config). +name: ๐Ÿ”€ Feature PR (v4) + +on: + push: + branches: + # CUSTOMIZE: prune this list to whichever branch prefixes your + # repo uses. Must align with the `case` statement in the reusable. + - 'feat/**' + - 'feature/**' + - 'fix/**' + - 'release/**' + - 'chore/**' + - 'refactor/**' + - 'docs/**' + - 'ci/**' + - 'perf/**' + - 'test/**' + - 'style/**' + - 'hotfix/**' + +concurrency: + group: feature-pr-${{ github.repository }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + open-pr: + permissions: + contents: read + pull-requests: write + uses: CLDMV/.github/.github/workflows/workflow-feature-pr.yml@v4 + secrets: + # Map your repo/org secrets to the expected names. + BOT_APP_CLIENT_ID: ${{ secrets.CLDMV_BOT_APP_CLIENT_ID }} + BOT_APP_PRIVATE_KEY: ${{ secrets.CLDMV_BOT_APP_PRIVATE_KEY }} diff --git a/.github/workflows/hotfix-redirector.yml b/.github/workflows/hotfix-redirector.yml new file mode 100644 index 0000000..830cad6 --- /dev/null +++ b/.github/workflows/hotfix-redirector.yml @@ -0,0 +1,51 @@ +# +# @Project: @cldmv/.github +# @Filename: /examples/individual-repo-workflows/release-flow-v4/hotfix-redirector.yml +# @Date: 2026-05-22 00:00:00 -07:00 (1779778800) +# @Author: Nate Corcoran +# @Email: +# @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved. +# + +# Individual repo: .github/workflows/hotfix-redirector.yml +# +# v4 hotfix lane: retarget hotfix/security PRs to the `hotfixes` branch. +# +# Two paths trigger a redirect (CLDMV/.github docs/conventions/release-flow-v4.md ยง5.2, ยง6.5): +# 1. Head branch matches `hotfix/*` or `security/*` (human-driven hotfix flow). +# 2. Author is `dependabot[bot]` AND its base isn't Dependabot's routine +# target-branch (default "next") โ€” GitHub always overrides dependabot.yml's +# target-branch for security updates, so a base landing on the default +# branch instead of "next" is itself the signal. Routine version bumps stay +# on "next". +# +# Thin caller: all job logic (token, checkout, git identity, redirect action) +# lives in the reusable, pinned at @v4. Bumping the pin carries new requirements +# (e.g. the checkout + git identity the cherry-pick path needs) without editing +# this file. +name: ๐Ÿ”€ Hotfix PR Redirector (v4) + +# SECURITY NOTE: pull_request_target runs in the BASE repo's context with WRITE +# permissions + secrets. The reusable checks out `hotfixes` (a trusted base-repo +# branch, NOT the PR head/fork) and only cherry-picks/pushes against it. +# +# `opened` only (NOT `edited`): if a maintainer manually re-targets the PR, we +# must not fight them by redirecting again. +on: + pull_request_target: + types: [opened] + +concurrency: + group: hotfix-redirector-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + redirect: + permissions: + contents: write + pull-requests: write + uses: CLDMV/.github/.github/workflows/workflow-hotfix-redirector.yml@v4 + secrets: + # Map your repo/org secrets to the expected names. + BOT_APP_CLIENT_ID: ${{ secrets.CLDMV_BOT_APP_CLIENT_ID }} + BOT_APP_PRIVATE_KEY: ${{ secrets.CLDMV_BOT_APP_PRIVATE_KEY }} diff --git a/.github/workflows/hotfixes-release.yml b/.github/workflows/hotfixes-release.yml new file mode 100644 index 0000000..444884c --- /dev/null +++ b/.github/workflows/hotfixes-release.yml @@ -0,0 +1,55 @@ +# +# @Project: @cldmv/.github +# @Filename: /examples/individual-repo-workflows/release-flow-v4/hotfixes-release.yml +# @Date: 2026-05-22 00:00:00 -07:00 (1779778800) +# @Author: Nate Corcoran +# @Email: +# @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved. +# + +# Individual repo: .github/workflows/hotfixes-release.yml +# +# v4 hotfix lane: maintain the ONE persistent `hotfixes โ†’ master` release PR. +# +# Mirror of next-release.yml but for the `hotfixes` integration branch +# (CLDMV/.github docs/conventions/release-flow-v4.md ยง5.4, ยง6.2). Fires on every +# push to `hotfixes` (hotfix/security PR squash-merges land here), and +# resolves-or-creates the persistent `hotfixes โ†’ master` release PR. +# +# Thin caller: all job logic (plan / create / refresh) lives in the reusable, +# pinned at @v4. Bumping the pin carries fixes without editing this file. +# CUSTOMIZE `package_name` / `build_command` to match your package (same values +# as your next-release.yml). +name: ๐Ÿš‘ Hotfixes Release (v4) + +on: + push: + branches: [hotfixes] + workflow_dispatch: # manual kick โ€” e.g. to open/refresh the PR for content already on `hotfixes` + +concurrency: + group: hotfixes-release-${{ github.repository }} + cancel-in-progress: false + +jobs: + release: + permissions: + contents: write + pull-requests: write + uses: CLDMV/.github/.github/workflows/workflow-hotfixes-release.yml@v4 + with: + package_name: "@cldmv/vitest-runner" + build_command: "npm run build:ci" + secrets: + # Map your repo/org secrets to the expected names. + BOT_APP_CLIENT_ID: ${{ secrets.CLDMV_BOT_APP_CLIENT_ID }} + BOT_APP_PRIVATE_KEY: ${{ secrets.CLDMV_BOT_APP_PRIVATE_KEY }} + # Optional release-PR notifier webhooks โ€” each is independently + # opt-in: leave one unset and that channel is silently skipped. + # Delete the lines you don't use. + DISCORD_RELEASE_PR_PUBLIC_WEBHOOK: ${{ secrets.DISCORD_RELEASE_PR_PUBLIC_WEBHOOK }} + DISCORD_RELEASE_PR_PRIVATE_WEBHOOK: ${{ secrets.DISCORD_RELEASE_PR_PRIVATE_WEBHOOK }} + SLACK_RELEASE_PR_PUBLIC_WEBHOOK: ${{ secrets.SLACK_RELEASE_PR_PUBLIC_WEBHOOK }} + SLACK_RELEASE_PR_PRIVATE_WEBHOOK: ${{ secrets.SLACK_RELEASE_PR_PRIVATE_WEBHOOK }} + GENERIC_RELEASE_PR_PUBLIC_WEBHOOK: ${{ secrets.GENERIC_RELEASE_PR_PUBLIC_WEBHOOK }} + GENERIC_RELEASE_PR_PRIVATE_WEBHOOK: ${{ secrets.GENERIC_RELEASE_PR_PRIVATE_WEBHOOK }} diff --git a/.github/workflows/master-commit-audit.yml b/.github/workflows/master-commit-audit.yml new file mode 100644 index 0000000..7caf1f3 --- /dev/null +++ b/.github/workflows/master-commit-audit.yml @@ -0,0 +1,52 @@ +# +# @Project: @cldmv/.github +# @Filename: /examples/individual-repo-workflows/release-companions/master-commit-audit.yml +# @Date: 2026-05-31 00:00:00 -07:00 (1780210800) +# @Author: Nate Corcoran +# @Email: +# @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved. +# + +# Individual repo: .github/workflows/master-commit-audit.yml +# +# Post-merge safety net: when any commit lands on the default branch, verify +# its subject matches the expected release-flow patterns. On miss, auto-file a +# GitHub Issue (deduped by SHA) so the alert is persistent and assignable โ€” +# not just a red โŒ that dies in inbox. +# +# Catches: release-workflow title-generation regressions, branch-protection +# bypasses, unexpected bot commits, direct emergency pushes. +# +# Thin caller: steps, the action ref, and the canonical commit-subject pattern +# set all live in reusable-master-commit-audit.yml@v4 (the patterns come from +# the audit-commit-subject action's default). Nothing here can drift. +name: ๐Ÿงพ Master Commit Audit + +on: + push: + branches: [master, main] + +permissions: + contents: read + issues: write + +jobs: + audit: + uses: CLDMV/.github/.github/workflows/reusable-master-commit-audit.yml@v4 + with: + # allowed_patterns omitted โ†’ inherit the canonical default + # (release + chore + merge). Uncomment ONLY if this repo lands other + # commit shapes directly on the default branch: + # allowed_patterns: | + # ^release: v\d+\.\d+\.\d+( - .+?)?( \(#\d+\))?$ + # ^chore(\([^)]+\))?: .+ + # ^Merge pull request #\d+ from .+ + # ^feat(\([^)]+\))?: .+ + issue_labels: "type: ci,priority: high" + # issue_assignee: "shinrai" # uncomment to auto-assign + # Optional bot App credentials โ€” when set, the audit issue is filed by + # the consumer's bot App instead of github-actions[bot]. Remove both + # lines to fall back to GITHUB_TOKEN. + secrets: + BOT_APP_CLIENT_ID: ${{ secrets.CLDMV_BOT_APP_CLIENT_ID }} + BOT_APP_PRIVATE_KEY: ${{ secrets.CLDMV_BOT_APP_PRIVATE_KEY }} diff --git a/.github/workflows/next-release.yml b/.github/workflows/next-release.yml new file mode 100644 index 0000000..4518597 --- /dev/null +++ b/.github/workflows/next-release.yml @@ -0,0 +1,61 @@ +# +# @Project: @cldmv/.github +# @Filename: /examples/individual-repo-workflows/release-flow-v4/next-release.yml +# @Date: 2026-05-22 00:00:00 -07:00 (1779778800) +# @Author: Nate Corcoran +# @Email: +# @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved. +# + +# Individual repo: .github/workflows/next-release.yml +# +# v4 core: maintain the ONE persistent `next โ†’ master` release PR for this repo. +# +# Fires on every push to `next` (contributor PR squash-merges land here). +# Resolves the existing release PR and refreshes it, or creates it the first +# time `next` diverges from master. The release PR batches all accumulated +# feature commits into a single release โ€” that batching is v4's whole point +# (see CLDMV/.github docs/conventions/release-flow-v4.md ยง5.3, ยง6.1). +# +# Thin caller: all job logic (plan / create / refresh) lives in the reusable, +# pinned at @v4. Bumping the pin carries fixes without editing this file. +# CUSTOMIZE: +# - `package_name` โ†’ your npm package (or any unique identifier) +# - `build_command` โ†’ your build script, or a stub like +# `echo 'โœ“ no build step'` for a meta package (optional; +# defaults to `npm run build:ci`) +name: ๐Ÿš€ Next Release (v4) + +on: + push: + branches: [next] + workflow_dispatch: # manual kick โ€” e.g. to open/refresh the PR for content already on `next` + +# Serialize: each run re-resolves the current PR state, so queueing (not +# cancelling) avoids a create/refresh race when pushes land back-to-back. +concurrency: + group: next-release-${{ github.repository }} + cancel-in-progress: false + +jobs: + release: + permissions: + contents: write + pull-requests: write + uses: CLDMV/.github/.github/workflows/workflow-next-release.yml@v4 + with: + package_name: "@cldmv/vitest-runner" + build_command: "npm run build:ci" + secrets: + # Map your repo/org secrets to the expected names. + BOT_APP_CLIENT_ID: ${{ secrets.CLDMV_BOT_APP_CLIENT_ID }} + BOT_APP_PRIVATE_KEY: ${{ secrets.CLDMV_BOT_APP_PRIVATE_KEY }} + # Optional release-PR notifier webhooks โ€” each is independently + # opt-in: leave one unset and that channel is silently skipped. + # Delete the lines you don't use. + DISCORD_RELEASE_PR_PUBLIC_WEBHOOK: ${{ secrets.DISCORD_RELEASE_PR_PUBLIC_WEBHOOK }} + DISCORD_RELEASE_PR_PRIVATE_WEBHOOK: ${{ secrets.DISCORD_RELEASE_PR_PRIVATE_WEBHOOK }} + SLACK_RELEASE_PR_PUBLIC_WEBHOOK: ${{ secrets.SLACK_RELEASE_PR_PUBLIC_WEBHOOK }} + SLACK_RELEASE_PR_PRIVATE_WEBHOOK: ${{ secrets.SLACK_RELEASE_PR_PRIVATE_WEBHOOK }} + GENERIC_RELEASE_PR_PUBLIC_WEBHOOK: ${{ secrets.GENERIC_RELEASE_PR_PUBLIC_WEBHOOK }} + GENERIC_RELEASE_PR_PRIVATE_WEBHOOK: ${{ secrets.GENERIC_RELEASE_PR_PRIVATE_WEBHOOK }} diff --git a/.github/workflows/next-reset.yml b/.github/workflows/next-reset.yml new file mode 100644 index 0000000..87ba088 --- /dev/null +++ b/.github/workflows/next-reset.yml @@ -0,0 +1,43 @@ +# +# @Project: @cldmv/.github +# @Filename: /examples/individual-repo-workflows/release-flow-v4/next-reset.yml +# @Date: 2026-05-22 00:00:00 -07:00 (1779778800) +# @Author: Nate Corcoran +# @Email: +# @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved. +# + +# Individual repo: .github/workflows/next-reset.yml +# +# v4 core: after a release lands on master, re-sync the integration branches +# (CLDMV/.github docs/conventions/release-flow-v4.md ยง6.3, ยง7). +# +# - `hotfixes` is ALWAYS force-reset to master HEAD after any release. +# - `next` depends on which lane released: +# * normal release (next โ†’ master, or a v3-style feat โ†’ master): +# force-reset `next` to master HEAD (ยง7.1). +# * hotfix release (hotfixes โ†’ master): MERGE master into `next` instead, +# so next's accumulated feature work is preserved (ยง7.2, option B). +# +# Thin caller: all job logic (the wait-for-tags gate + the branch sync) lives in +# the reusable, pinned at @v4. Bumping the pin carries fixes without editing +# this file. +name: โ™ป๏ธ Next/Hotfixes Reset (v4) + +on: + push: + branches: [master, main] + +concurrency: + group: next-reset-${{ github.repository }} + cancel-in-progress: false + +jobs: + sync: + permissions: + contents: write + uses: CLDMV/.github/.github/workflows/workflow-next-reset.yml@v4 + secrets: + # Map your repo/org secrets to the expected names. + BOT_APP_CLIENT_ID: ${{ secrets.CLDMV_BOT_APP_CLIENT_ID }} + BOT_APP_PRIVATE_KEY: ${{ secrets.CLDMV_BOT_APP_PRIVATE_KEY }} diff --git a/.github/workflows/pr-title-normalizer.yml b/.github/workflows/pr-title-normalizer.yml new file mode 100644 index 0000000..adba973 --- /dev/null +++ b/.github/workflows/pr-title-normalizer.yml @@ -0,0 +1,45 @@ +# +# @Project: @cldmv/.github +# @Filename: /examples/individual-repo-workflows/release-flow-v4/pr-title-normalizer.yml +# @Date: 2026-05-22 00:00:00 -07:00 (1779778800) +# @Author: Nate Corcoran +# @Email: +# @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved. +# + +# Individual repo: .github/workflows/pr-title-normalizer.yml +# +# Normalize contributor PR titles to Conventional Commits format, derived from +# the highest-priority commit in the PR. The release flow expects this shape, so +# a v4 repo wants this enabled. (Also backportable to v3 repos โ€” the underlying +# action shipped in v3.3.0; it owns all skip logic: bot authors, the +# long-running release PRs, titles already starting with `release:`, and titles +# that already conform.) +# +# Thin caller: all job logic lives in the reusable, pinned at @v4. Bumping the +# pin carries fixes without editing this file. +name: ๐Ÿท๏ธ PR Title Normalizer + +# SECURITY NOTE: pull_request_target runs in the BASE repo's context with WRITE +# permissions and access to secrets. SAFE โ€” the reusable's path is API-only (it +# never checks out or executes PR content). Triggers on opened + synchronize +# only (NOT edited): a maintainer hand-editing the title must not kick off a +# re-normalize loop. +on: + pull_request_target: + types: [opened, synchronize] + +concurrency: + group: pr-title-normalizer-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + normalize: + permissions: + contents: read + pull-requests: write + uses: CLDMV/.github/.github/workflows/workflow-pr-title-normalizer.yml@v4 + secrets: + # Map your repo/org secrets to the expected names. + BOT_APP_CLIENT_ID: ${{ secrets.CLDMV_BOT_APP_CLIENT_ID }} + BOT_APP_PRIVATE_KEY: ${{ secrets.CLDMV_BOT_APP_PRIVATE_KEY }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..12886be --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,139 @@ +# +# @Project: @cldmv/.github +# @Filename: /examples/individual-repo-workflows/core-cicd/publish.yml +# @Date: 2026-05-20 00:00:00 -07:00 (1779606000) +# @Author: Nate Corcoran +# @Email: +# @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved. +# + +# Individual repo: .github/workflows/publish.yml +name: ๐Ÿ“ฆ Release and Publish + +on: + push: + branches: [master, main] + paths-ignore: + - "**.md" + - ".github/ISSUE_TEMPLATE/**" + - ".github/PULL_REQUEST_TEMPLATE/**" + workflow_dispatch: + inputs: + debug: + description: "Enable debug logging for troubleshooting" + type: boolean + required: false + default: false + dry_run: + description: "Dry run mode - validate everything but don't publish or create releases" + type: boolean + required: false + default: false + node_version: + description: "Node.js version to use (default: lts/*)" + type: string + required: false + default: "lts/*" + package_manager: + description: "Package manager (npm or yarn)" + type: string + required: false + default: "npm" + test_environment: + description: "Environment for tests (affects NODE_ENV and NODE_OPTIONS --conditions flag)" + type: string + required: false + default: "development" + version: + description: "Version to publish (auto-detected from package.json if not provided)" + type: string + required: false + default: "" + publish_to_npm: + description: "Publish to NPM registry" + type: boolean + required: false + default: true + publish_to_github_packages: + description: "Publish to GitHub Packages registry" + type: boolean + required: false + default: true + min_node_version: + description: "Minimum Node.js version for matrix testing (enables matrix when set)" + type: string + required: false + default: "20" + max_node_major: + description: "Override max Node.js major version (default: 24)" + type: string + required: false + default: "24" + use_gpg: + description: "Enable GPG signing (if GPG secrets provided)" + type: boolean + required: false + default: false + +# NEVER cancel an in-flight publish โ€” half-published versions are nasty to +# clean up. Concurrent publishes for the same ref queue instead so they +# serialize naturally. +concurrency: + group: publish-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + publish-package: + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' + permissions: + contents: write + packages: write + id-token: write + uses: CLDMV/.github/.github/workflows/workflow-publish.yml@v4 + with: + package_name: "@cldmv/vitest-runner" # Required: replace with your NPM package name + debug: ${{ github.event.inputs.debug == 'true' }} + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + node_version: ${{ github.event.inputs.node_version || 'lts/*' }} + package_manager: ${{ github.event.inputs.package_manager || 'npm' }} + version: ${{ github.event.inputs.version || '' }} + publish_to_npm: ${{ github.event.inputs.publish_to_npm != 'false' }} + publish_to_github_packages: ${{ github.event.inputs.publish_to_github_packages != 'false' }} + publish_command: "" + github_packages_publish_command: "" + min_node_version: ${{ github.event.inputs.min_node_version || '20' }} + max_node_major: ${{ github.event.inputs.max_node_major || '24' }} + test_command: "npm test" # Use defaults: NODE_ENV=development, NODE_OPTIONS=--conditions=development + # test_command: "NODE_OPTIONS='--conditions=slothlet-dev' npm test" # Override NODE_OPTIONS only + # test_command: "NODE_ENV=test npm test" # Override NODE_ENV only + # test_command: "NODE_ENV=test NODE_OPTIONS='--conditions=slothlet-dev' npm test" # Override both + test_environment: ${{ github.event.inputs.test_environment || 'development' }} # Alternative to setting in test_command + build_command: "npm run build:ci" + # --- Satellite packages (optional) ---------------------------------------- + # Publish extra packages carved from this build's output (e.g. locale JSON, + # generated types) at the SAME version/commit as the core, each with its own + # @scope/name@version tag + GitHub Release. Uncomment to opt in. + # extra_packages: a JSON [{ name, dir }] array, OR a glob (single '*' in the + # final path segment, e.g. "dist-packages/*"). Empty = disabled. + # build_subpackages_command: runs after build_command to produce + # dist-packages// โ€” omit if build_command already does the carve. + # First publish of each new @scope/name needs a one-time token publish + + # trusted-publisher setup. Full details + the contract: + # docs/conventions/satellite-packages.md + # extra_packages: "dist-packages/*" + # build_subpackages_command: "npm run build:subpackages" + # -------------------------------------------------------------------------- + is_prerelease: false + release_source_only: false + create_documentation: true + skip_performance_tests: false + skip_matrix_tests: false + use_gpg: ${{ github.event.inputs.use_gpg == 'true' }} + secrets: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + BOT_APP_CLIENT_ID: ${{ secrets.CLDMV_BOT_APP_CLIENT_ID }} + BOT_APP_PRIVATE_KEY: ${{ secrets.CLDMV_BOT_APP_PRIVATE_KEY }} + TAGGER_NAME: ${{ secrets.CLDMV_BOT_NAME }} + TAGGER_EMAIL: ${{ secrets.CLDMV_BOT_EMAIL }} + GPG_PRIVATE_KEY: ${{ secrets.CLDMV_BOT_GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.CLDMV_BOT_GPG_PASSPHRASE }} diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 0000000..2101c19 --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,56 @@ +# +# @Project: @cldmv/.github +# @Filename: /examples/individual-repo-workflows/security/scorecard.yml +# @Date: 2026-05-31 00:00:00 -07:00 (1780210800) +# @Author: Nate Corcoran +# @Email: +# @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved. +# + +# Individual repo: .github/workflows/scorecard.yml +# +# OpenSSF Scorecard โ€” scans the repo against ~18 security best-practice checks +# and produces a 0-10 score. Thin caller: the steps and the SHA-pinned +# scorecard-action version live in reusable-scorecard.yml@v4, so the action +# version can't drift in this copy (it just calls the org reusable). Triggers +# stay here, per OpenSSF's recommended setup. +name: ๐Ÿ”ฌ OpenSSF Scorecard + +on: + branch_protection_rule: + schedule: + - cron: "32 7 * * 1" # weekly Monday 07:32 UTC + push: + branches: [master, main] + workflow_dispatch: + +# Caller must grant what the reusable needs โ€” notably id-token: write for the +# OpenSSF transparency-log publish. +# +# No workflow-level `permissions:` here โ€” grant on the `analyze` job below +# instead. scorecard-action's publish step verifies that write permissions +# were granted JOB-scoped, not workflow-wide (matching OSSF's own example: +# https://github.com/ossf/scorecard-action#example-workflow). A workflow-level +# grant satisfies GitHub's own reusable-workflow permission rules fine, but +# still trips scorecard-action's own check โ€” the rejection ("workflow +# verification failed: global perm is set to write: permission for X is set +# to write") means "granted globally," not "forbidden." +# +# Do NOT add security-events: write here while publish_results: true below. +# scorecard-action's publish step rejects submissions from a workflow whose +# token has security-events write access (it verifies the caller can't have +# tampered with results before they hit the public transparency log). That +# trade-off means the reusable's own SARIF-to-Security-tab upload step has no +# permission to run in this configuration; the public OpenSSF badge is the +# thing this default enables, so consumers take that trade-off by default. +# Only add security-events: write back (job-scoped) if publish_results is set +# to false instead (SARIF-to-Security-tab upload, no public badge). +jobs: + analyze: + permissions: + id-token: write + contents: read + actions: read + uses: CLDMV/.github/.github/workflows/reusable-scorecard.yml@v4 + with: + publish_results: true # set false for private repos / to skip the public badge diff --git a/.github/workflows/update-major-version-tags.yml b/.github/workflows/update-major-version-tags.yml new file mode 100644 index 0000000..cabcb26 --- /dev/null +++ b/.github/workflows/update-major-version-tags.yml @@ -0,0 +1,87 @@ +# +# @Project: @cldmv/.github +# @Filename: /examples/individual-repo-workflows/core-cicd/update-major-version-tags.yml +# @Date: 2026-05-20 00:00:00 -07:00 (1779606000) +# @Author: Nate Corcoran +# @Email: +# @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved. +# + +# Individual repo: .github/workflows/update-major-version-tags.yml +name: ๐Ÿท๏ธ Update Major Version Tags + +on: + release: + types: [published] + workflow_dispatch: + inputs: + debug: + description: "Enable debug logging for troubleshooting" + type: boolean + required: false + default: false + create_documentation: + description: "Whether to create/update VERSION_TAGS.md documentation" + type: boolean + required: false + default: false + use_gpg: + description: "Enable GPG signing (if GPG secrets provided)" + type: boolean + required: false + default: true + # Tag health configuration + max_tags: + description: "Maximum number of tags to process (safety limit)" + required: false + default: "100" + max_major_versions: + description: "Maximum number of major versions to process" + required: false + default: "10" + max_minor_versions: + description: "Maximum number of minor versions per major to process" + required: false + default: "10" + bot_patterns: + description: "JSON array of bot name patterns to identify bot signatures" + required: false + default: '["CLDMV Bot", "cldmv-bot", "github-actions[bot]"]' + include_patterns: + description: "JSON array of tag patterns to include (e.g. ['v*', 'release-*'])" + required: false + default: '["v*"]' + exclude_patterns: + description: "JSON array of tag patterns to exclude" + required: false + default: "[]" + +jobs: + update-tags: + # Skip release events fired without a tag_name (e.g. "untagged-" runs + # the bot or a prior code path can produce). The reusable workflow has its + # own tag-readiness polling for forward-facing prevention; this guard + # protects against legacy / external sources of untagged release events. + # Batch 1.2 from tmp/plan-future-workflows.md. + if: github.event_name != 'release' || github.event.release.tag_name != '' + uses: CLDMV/.github/.github/workflows/workflow-update-major-version-tags.yml@v4 + permissions: + contents: write + with: + debug: ${{ github.event.inputs.debug == 'true' }} + create_documentation: ${{ github.event.inputs.create_documentation == 'true' }} + use_gpg: ${{ github.event.inputs.use_gpg != 'false' }} + max_tags: ${{ github.event.inputs.max_tags || '100' }} + max_major_versions: ${{ github.event.inputs.max_major_versions || '10' }} + max_minor_versions: ${{ github.event.inputs.max_minor_versions || '10' }} + bot_patterns: ${{ github.event.inputs.bot_patterns || '["CLDMV Bot", "cldmv-bot", "github-actions[bot]"]' }} + include_patterns: ${{ github.event.inputs.include_patterns || '["v*"]' }} + exclude_patterns: ${{ github.event.inputs.exclude_patterns || '[]' }} + secrets: + # Map your repo/org secrets to the expected names + TAGGER_NAME: ${{ secrets.CLDMV_BOT_NAME }} + TAGGER_EMAIL: ${{ secrets.CLDMV_BOT_EMAIL }} + GPG_PRIVATE_KEY: ${{ secrets.CLDMV_BOT_GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.CLDMV_BOT_GPG_PASSPHRASE }} + BOT_APP_CLIENT_ID: ${{ secrets.CLDMV_BOT_APP_CLIENT_ID }} + BOT_APP_PRIVATE_KEY: ${{ secrets.CLDMV_BOT_APP_PRIVATE_KEY }} diff --git a/.github/workflows/v4-bootstrap.yml b/.github/workflows/v4-bootstrap.yml new file mode 100644 index 0000000..d8c86c2 --- /dev/null +++ b/.github/workflows/v4-bootstrap.yml @@ -0,0 +1,106 @@ +# +# @Project: @cldmv/.github +# @Filename: /examples/individual-repo-workflows/release-flow-v4/v4-bootstrap.yml +# @Date: 2026-05-26 00:00:00 -07:00 (1780124400) +# @Author: Nate Corcoran +# @Email: +# @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved. +# + +# Individual repo: .github/workflows/v4-bootstrap.yml +# +# Per-repo v4 bootstrap โ€” thin wrapper around the shared +# `org-bootstrap-repo@v4` action. Run once per repo from the Actions tab +# (or, for org-wide rollout, prefer `local-org-onboarding.yml` in +# CLDMV/.github which fans out across many repos in parallel). +# +# What gets applied (overwrite-with-warn โ€” divergences are surfaced in the +# run summary): +# - `next` + `hotfixes` branches created from master HEAD if missing +# - repo settings: allow_auto_merge=true, delete_branch_on_merge=false, +# allow_squash_merge=true, allow_merge_commit=true, +# allow_rebase_merge=false, allow_update_branch=true; plus PR-merge +# dialog defaults (merge_commit_title / squash_merge_commit_title = +# PR_TITLE, merge_commit_message / squash_merge_commit_message = +# PR_BODY) so the resulting commit captures the PR title + body +# verbatim (release-PR body = the categorized changelog โ†’ lands on +# master). Per-branch ruleset allowed_merge_methods picks the method. +# - security toggles: dependabot alerts + security updates, secret +# scanning + push protection, private vulnerability reporting +# - rulesets: replaces the three rulesets (Protect Master/Next/Hotfixes) +# with the org canonical defaults +# +# What is NOT applied (GitHub doesn't expose it via REST / GraphQL / gh CLI +# โ€” confirmed against community/community#188598; the bootstrap surfaces +# this as a 'Manual one-time toggles' line in the run summary): +# - Settings โ†’ General โ†’ Pull Requests โ†’ "Auto-close issues with merged +# linked pull requests" (recommended ON). Toggle in the repo UI once. +# +# Idempotent โ€” re-running is safe. Default `dry_run: true` previews +# everything before applying. +# +# Full design: CLDMV/.github docs/conventions/release-flow-v4.md +# Migration checklist: CLDMV/.github docs/migration/v3-to-v4.md +name: ๐Ÿš€ v4 Bootstrap + +on: + workflow_dispatch: + inputs: + dry_run: + description: "Dry-run: preview every mutation without firing it. Default `true` โ€” set to `false` to actually apply changes." + type: boolean + required: false + default: true + code_security: + description: "Code Security policy. off = disable. public-only = enable only if this repo is public (free). all = enable (paid on private)." + type: choice + required: false + default: "off" + options: + - "off" + - "public-only" + - "all" + secret_protection: + description: "Secret Protection (scanning + push protection) policy. Same shape as code_security." + type: choice + required: false + default: "off" + options: + - "off" + - "public-only" + - "all" + steps: + description: "Subset of phases to run, comma-separated." + required: false + default: "branches,settings,security,rulesets" + +permissions: + contents: read + +jobs: + bootstrap: + name: "๐Ÿš€ Bootstrap v4 (this repo)" + runs-on: ubuntu-latest + steps: + - name: Create App token + id: app-token + # Full-permission App token โ€” bootstrap needs administration:write + # for security toggles + ruleset import, plus contents:write for + # branch creation. + uses: CLDMV/.github/.github/actions/github/steps/create-app-token@v4 + with: + client_id: ${{ secrets.CLDMV_BOT_APP_CLIENT_ID }} + private_key: ${{ secrets.CLDMV_BOT_APP_PRIVATE_KEY }} + env: + BOT_APP_CLIENT_ID: ${{ secrets.CLDMV_BOT_APP_CLIENT_ID }} + BOT_APP_PRIVATE_KEY: ${{ secrets.CLDMV_BOT_APP_PRIVATE_KEY }} + + - name: Bootstrap + uses: CLDMV/.github/.github/actions/github/jobs/org-bootstrap-repo@v4 + with: + # target_repo defaults to GITHUB_REPOSITORY (this repo). + github_token: ${{ steps.app-token.outputs.token }} + dry_run: ${{ github.event.inputs.dry_run }} + steps: ${{ github.event.inputs.steps }} + code_security: ${{ github.event.inputs.code_security }} + secret_protection: ${{ github.event.inputs.secret_protection }} diff --git a/package.json b/package.json index 9305e4d..317a819 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,8 @@ "test:coverage": "vitest run --coverage --config .configs/vitest.config.mjs", "ci:coverage": "vitest run --coverage --reporter=dot --maxWorkers=1 --config .configs/vitest.config.mjs", "types:build": "tsc -p .configs/tsconfig.json", - "types:check": "tsc -p .configs/tsconfig.json --noEmit" + "types:check": "tsc -p .configs/tsconfig.json --noEmit", + "build:ci": "npm run lint && npm run format:check && npm run types:build" }, "keywords": [ "vitest",