Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
1ddbe0c
fix: stop overriding master-commit-audit's allowed_patterns with a st…
cldmv-bot[bot] Jul 20, 2026
a38fa1b
chore: bump version to 1.1.3
cldmv-bot[bot] Jul 20, 2026
67e529f
deps: bump the minor group with 2 updates
dependabot[bot] Jul 22, 2026
dccf5ba
deps: bump @eslint/json from 0.10.0 to 2.0.1
dependabot[bot] Jul 22, 2026
118df83
deps: bump globals from 15.15.0 to 17.7.0
dependabot[bot] Jul 22, 2026
4477443
chore: centralize release-flow workflows into thin @v4 callers
Shinrai Jul 28, 2026
ae3c7b8
deps: bump the minor group with 2 updates (#24)
Shinrai Jul 28, 2026
c258581
deps: bump @eslint/json from 0.10.0 to 2.0.1 (#25)
Shinrai Jul 28, 2026
887e9bc
deps: bump globals from 15.15.0 to 17.7.0 (#28)
Shinrai Jul 28, 2026
77c28b8
deps: bump eslint from 9.39.4 to 10.8.0
dependabot[bot] Jul 28, 2026
948f46d
deps: bump @eslint/markdown from 6.6.0 to 8.0.3
dependabot[bot] Jul 28, 2026
7e0e88d
deps: bump eslint from 9.39.4 to 10.8.0 (#27)
Shinrai Jul 28, 2026
0a13592
chore: centralize release-flow workflows into thin @v4 callers (#29)
Shinrai Jul 28, 2026
d517a90
deps: bump @eslint/markdown from 6.6.0 to 8.0.3 (#26)
Shinrai Jul 28, 2026
266ebdf
ci: add no-op build script so the coverage-badge leg passes
Shinrai Jul 30, 2026
b6f93fb
deps: drop Node 20, bump chalk to 6 and commander to 15
Shinrai Jul 31, 2026
fb27adf
deps: bump globals in the minor group across 1 directory
dependabot[bot] Jul 31, 2026
a67338f
deps: bump @cldmv/slothlet in the patch group across 1 directory
dependabot[bot] Jul 31, 2026
30dee04
deps: bump globals from 17.7.0 to 17.8.0 in the minor group across 1 …
cldmv-bot[bot] Jul 31, 2026
0bacf1d
deps: bump @cldmv/slothlet from 3.12.1 to 3.12.2 in the patch group a…
cldmv-bot[bot] Jul 31, 2026
386d1ae
deps: drop Node 20, bump chalk to 6 and commander to 15 (#37)
Shinrai Jul 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,10 @@ on:
required: false
default: "lts/*"
min_node_version:
description: "Minimum Node.js version for matrix testing (default: 20, oldest non-EOL)"
description: "Minimum Node.js version for matrix testing (default: 22, oldest non-EOL LTS)"
type: string
required: false
default: "20"
default: "22"
max_node_major:
description: "Override max Node.js major version (default: 22)"
type: string
Expand Down Expand Up @@ -184,12 +184,12 @@ jobs:
.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' }}
min_node_version: ${{ github.event.inputs.min_node_version || '22' }}
max_node_major: ${{ github.event.inputs.max_node_major || '22' }}
# 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
# LTS-only matrix (even majors: 22, 24, …) on every event. Odd majors
# (23, 25, …) 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
# (`>=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' }}
Expand Down
243 changes: 19 additions & 224 deletions .github/workflows/feature-pr.yml
Original file line number Diff line number Diff line change
@@ -1,42 +1,35 @@
#
# @Project: @cldmv/git-embedded
# @Filename: /.github/workflows/feature-pr.yml
# @Date: 2026-07-18 15:49:12 -07:00 (1784414952)
# @Project: @cldmv/.github
# @Filename: /examples/individual-repo-workflows/release-flow-v4/feature-pr.yml
# @Author: Nate Corcoran <CLDMV>
# @Email: <Shinrai@users.noreply.github.com>
# @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.
# 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).
# Reserved branches NOT auto-PR'd: dependabot/* and copilot/* (they manage their
# own PRs); badges, gh-pages (bot-only); master/main (the target).
#
# On first push: creates the PR with a categorized changelog body (same
# format the v4 release-PR machinery generates). On subsequent pushes:
# refreshes the existing PR's body with the latest categorized commits.
# Uses the shared get-commit-range + generate-comprehensive-changelog
# actions for the format, so consumer PRs look identical to release PRs
# in structure (Breaking Changes / Features / Bug Fixes / Other Changes /
# Contributors).
#
# Skipped automatically: bot pushes (your bot App's login / github-actions[bot])
# and any push whose head commit starts with 'chore: bump version'.
# 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 below.
# repo uses. Must align with the `case` statement in the reusable.
- 'feat/**'
- 'feature/**'
- 'fix/**'
Expand All @@ -50,215 +43,17 @@ on:
- 'style/**'
- 'hotfix/**'

permissions:
contents: read
pull-requests: write

# Serialize per-branch so a flurry of pushes doesn't race the
# "does a PR already exist?" check.
concurrency:
group: feature-pr-${{ github.repository }}-${{ github.ref }}
cancel-in-progress: false

jobs:
open-pr:
# Loop guard: replace 'cldmv-bot[bot]' with your bot App's login.
if: |
github.actor != 'cldmv-bot[bot]' &&
github.actor != 'github-actions[bot]' &&
!startsWith(github.event.head_commit.message, 'chore: bump version')
runs-on: ubuntu-latest
steps:
- name: Determine target branch
id: target
shell: bash
run: |
branch="${GITHUB_REF#refs/heads/}"
echo "branch=$branch" >> "$GITHUB_OUTPUT"
# CUSTOMIZE: adjust the case arms to match your branch
# conventions. Anything not matched is silently skipped
# (so master/main, badges, gh-pages, dependabot/*, etc.
# are safe regardless of what fires the workflow).
# The flow_label sorts first in the PR's label list
# (the leading `!` precedes every letter alphabetically)
# so a glance at any PR's badges reveals which lane it's in.
# Lane (target) AND declared type both come from the branch
# prefix — the v4 convention requires a typed prefix, so a
# `docs/*` branch is a docs change, `fix/*` a fix, etc. The flow
# label is `! <type> → <lane>` so it reflects what the PR actually
# is, not a blanket "feature". (Previously every next-lane branch
# got `! feature → next`, mislabelling docs/fix/chore PRs.)
case "$branch" in
hotfix/*) target="hotfixes"; type="hotfix" ;;
feat/*|feature/*) target="next"; type="feature" ;;
fix/*) target="next"; type="fix" ;;
docs/*) target="next"; type="docs" ;;
chore/*) target="next"; type="chore" ;;
refactor/*) target="next"; type="refactor" ;;
ci/*) target="next"; type="ci" ;;
perf/*) target="next"; type="perf" ;;
test/*) target="next"; type="test" ;;
style/*) target="next"; type="style" ;;
release/*) target="next"; type="release" ;;
*)
target=""; type=""; flow_label=""
echo "::notice::Branch '$branch' does not match any auto-PR pattern; skipping."
;;
esac
# Leading `!` sorts the flow label first in the PR's badge list,
# so a glance reveals both the change type and its lane.
if [ -n "$target" ]; then
flow_label="! ${type} → ${target}"
fi
echo "target=$target" >> "$GITHUB_OUTPUT"
echo "flow_label=$flow_label" >> "$GITHUB_OUTPUT"

- name: Create App token
id: app-token
if: steps.target.outputs.target != ''
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: Check for existing PR
id: existing
if: steps.target.outputs.target != ''
shell: bash
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
pr=$(gh pr list --repo "$GITHUB_REPOSITORY" \
--head "${{ steps.target.outputs.branch }}" \
--base "${{ steps.target.outputs.target }}" \
--state open \
--json number --jq '.[0].number // ""')
echo "number=$pr" >> "$GITHUB_OUTPUT"
if [ -n "$pr" ]; then
echo "::notice::Existing PR #$pr will be refreshed."
fi

- name: Checkout (full history for git log)
if: steps.target.outputs.target != ''
uses: CLDMV/.github/.github/actions/common/steps/checkout-code@v4
with:
fetch-depth: 0

- name: Fetch target branch ref
if: steps.target.outputs.target != ''
shell: bash
run: |
git fetch --quiet origin "${{ steps.target.outputs.target }}"

- name: Get categorized commits (base..head)
id: commits
if: steps.target.outputs.target != ''
uses: CLDMV/.github/.github/actions/git/steps/get-commit-range@v4
with:
base-ref: origin/${{ steps.target.outputs.target }}
head-ref: HEAD

- name: Detect feature commits in range
id: feat
if: steps.target.outputs.target != ''
shell: bash
env:
COMMITS: ${{ steps.commits.outputs.commits }}
run: |
# `type: feature` is applied when the range contains a feature,
# mirroring the changelog's own "Features" section: get-commit-
# range tags `feat:` (and content-categorized add/new) commits
# as category "feature". Reuses the already-computed commits.
has_feature=false
if printf '%s' "$COMMITS" | jq -e 'any(.[]; .category == "feature")' >/dev/null 2>&1; then
has_feature=true
fi
echo "has_feature=$has_feature" >> "$GITHUB_OUTPUT"
echo "📊 feature detected in range: $has_feature"

- name: Generate categorized changelog body
id: changelog
if: steps.target.outputs.target != ''
uses: CLDMV/.github/.github/actions/git/steps/generate-comprehensive-changelog@v4
with:
commits: ${{ steps.commits.outputs.commits }}
commit-range: ${{ steps.commits.outputs.commit-range }}
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}

- name: Save body to file
if: steps.target.outputs.target != ''
shell: bash
env:
BODY: ${{ steps.changelog.outputs.changelog-content }}
run: |
printf '%s' "$BODY" > /tmp/pr-body.md

- name: Create PR (first push)
if: steps.target.outputs.target != '' && steps.existing.outputs.number == ''
shell: bash
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
HEAD_COMMIT_MSG: ${{ github.event.head_commit.message }}
HEAD_BRANCH: ${{ steps.target.outputs.branch }}
BASE_BRANCH: ${{ steps.target.outputs.target }}
run: |
# Title = head commit's first line — preserves the
# conventional-commit prefix the release-PR title-normalizer
# and commit-type aggregator expect.
title=$(printf '%s\n' "$HEAD_COMMIT_MSG" | head -1)
pr_url=$(gh pr create \
--repo "$GITHUB_REPOSITORY" \
--base "$BASE_BRANCH" \
--head "$HEAD_BRANCH" \
--title "$title" \
--body-file /tmp/pr-body.md)
echo "::notice::Opened $pr_url"
# Apply the flow label (sorts first in the PR's badge list).
# `|| true` so a missing label in the repo (catalog not yet
# synced) doesn't fail the workflow.
if [ -n "${{ steps.target.outputs.flow_label }}" ]; then
gh pr edit "$pr_url" --add-label "${{ steps.target.outputs.flow_label }}" || true
fi
# Apply `type: feature` when the range implements a feature.
if [ "${{ steps.feat.outputs.has_feature }}" = "true" ]; then
gh pr edit "$pr_url" --add-label "type: feature" || true
fi
{
echo "### 🔀 Auto-opened PR"
echo ""
echo "$pr_url"
} >> "$GITHUB_STEP_SUMMARY"

- name: Refresh existing PR body
if: steps.target.outputs.target != '' && steps.existing.outputs.number != ''
shell: bash
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
PR_NUMBER: ${{ steps.existing.outputs.number }}
run: |
gh pr edit "$PR_NUMBER" \
--repo "$GITHUB_REPOSITORY" \
--body-file /tmp/pr-body.md
# Re-apply the flow label so a manual removal doesn't
# strand the PR without its lane indicator.
if [ -n "${{ steps.target.outputs.flow_label }}" ]; then
gh pr edit "$PR_NUMBER" \
--repo "$GITHUB_REPOSITORY" \
--add-label "${{ steps.target.outputs.flow_label }}" || true
fi
# Apply `type: feature` when the range implements a feature.
if [ "${{ steps.feat.outputs.has_feature }}" = "true" ]; then
gh pr edit "$PR_NUMBER" \
--repo "$GITHUB_REPOSITORY" \
--add-label "type: feature" || true
fi
echo "::notice::Refreshed PR #${PR_NUMBER} body"
{
echo "### 🔀 Refreshed PR body"
echo ""
echo "PR #${PR_NUMBER}"
} >> "$GITHUB_STEP_SUMMARY"
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 }}
64 changes: 24 additions & 40 deletions .github/workflows/hotfix-redirector.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#
# @Project: @cldmv/git-embedded
# @Filename: /.github/workflows/hotfix-redirector.yml
# @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 <CLDMV>
# @Email: <Shinrai@users.noreply.github.com>
Expand All @@ -13,55 +13,39 @@
#
# 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 the PR body references a GHSA security
# advisory (Dependabot's security-update PRs flow into the hotfix lane;
# routine version bumps stay on `next`).
# 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".
#
# The redirect-hotfix-pr action owns all detection logic — it skips non-matching
# bot PRs, non-matching heads, and PRs already on `hotfixes`, and posts a
# one-time explanatory comment with the appropriate reason.
# 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. SAFE here because it is API-only — the
# redirect-hotfix-pr action never checks out or executes PR content.
# DO NOT add a checkout step.
# 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.
# `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]

permissions:
contents: read
pull-requests: write

concurrency:
group: hotfix-redirector-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
redirect:
name: "🔀 Redirect to hotfixes"
runs-on: ubuntu-latest
steps:
- name: Create App token
id: app-token
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: Redirect hotfix/security PR to hotfixes
uses: CLDMV/.github/.github/actions/github/steps/redirect-hotfix-pr@v4
with:
pr-number: ${{ github.event.pull_request.number }}
github-token: ${{ steps.app-token.outputs.token }}
head-ref: ${{ github.event.pull_request.head.ref }}
base-ref: ${{ github.event.pull_request.base.ref }}
user-type: ${{ github.event.pull_request.user.type }}
target-base: hotfixes
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 }}
Loading
Loading