Split local changes into CODEOWNERS buckets and (optionally) create one PR per bucket (CLI + GitHub Action).
npm install split-by-codeownersSplit a set of local changes into CODEOWNERS buckets and (optionally) create one PR per bucket.
Works in two modes sharing the same core logic:
- GitHub Action: run in CI after your codemod step; generates patches + can create/update PRs (no third-party PR actions).
- CLI (npx): run locally to bucketize current working tree changes; can create/update PRs using gh for best dev UX.
If a codemod touches files owned by different teams, you often want:
- smaller PRs
- clearer ownership/review routing
- independent merge/rollback per owner group
This tool groups changed files by their effective CODEOWNERS owner set (last-match-wins semantics).
``yaml
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- name: Run codemods
run: ./run-codemods.sh
- name: Split into CODEOWNERS PRs
uses: anatoliisf/split-by-codeowners@v1
with:
github_token: ${{ github.token }}
`
Bucketize + write patches:
`bash`
npx split-by-codeowners
Create/update PRs locally (recommended: gh auth):
`bash`
gh auth login -h github.com
npx split-by-codeowners --create-prs --base-branch main
| Name | Required | Default | Description |
| -------------------- | -------- | ------------------------------------- | ----------------------------------------------------------------------------- |
| repo_path | no | . | Repo root path relative to GITHUB_WORKSPACE |codeowners_path
| | no | CODEOWNERS | Path to CODEOWNERS file |base_ref
| | no | "" | Base ref for changed-files discovery (currently workspace-focused; see notes) |include_unowned
| | no | "true" | Include files with no owners in a special bucket |unowned_bucket_key
| | no | __UNOWNED__ | Bucket key for unowned files |max_buckets
| | no | "30" | Fail if buckets exceed this number |exclude_patterns
| | no | "" | Newline-separated glob patterns to exclude (minimatch) |patch_dir
| | no | bucket-patches | Directory to write per-bucket patch files |bucket_prefix
| | no | bucket | Patch file prefix |dry_run
| | no | "false" | Compute buckets but don’t write patches |cleanup_patches
| | no | "false" | Delete patch_dir after a successful run |create_prs
| | no | "true" | Create/update one PR per bucket |github_token
| | no | "" | Token used for pushing branches + GitHub API (defaults to env GITHUB_TOKEN) |base_branch
| | no | "" | Base branch for PRs (defaults to repo default branch) |branch_prefix
| | no | codemods/ | Prefix for created branches |commit_message
| | no | chore: automated changes | Commit message for bucket PRs |pr_title
| | no | chore: automated changes ({owners}) | PR title template ({owners}, {bucket_key}) |pr_body
| | no | (see action.yml) | PR body template ({owners}, {bucket_key}, {files}) |draft
| | no | "false" | Create PRs as drafts |
| Name | Description |
| -------------- | ------------------------------------------------- |
| matrix_json | JSON for strategy.matrix ({ include: [...] }) |buckets_json
| | Full buckets JSON (owners + files + matched rule) |prs_json
| | If create_prs=true, list of created/updated PRs |
`yaml
- name: Bucketize (patches + matrix)
id: split
uses: anatoliisf/split-by-codeowners@v1
with:
draft: "true"
- name: Use matrix
run: echo '${{ steps.split.outputs.matrix_json }}'
`
At minimum:
`yaml`
permissions:
contents: write
pull-requests: write
The CLI operates on your current working tree (modified + untracked files) and groups them by CODEOWNERS.
`bash`
npx split-by-codeowners --help
#### Common
- --repo-path
- --codeowners )--exclude
- : File containing newline-separated glob patterns to exclude, or - to read from stdin--include-unowned
- : Include files with no owners in an __UNOWNED__ bucket (default: true)--unowned-bucket-key
- : Bucket key for unowned files (default: __UNOWNED__)--max-buckets
- : Fail if number of buckets exceeds n (default: 30)--patch-dir
- : Directory to write patch files (default: bucket-patches)--bucket-prefix
- : Patch file prefix (default: bucket)--dry-run
- : Compute buckets but don’t write patches--cleanup-patches
- : Delete patch_dir after a successful run (default in this repo: enabled)
#### PR creation
- --create-prs: Create/update one PR per bucket (local: uses gh auth)--base-branch
- : Base branch for PRs (default: repo default branch)--branch-prefix
- : Branch prefix for bucket branches (default: codemods/)--commit-message
- : Commit message for bucket commits--pr-title
- : Title template (supports {owners}, {bucket_key})--pr-body
- : Body template (supports {owners}, {bucket_key}, {files})--pr-body-mode
- : custom|template|template_with_bucket|none--pr-template-path
- : PR template file path (used when pr_body_mode=template*)--draft
- : Create PRs as drafts (default: false)
Exclude some paths:
`bash`
npx split-by-codeowners --exclude - < excludes.txt
Create/update PRs:
`bash`
npx split-by-codeowners --create-prs --base-branch main
Use the repo PR template for PR creation (recommended):
`bash`
npx split-by-codeowners --create-prs --pr-body-mode template_with_bucket
- Locally (no GITHUB_ACTIONS), the tool prefers gh for PR creation.GITHUB_ACTIONS=true
- In GitHub Actions (), the tool requires a token and uses API auth.
- Uses CODEOWNERS last matching rule wins for each file.
- Bucket key is the sorted owners list (normalized) or unowned_bucket_key.
Ensure workflow permissions include:
- contents: writepull-requests: write
-
Raise max_buckets or add exclude_patterns to avoid noisy files (lockfiles, snapshots, etc.).
Build bundles (committed dist/ is required for Marketplace Actions):
`bash`
npm ci
npm run build
This repo includes a manual workflow: .github/workflows/publish-npm.yml.
- Bump package.json version and ensure dist/ and dist-cli/ are up to date.workflow_dispatch`).
- Run the workflow from the GitHub Actions UI (