split-by-codeowners 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Anatolii (Nate) Kurochkin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,181 @@
1
+ # split-by-codeowners
2
+
3
+ Split a set of local changes into **CODEOWNERS buckets** and (optionally) create **one PR per bucket**.
4
+
5
+ Works in two modes sharing the same core logic:
6
+
7
+ - **GitHub Action**: run in CI after your codemod step; generates patches + can create/update PRs (no third-party PR actions).
8
+ - **CLI (`npx`)**: run locally to bucketize current working tree changes; can create/update PRs using `gh` for best dev UX.
9
+
10
+ ## Why
11
+
12
+ If a codemod touches files owned by different teams, you often want:
13
+
14
+ - smaller PRs
15
+ - clearer ownership/review routing
16
+ - independent merge/rollback per owner group
17
+
18
+ This tool groups changed files by their **effective CODEOWNERS owner set** (last-match-wins semantics).
19
+
20
+ ## Quickstart (GitHub Action)
21
+
22
+ ```yaml
23
+ permissions:
24
+ contents: write
25
+ pull-requests: write
26
+
27
+ steps:
28
+ - uses: actions/checkout@v4
29
+ - name: Run codemods
30
+ run: ./run-codemods.sh
31
+
32
+ - name: Split into CODEOWNERS PRs
33
+ uses: anatoliisf/split-by-codeowners@v1
34
+ with:
35
+ create_prs: "true"
36
+ github_token: ${{ github.token }}
37
+ ```
38
+
39
+ ## Quickstart (CLI)
40
+
41
+ Bucketize + write patches:
42
+
43
+ ```bash
44
+ npx split-by-codeowners
45
+ ```
46
+
47
+ Create/update PRs locally (recommended: `gh` auth):
48
+
49
+ ```bash
50
+ gh auth login -h github.com
51
+ npx split-by-codeowners --create-prs --base-branch main
52
+ ```
53
+
54
+ ## GitHub Action
55
+
56
+ ### Inputs
57
+
58
+ | Name | Required | Default | Description |
59
+ | --- | --- | --- | --- |
60
+ | `codeowners_path` | no | `CODEOWNERS` | Path to CODEOWNERS file |
61
+ | `base_ref` | no | `""` | Base ref for changed-files discovery (currently workspace-focused; see notes) |
62
+ | `include_unowned` | no | `"true"` | Include files with no owners in a special bucket |
63
+ | `unowned_bucket_key` | no | `__UNOWNED__` | Bucket key for unowned files |
64
+ | `max_buckets` | no | `"30"` | Fail if buckets exceed this number |
65
+ | `exclude_patterns` | no | `""` | Newline-separated glob patterns to exclude (minimatch) |
66
+ | `patch_dir` | no | `bucket-patches` | Directory to write per-bucket patch files |
67
+ | `bucket_prefix` | no | `bucket` | Patch file prefix |
68
+ | `dry_run` | no | `"false"` | Compute buckets but don’t write patches |
69
+ | `cleanup_patches` | no | `"false"` | Delete `patch_dir` after a successful run |
70
+ | `create_prs` | no | `"false"` | Create/update one PR per bucket |
71
+ | `github_token` | no | `""` | Token used for pushing branches + GitHub API (defaults to env `GITHUB_TOKEN`) |
72
+ | `base_branch` | no | `""` | Base branch for PRs (defaults to repo default branch) |
73
+ | `branch_prefix` | no | `codemods/` | Prefix for created branches |
74
+ | `commit_message` | no | `chore: automated changes` | Commit message for bucket PRs |
75
+ | `pr_title` | no | `chore: automated changes ({owners})` | PR title template (`{owners}`, `{bucket_key}`) |
76
+ | `pr_body` | no | *(see `action.yml`)* | PR body template (`{owners}`, `{bucket_key}`, `{files}`) |
77
+ | `draft` | no | `"false"` | Create PRs as drafts |
78
+
79
+ ### Outputs
80
+
81
+ | Name | Description |
82
+ | --- | --- |
83
+ | `matrix_json` | JSON for `strategy.matrix` (`{ include: [...] }`) |
84
+ | `buckets_json` | Full buckets JSON (owners + files + matched rule) |
85
+ | `prs_json` | If `create_prs=true`, list of created/updated PRs |
86
+
87
+ ### Example: bucketize only (matrix + patches)
88
+
89
+ ```yaml
90
+ - name: Bucketize (patches + matrix)
91
+ id: split
92
+ uses: anatoliisf/split-by-codeowners@v1
93
+ with:
94
+ create_prs: "false"
95
+
96
+ - name: Use matrix
97
+ run: echo '${{ steps.split.outputs.matrix_json }}'
98
+ ```
99
+
100
+ ### Required permissions (when `create_prs=true`)
101
+
102
+ At minimum:
103
+
104
+ ```yaml
105
+ permissions:
106
+ contents: write
107
+ pull-requests: write
108
+ ```
109
+
110
+ ## CLI
111
+
112
+ The CLI operates on your **current working tree** (modified + untracked files) and groups them by CODEOWNERS.
113
+
114
+ ### Usage
115
+
116
+ ```bash
117
+ npx split-by-codeowners --help
118
+ ```
119
+
120
+ ### Common examples
121
+
122
+ Exclude some paths:
123
+
124
+ ```bash
125
+ npx split-by-codeowners --exclude - < excludes.txt
126
+ ```
127
+
128
+ Create/update PRs:
129
+
130
+ ```bash
131
+ npx split-by-codeowners --create-prs --base-branch main
132
+ ```
133
+
134
+ Force token auth locally (instead of `gh`):
135
+
136
+ ```bash
137
+ export GH_TOKEN=...
138
+ npx split-by-codeowners --create-prs --token "$GH_TOKEN" --base-branch main
139
+ ```
140
+
141
+ ### Notes on local PR creation
142
+
143
+ - Locally (no `GITHUB_ACTIONS`), the tool **prefers `gh`** for PR creation.
144
+ - In GitHub Actions (`GITHUB_ACTIONS=true`), the tool **requires a token** and uses API auth.
145
+
146
+ ## How it buckets files
147
+
148
+ - Uses CODEOWNERS **last matching rule wins** for each file.
149
+ - Bucket key is the sorted owners list (normalized) or `unowned_bucket_key`.
150
+
151
+ ## Troubleshooting
152
+
153
+ ### “Permission denied to github-actions[bot]”
154
+
155
+ Ensure workflow permissions include:
156
+
157
+ - `contents: write`
158
+ - `pull-requests: write`
159
+
160
+ ### “Too many buckets”
161
+
162
+ Raise `max_buckets` or add `exclude_patterns` to avoid noisy files (lockfiles, snapshots, etc.).
163
+
164
+ ## Development
165
+
166
+ Build bundles (committed `dist/` is required for Marketplace Actions):
167
+
168
+ ```bash
169
+ npm ci
170
+ npm run build
171
+ ```
172
+
173
+ ## Maintainers
174
+
175
+ ### Publish CLI to npm (manual)
176
+
177
+ This repo includes a manual workflow: `.github/workflows/publish-npm.yml`.
178
+
179
+ - Create an npm token with publish rights and add it as repo secret **`NPM_TOKEN`**.
180
+ - Bump `package.json` version and ensure `dist/` and `dist-cli/` are up to date.
181
+ - Run the workflow from the GitHub Actions UI (`workflow_dispatch`).
package/action.yml ADDED
@@ -0,0 +1,109 @@
1
+ name: "Split changes by CODEOWNERS (bucketize + patches)"
2
+ description: "Bucketizes local git changes by effective CODEOWNERS and can optionally create one PR per bucket (no third-party PR actions)."
3
+ author: "anatoliisf"
4
+ branding:
5
+ icon: "shuffle"
6
+ color: "blue"
7
+
8
+ inputs:
9
+ codeowners_path:
10
+ description: "Path to CODEOWNERS. Defaults to CODEOWNERS at repo root."
11
+ required: false
12
+ default: "CODEOWNERS"
13
+
14
+ base_ref:
15
+ description: "Optional base ref for changed files discovery. If empty, uses HEAD vs working tree."
16
+ required: false
17
+ default: ""
18
+
19
+ include_unowned:
20
+ description: "Include files with no owners in a special bucket"
21
+ required: false
22
+ default: "true"
23
+
24
+ unowned_bucket_key:
25
+ description: "Bucket key for unowned files"
26
+ required: false
27
+ default: "__UNOWNED__"
28
+
29
+ max_buckets:
30
+ description: "Fail if buckets exceed this number"
31
+ required: false
32
+ default: "30"
33
+
34
+ exclude_patterns:
35
+ description: "Newline-separated glob patterns to exclude"
36
+ required: false
37
+ default: ""
38
+
39
+ patch_dir:
40
+ description: "Directory to write patches"
41
+ required: false
42
+ default: "bucket-patches"
43
+
44
+ bucket_prefix:
45
+ description: "Patch file prefix"
46
+ required: false
47
+ default: "bucket"
48
+
49
+ dry_run:
50
+ description: "Compute buckets but don't write patches"
51
+ required: false
52
+ default: "false"
53
+
54
+ cleanup_patches:
55
+ description: "If true, delete patch_dir after successful run (useful when create_prs=true)."
56
+ required: false
57
+ default: "false"
58
+
59
+ create_prs:
60
+ description: "If true, create/update one PR per bucket by pushing branches and calling GitHub API."
61
+ required: false
62
+ default: "false"
63
+
64
+ github_token:
65
+ description: "GitHub token used for API + pushing branches. Defaults to GITHUB_TOKEN."
66
+ required: false
67
+ default: ""
68
+
69
+ base_branch:
70
+ description: "Base branch to open PRs against. If empty, uses repo default branch."
71
+ required: false
72
+ default: ""
73
+
74
+ branch_prefix:
75
+ description: "Prefix for created branches."
76
+ required: false
77
+ default: "codemods/"
78
+
79
+ commit_message:
80
+ description: "Commit message used in bucket PRs."
81
+ required: false
82
+ default: "chore: automated changes"
83
+
84
+ pr_title:
85
+ description: "PR title template. Supports {owners} and {bucket_key}."
86
+ required: false
87
+ default: "chore: automated changes ({owners})"
88
+
89
+ pr_body:
90
+ description: "PR body template. Supports {owners}, {bucket_key}, {files}."
91
+ required: false
92
+ default: "Automated changes bucketed by CODEOWNERS.\n\nOwners: {owners}\nBucket key: {bucket_key}\n\nFiles:\n{files}\n"
93
+
94
+ draft:
95
+ description: "Create PRs as drafts."
96
+ required: false
97
+ default: "false"
98
+
99
+ outputs:
100
+ matrix_json:
101
+ description: "JSON for strategy.matrix (one bucket per job)"
102
+ buckets_json:
103
+ description: "Full buckets JSON"
104
+ prs_json:
105
+ description: "If create_prs=true, JSON list of created/updated PRs (url + number + bucket_key)."
106
+
107
+ runs:
108
+ using: "node20"
109
+ main: "dist/index.js"