codeowners-guard 0.1.1

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.md ADDED
@@ -0,0 +1,71 @@
1
+ # PolyForm Perimeter License 1.0.1
2
+
3
+ <https://polyformproject.org/licenses/perimeter/1.0.1>
4
+
5
+ ## Acceptance
6
+
7
+ In order to get any license under these terms, you must agree to them as both strict obligations and conditions to all your licenses.
8
+
9
+ ## Copyright License
10
+
11
+ The licensor grants you a copyright license for the software to do everything you might do with the software that would otherwise infringe the licensor's copyright in it for any permitted purpose. However, you may only distribute the software according to [Distribution License](#distribution-license) and make changes or new works based on the software according to [Changes and New Works License](#changes-and-new-works-license).
12
+
13
+ ## Distribution License
14
+
15
+ The licensor grants you an additional copyright license to distribute copies of the software. Your license to distribute covers distributing the software with changes and new works permitted by [Changes and New Works License](#changes-and-new-works-license).
16
+
17
+ ## Notices
18
+
19
+ You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms or the URL for them above, as well as copies of any plain-text lines beginning with `Required Notice:` that the licensor provided with the software. For example:
20
+
21
+ > Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
22
+
23
+ ## Changes and New Works License
24
+
25
+ The licensor grants you an additional copyright license to make changes and new works based on the software for any permitted purpose.
26
+
27
+ ## Patent License
28
+
29
+ The licensor grants you a patent license for the software that covers patent claims the licensor can license, or becomes able to license, that you would infringe by using the software.
30
+
31
+ ## Noncompete
32
+
33
+ Any purpose is a permitted purpose, except for providing to others any product that competes with the software.
34
+
35
+ ## Competition
36
+
37
+ If you use this software to market a product as a substitute for the functionality or value of the software, it competes with the software. A product may compete regardless how it is designed or deployed. For example, a product may compete even if it provides its functionality via any kind of interface (including services, libraries or plug-ins), even if it is ported to a different platform or programming language, and even if it is provided free of charge.
38
+
39
+ ## Fair Use
40
+
41
+ You may have "fair use" rights for the software under the law. These terms do not limit them.
42
+
43
+ ## No Other Rights
44
+
45
+ These terms do not allow you to sublicense or transfer any of your licenses to anyone else, or prevent the licensor from granting licenses to anyone else. These terms do not imply any other licenses.
46
+
47
+ ## Patent Defense
48
+
49
+ If you make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
50
+
51
+ ## Violations
52
+
53
+ The first time you are notified in writing that you have violated any of these terms, or done anything with the software not covered by your licenses, your licenses can nonetheless continue if you come into full compliance with these terms, and take practical steps to correct past violations, within 32 days of receiving notice. Otherwise, all your licenses end immediately.
54
+
55
+ ## No Liability
56
+
57
+ ***As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.***
58
+
59
+ ## Definitions
60
+
61
+ The **licensor** is the individual or entity offering these terms, and the **software** is the software the licensor makes available under these terms.
62
+
63
+ A **product** can be a good or service, or a combination of them.
64
+
65
+ **You** refers to the individual or entity agreeing to these terms.
66
+
67
+ **Your company** is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. **Control** means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
68
+
69
+ **Your licenses** are all the licenses granted to you for the software under these terms.
70
+
71
+ **Use** means anything you do with the software requiring one of your licenses.
package/README.md ADDED
@@ -0,0 +1,178 @@
1
+ <p align="center">
2
+ <img src="assets/codeowners-guard.png" alt="CODEOWNERS Guard" width="760" />
3
+ </p>
4
+
5
+ <h1 align="center">CODEOWNERS Guard</h1>
6
+
7
+ <p align="center"><strong>Fast, GitHub-native validation for the CODEOWNERS file that GitHub will actually use.</strong></p>
8
+
9
+ <p align="center">
10
+ <a href="https://github.com/rarepops/codeowners-guard/actions/workflows/ci.yml"><img src="https://github.com/rarepops/codeowners-guard/actions/workflows/ci.yml/badge.svg" alt="CI" /></a>
11
+ <a href="https://github.com/rarepops/codeowners-guard/actions/workflows/codeql.yml"><img src="https://github.com/rarepops/codeowners-guard/actions/workflows/codeql.yml/badge.svg" alt="CodeQL" /></a>
12
+ <a href="https://github.com/rarepops/codeowners-guard/actions/workflows/release.yml"><img src="https://github.com/rarepops/codeowners-guard/actions/workflows/release.yml/badge.svg?event=push" alt="Release build" /></a>
13
+ <a href="https://github.com/rarepops/codeowners-guard/releases/latest"><img src="https://img.shields.io/github/v/release/rarepops/codeowners-guard?label=release&color=brightgreen" alt="Latest release" /></a>
14
+ <a href="https://github.com/rarepops/codeowners-guard/releases"><img src="https://img.shields.io/github/downloads/rarepops/codeowners-guard/total?label=downloads" alt="Downloads" /></a>
15
+ <a href="LICENSE.md"><img src="https://img.shields.io/badge/license-PolyForm%20Perimeter%201.0.1-blue" alt="License" /></a>
16
+ <a href="https://nodejs.org/"><img src="https://img.shields.io/badge/Node.js-24%2B-339933?logo=node.js&logoColor=white" alt="Node.js 24 or newer" /></a>
17
+ <img src="https://img.shields.io/badge/action%20runtime-Node.js%2024-2088FF?logo=githubactions&logoColor=white" alt="GitHub Action runtime: Node.js 24" />
18
+ <img src="https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS-lightgrey" alt="Platforms: Windows, Linux, and macOS" />
19
+ </p>
20
+
21
+ <p align="center">
22
+ <a href="https://github.com/rarepops/codeowners-guard/commits/main"><img src="https://img.shields.io/github/last-commit/rarepops/codeowners-guard/main?label=last%20commit" alt="Last commit on main" /></a>
23
+ <a href="https://github.com/rarepops/codeowners-guard/issues"><img src="https://img.shields.io/github/issues/rarepops/codeowners-guard" alt="Open issues" /></a>
24
+ </p>
25
+
26
+ CODEOWNERS Guard combines GitHub's own diagnostics with local repository checks. It runs as a native Node.js action, so it works on Linux, macOS, and Windows without pulling a container image.
27
+
28
+ ## Why Guard
29
+
30
+ - **GitHub is the syntax authority.** Diagnostics come from the same CODEOWNERS API that evaluates the selected branch, tag, or commit.
31
+ - **Local checks cover the gaps.** Duplicate patterns, rules that match no tracked file, and files without an effective owner are reported separately.
32
+ - **Action-first feedback.** Findings become file annotations and a job summary, with counts exposed as workflow outputs.
33
+ - **No container startup.** The Action runs directly on Node.js 24 on Linux, macOS, and Windows runners.
34
+ - **Useful outside Actions.** The same core ships as a cross-platform CLI with deterministic text and JSON output.
35
+
36
+ ## Checks
37
+
38
+ | Check | What it reports | Severity |
39
+ | --- | --- | --- |
40
+ | `syntax` | Errors returned by GitHub's CODEOWNERS API for the selected ref | Error |
41
+ | `duplicates` | A pattern that appears more than once | Warning |
42
+ | `dangling` | A pattern that matches no tracked file | Warning |
43
+ | `unowned` | A tracked file with no effective owner, including files cleared by an ownerless rule | Warning |
44
+
45
+ Rules use GitHub's last-match-wins behavior. CODEOWNERS Guard searches the standard locations in GitHub's order: `.github/CODEOWNERS`, `CODEOWNERS`, then `docs/CODEOWNERS`.
46
+
47
+ When the `syntax` check is disabled, local checks assume the remaining CODEOWNERS lines are valid. Keep `syntax` enabled in the Action, or validate the committed ref with GitHub before relying on local-only coverage results.
48
+
49
+ See the [check reference](docs/checks.md) for exact matching, exclusion, and result-limit behavior.
50
+
51
+ ## GitHub Action
52
+
53
+ ```yaml
54
+ name: CODEOWNERS
55
+
56
+ on:
57
+ pull_request:
58
+ push:
59
+ branches: [main]
60
+
61
+ permissions:
62
+ contents: read
63
+
64
+ jobs:
65
+ validate:
66
+ runs-on: ubuntu-latest
67
+ steps:
68
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
69
+ - uses: rarepops/codeowners-guard@v0.1.1
70
+ with:
71
+ checks: syntax,duplicates,dangling,unowned
72
+ exclude: |
73
+ dist/
74
+ coverage/
75
+ ```
76
+
77
+ For the strongest supply-chain pinning, replace `v0.1.1` with its full commit SHA. A complete least-privilege workflow is available in [examples/codeowners.yml](examples/codeowners.yml).
78
+
79
+ The action adds file annotations and a job summary. Its default token is `${{ github.token }}`, and the workflow only needs `contents: read`.
80
+
81
+ The Action takes its API endpoint from GitHub's runner environment. It does not accept an endpoint input that could redirect the automatically supplied token. GitHub Enterprise Server runners provide their own trusted `GITHUB_API_URL`.
82
+
83
+ ### Inputs
84
+
85
+ | Input | Default | Description |
86
+ | --- | --- | --- |
87
+ | `github-token` | `${{ github.token }}` | Token used for GitHub diagnostics |
88
+ | `path` | `.` | Repository path relative to `GITHUB_WORKSPACE` |
89
+ | `codeowners` | auto-detect | Explicit CODEOWNERS path |
90
+ | `checks` | all checks | Comma-separated checks |
91
+ | `exclude` | none | Newline-separated gitignore patterns omitted from local checks |
92
+ | `repository` | `${{ github.repository }}` | Repository in `owner/name` form |
93
+ | `ref` | `${{ github.sha }}` | Branch, tag, or commit used by the syntax check |
94
+ | `fail-on` | `warning` | Failure threshold: `warning` or `error` |
95
+ | `max-annotations` | `50` | Maximum workflow annotations and summary rows, up to `100` |
96
+
97
+ Annotation limits do not change validation counts or failure behavior.
98
+
99
+ ### Outputs
100
+
101
+ The action returns `valid`, `issue-count`, `error-count`, and `warning-count`.
102
+
103
+ ## CLI
104
+
105
+ Build and run the CLI locally:
106
+
107
+ ```shell
108
+ npm ci
109
+ npm run build
110
+ node dist/cli.js .
111
+ ```
112
+
113
+ Local checks require no network access:
114
+
115
+ ```shell
116
+ node dist/cli.js . \
117
+ --checks duplicates,dangling,unowned \
118
+ --exclude dist/ \
119
+ --format json
120
+ ```
121
+
122
+ GitHub's syntax check validates a committed branch, tag, or SHA:
123
+
124
+ ```shell
125
+ GITHUB_TOKEN=ghp_example node dist/cli.js . \
126
+ --checks syntax,duplicates,dangling,unowned \
127
+ --repository owner/repository \
128
+ --ref main
129
+ ```
130
+
131
+ Tokens are accepted only through `GITHUB_TOKEN` or `GH_TOKEN`; command-line token arguments are deliberately unsupported so credentials do not enter shell history or process listings.
132
+
133
+ Use `--max-issues` to retain up to 10,000 issue details in text or JSON output. The default is 1,000. Use `--fail-on error` to report local warnings without returning a failing exit status. Exit code `1` means validation failed, and exit code `2` means the command could not run.
134
+
135
+ See [troubleshooting](docs/troubleshooting.md) for authentication, ref mismatch, missing file, and exit-code guidance.
136
+
137
+ ## Design
138
+
139
+ GitHub remains the authority for syntax diagnostics. Local checks operate on files returned by `git ls-files`, use a maintained gitignore-compatible matcher, and do not make separate user or team lookup calls. This keeps the Action small and avoids maintaining a second copy of GitHub's owner-resolution behavior.
140
+
141
+ The syntax check targets `ref`, while local checks target the checked-out working tree. In normal Actions usage both refer to the same commit. For uncommitted local changes, run local checks only or push the change to a ref before requesting GitHub diagnostics.
142
+
143
+ ### Security
144
+
145
+ - GitHub workflow dependencies are pinned to immutable commit SHAs, and repository settings require SHA-pinned Actions.
146
+ - API calls require HTTPS, reject redirects, time out after 15 seconds, cap responses at 1 MiB, and retry only bounded transient failures.
147
+ - Action paths and CODEOWNERS files cannot escape the checked-out workspace through traversal or symbolic links.
148
+ - Terminal text, workflow annotations, and HTML summaries escape control and bidirectional characters.
149
+ - Dependency installation disables lifecycle scripts; CI checks advisories, registry signatures, and dependency diffs.
150
+ - Tagged release artifacts include SHA-256 checksums and GitHub build-provenance attestations.
151
+
152
+ ### Performance
153
+
154
+ - Tracked paths stream from `git ls-files -z`, avoiding a fixed child-process output buffer.
155
+ - GitHub diagnostics and tracked-file enumeration run concurrently.
156
+ - Each file is normalized once and evaluated against ownership rules in one pass, while duplicate-only checks skip Git entirely.
157
+ - Finding details are retained within configured bounds while exact counts and failure behavior cover every finding.
158
+ - `npm run bench` measures a 10,000-rule duplicate workload and a 10,000-file by 100-rule ownership workload.
159
+
160
+ ## Development
161
+
162
+ Requires Node.js 24 or newer.
163
+
164
+ ```shell
165
+ npm ci
166
+ npm run check
167
+ npm run bench
168
+ ```
169
+
170
+ `npm run check` includes linting, strict type checking, tests with coverage thresholds, and a production build. `dist/` is committed because GitHub executes JavaScript actions directly from the repository. CI rejects source changes that do not include rebuilt bundles and third-party notices.
171
+
172
+ ## License
173
+
174
+ CODEOWNERS Guard is source-available under the [PolyForm Perimeter License 1.0.1](LICENSE.md). The license permits use, modification, and redistribution, but does not permit using the software to provide a competing product. Review the license terms before adopting or redistributing the project.
175
+
176
+ Licenses for packages embedded in the distributed bundles are reproduced in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
177
+
178
+ Copyright (c) 2026 Rares (rarepops).
@@ -0,0 +1,183 @@
1
+ # Third-Party Notices
2
+
3
+ This file is generated from the packages embedded in the distributed bundles. Do not edit it manually.
4
+
5
+ ## @actions/core 3.0.1
6
+
7
+ Source: https://github.com/actions/toolkit/tree/main/packages/core
8
+
9
+ License: MIT
10
+
11
+ ```text
12
+ The MIT License (MIT)
13
+
14
+ Copyright 2019 GitHub
15
+
16
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+ ```
22
+ ---
23
+
24
+ ## @actions/exec 3.0.0
25
+
26
+ Source: https://github.com/actions/toolkit/tree/main/packages/exec
27
+
28
+ License: MIT
29
+
30
+ ```text
31
+ The MIT License (MIT)
32
+
33
+ Copyright 2019 GitHub
34
+
35
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
36
+
37
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
38
+
39
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
40
+ ```
41
+ ---
42
+
43
+ ## @actions/http-client 4.0.1
44
+
45
+ Source: https://github.com/actions/toolkit/tree/main/packages/http-client
46
+
47
+ License: MIT
48
+
49
+ ```text
50
+ Actions Http Client for Node.js
51
+
52
+ Copyright (c) GitHub, Inc.
53
+
54
+ All rights reserved.
55
+
56
+ MIT License
57
+
58
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
59
+ associated documentation files (the "Software"), to deal in the Software without restriction,
60
+ including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
61
+ and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
62
+ subject to the following conditions:
63
+
64
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
65
+
66
+ THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
67
+ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
68
+ NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
69
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
70
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
71
+ ```
72
+ ---
73
+
74
+ ## @actions/io 3.0.2
75
+
76
+ Source: https://github.com/actions/toolkit/tree/main/packages/io
77
+
78
+ License: MIT
79
+
80
+ ```text
81
+ The MIT License (MIT)
82
+
83
+ Copyright 2019 GitHub
84
+
85
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
86
+
87
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
88
+
89
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
90
+ ```
91
+ ---
92
+
93
+ ## ignore 7.0.8
94
+
95
+ Source: https://github.com/kaelzhang/node-ignore.git
96
+
97
+ License: MIT
98
+
99
+ ```text
100
+ Copyright (c) 2013 Kael Zhang <i@kael.me>, contributors
101
+ http://kael.me/
102
+
103
+ Permission is hereby granted, free of charge, to any person obtaining
104
+ a copy of this software and associated documentation files (the
105
+ "Software"), to deal in the Software without restriction, including
106
+ without limitation the rights to use, copy, modify, merge, publish,
107
+ distribute, sublicense, and/or sell copies of the Software, and to
108
+ permit persons to whom the Software is furnished to do so, subject to
109
+ the following conditions:
110
+
111
+ The above copyright notice and this permission notice shall be
112
+ included in all copies or substantial portions of the Software.
113
+
114
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
115
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
116
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
117
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
118
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
119
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
120
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
121
+ ```
122
+ ---
123
+
124
+ ## tunnel 0.0.6
125
+
126
+ Source: https://github.com/koichik/node-tunnel/
127
+
128
+ License: MIT
129
+
130
+ ```text
131
+ The MIT License (MIT)
132
+
133
+ Copyright (c) 2012 Koichi Kobayashi
134
+
135
+ Permission is hereby granted, free of charge, to any person obtaining a copy
136
+ of this software and associated documentation files (the "Software"), to deal
137
+ in the Software without restriction, including without limitation the rights
138
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
139
+ copies of the Software, and to permit persons to whom the Software is
140
+ furnished to do so, subject to the following conditions:
141
+
142
+ The above copyright notice and this permission notice shall be included in
143
+ all copies or substantial portions of the Software.
144
+
145
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
146
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
147
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
148
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
149
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
150
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
151
+ THE SOFTWARE.
152
+ ```
153
+ ---
154
+
155
+ ## undici 6.28.0
156
+
157
+ Source: https://undici.nodejs.org
158
+
159
+ License: MIT
160
+
161
+ ```text
162
+ MIT License
163
+
164
+ Copyright (c) Matteo Collina and Undici contributors
165
+
166
+ Permission is hereby granted, free of charge, to any person obtaining a copy
167
+ of this software and associated documentation files (the "Software"), to deal
168
+ in the Software without restriction, including without limitation the rights
169
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
170
+ copies of the Software, and to permit persons to whom the Software is
171
+ furnished to do so, subject to the following conditions:
172
+
173
+ The above copyright notice and this permission notice shall be included in all
174
+ copies or substantial portions of the Software.
175
+
176
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
177
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
178
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
179
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
180
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
181
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
182
+ SOFTWARE.
183
+ ```
Binary file
package/dist/cli.js ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ var be=Object.create;var U=Object.defineProperty;var _e=Object.getOwnPropertyDescriptor;var Se=Object.getOwnPropertyNames;var Oe=Object.getPrototypeOf,ke=Object.prototype.hasOwnProperty;var Pe=(e,t)=>()=>{try{return t||e((t={exports:{}}).exports,t),t.exports}catch(n){throw t=0,n}};var Ne=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of Se(t))!ke.call(e,s)&&s!==n&&U(e,s,{get:()=>t[s],enumerable:!(r=_e(t,s))||r.enumerable});return e};var B=(e,t,n)=>(n=e!=null?be(Oe(e)):{},Ne(t||!e||!e.__esModule?U(n,"default",{value:e,enumerable:!0}):n,e));var W=Pe((Ft,$)=>{function ne(e){return Array.isArray(e)?e:[e]}var _=void 0,m="",L=" ",h="\\",We=/[.*+?()[\]{}^$|\\/]/,Fe=/^ +$/,Ue=/(?:[^\\]|^)\\$/,Be=/^\\!/,Me=/^\\#/,je=/\r?\n/g,Xe="//",S=47,Z=46,p="/",re="node-ignore";typeof Symbol<"u"&&(re=Symbol.for("node-ignore"));var se=re,x=(e,t,n)=>(Object.defineProperty(e,t,{value:n}),n),ie=()=>!1,ze=e=>{let{length:t}=e;return e.slice(0,t-t%2)},qe={alnum:"0-9A-Za-z",alpha:"A-Za-z",blank:" \\t",cntrl:"\\x00-\\x1f\\x7f",digit:"0-9",graph:"!-.0-~",lower:"a-z",print:" -.0-~",punct:"!-.:-@\\[-`{-~",space:" \\t\\n\\r",upper:"A-Z",xdigit:"0-9A-Fa-f"},Je="\\]^-[",O=e=>Je.indexOf(e)<0?e:h+e,Ke="(?!\\/)",Ye=(e,t)=>{if(e)return`[^\\/${t}]`;let n=`[${t}]`;return new RegExp(n).test("/")?Ke+n:n},Ze=(e,t)=>{let{length:n}=e,r=t+1,s=m,i=e[r];(i==="!"||i==="^")&&(s="^",r++);let o=m,a=m;for(;;){let u=e[r];if(u===_)return null;if(u===h){let c=e[r+1];if(c===_)return null;o+=O(c),a=c,r++}else if(u==="-"&&a&&r+1<n&&e[r+1]!=="]"){r++;let c=e[r];c===h&&(c=e[r+=1]),a<=c&&(o+=`-${O(c)}`),a=m}else if(u==="["&&e[r+1]===":"){let c=r+2,d=c;for(;d<n&&e[d]!=="]";)d++;if(d===n)return null;if(d>c&&e[d-1]===":"){let l=qe[e.slice(c,d-1)];if(l===_)return null;o+=l,a=m,r=d}else o+=O("["),a="[",r=c-2}else o+=O(u),a=u;if(r++,e[r]==="]")return{end:r,source:Ye(s,o)}}},Qe="[]",b="\0",et=new RegExp(`${b}(\\d+)${b}`,"g"),Q="\uE000",tt=e=>{let t=[],n=o=>`${b}${t.push(o)-1}${b}`,{length:r}=e,s=m,i=0;for(;i<r;){let o=e[i];if(o===h){let a=e[i+1];a==="*"||a==="["||a===L||a===h?s+=e.slice(i,i+2):s+=n(We.test(a)?h+a:a),i+=2}else if(o===b)s+=n(`[${b}]`),i++;else if(o==="["){let a=Ze(e,i);a===null?(s+=n(Qe),i=r):(s+=n(a.source),i=a.end+1)}else s+=o,i++}return{source:s,sources:t}},G=null,nt=/\/(?!$)/,rt=[[/^\uFEFF/,()=>m,"\uFEFF"],[/[\r\n]+$/,()=>m],[/((?:\\\\)*?)(\\? +)$/,(e,t,n)=>t+(n.indexOf("\\")===0?L:m)],[/(\\+?) /g,(e,t)=>{let{length:n}=t;return t.slice(0,n-n%2)+L}],[/[\\$.|*+(){^]/g,e=>`\\${e}`],[/(?!\\)\?/g,()=>"[^/]","?"],[/^\//,()=>"^",p],[/\//g,()=>"\\/",p],[/^\^*(?:\\\*\\\*\\\/)+/,()=>"^(?:.*\\/)?","*"],[G,(e,t)=>!e||e[0]==="^"?e:(nt.test(t)?"^":"(?:^|\\/)")+e],[/\\\/\\\*\\\*(?=\\\/|$)/g,(e,t,n)=>t+6<n.length?n.slice(t+6)==="\\/"?"(?:\\/[^\\/]+)+":"(?:\\/[^\\/]+)*":"\\/.+","*"],[/(^|[^\\]+)(\\\*)+(?=.+)/g,(e,t,n)=>{let r=n.replace(/\\\*/g,"[^\\/]*");return t+r},"*"],[/(^|[^\\])((?:\\\\)*)\\\*$/,(e,t,n)=>n.length/2%2===0?t+n+Q:e,"*"],[/\\\\\\(?=[$.|*+(){^])/g,()=>h,h+h],[/\\\\/g,()=>h,h+h],[/\\\[([^\]/]*?)(\\*)($|\])/g,(e,t,n,r)=>`\\[${t}${ze(n)}${r}`,"["],[G,e=>{let t=e[e.length-1];return!t||t===Q?e:t===p?`${e}$`:`${e}(?=$|\\/$)`}]],st=/(^|\\\/)?\uE000$/,P="regex",N="checkRegex",ee="_",it={[P](e,t){return`${t?`${t}[^/]+`:"[^/]*"}(?=$|\\/$)`},[N](e,t){return`${t?`${t}[^/]*`:"[^/]*"}(?=$|\\/$)`}},k="[^\\/]*",ot=e=>{if(e.indexOf(k)<0)return e;let t=[],{length:n}=e,r=0;for(;r<n;){let a=e[r];if(e.startsWith(k,r))t.push({wildcard:!0}),r+=k.length;else if(a==="["){let u=r+1;for(e[u]==="^"&&u++,e[u]==="]"&&u++;u<n&&e[u]!=="]";)u+=e[u]===h?2:1;u++,t.push({single:e.slice(r,u)}),r=u}else if(a===h)t.push({single:e.slice(r,r+2)}),r+=2;else if(a==="("){let u=0,c=r;do e[c]===h?c++:e[c]==="("?u++:e[c]===")"&&u--,c++;while(c<n&&u>0);"*+?".indexOf(e[c])>=0&&c++,t.push({boundary:e.slice(r,c)}),r=c}else a==="^"||a==="$"?(t.push({boundary:a}),r++):(t.push({single:a}),r++)}let s=m,i=[],o=()=>{let a;i.forEach((u,c)=>{u.wildcard&&(a=c)}),i.forEach((u,c)=>{if(!u.wildcard){s+=u.single;return}s+=c===a?k:`(?:(?!${i[c+1].single})[^\\/])*`}),i=[]};return t.forEach(a=>{if(a.boundary===void 0){i.push(a);return}o(),s+=a.boundary}),o(),s},at=e=>{let{source:t,sources:n}=tt(e),r=rt.reduce((s,[i,o,a])=>i===G?o(s,e):a!==_&&s.indexOf(a)<0?s:i.test(s)?s.replace(i,o.bind(e)):s,t);return n.length?r.replace(et,(s,i)=>n[i]):r},oe=e=>{let t=e.indexOf(p);return t<0||t===e.length-1},ct=e=>{let t=e.length-1,n=e.lastIndexOf(p,e[t]===p?t-1:t);return n<0?e:e.slice(n+1)},te=e=>{if(e.charCodeAt(0)===S||e.indexOf(Xe)>=0){let r=e.split(p).filter(Boolean);return r.pop(),r.length?r.join(p)+p:m}let t=e.length-1,n=e.lastIndexOf(p,e.charCodeAt(t)===S?t-1:t);return n<0?m:e.slice(0,n+1)},v=e=>typeof e=="string",ut=e=>e&&v(e)&&!Fe.test(e)&&!Ue.test(e)&&e.indexOf("#")!==0,lt=e=>e.split(je).filter(Boolean),D=class{constructor(t,n,r,s,i,o){this.pattern=t,this.mark=n,this.negative=i,x(this,"body",r),x(this,"ignoreCase",s),x(this,"regexPrefix",o)}get _basenameOnly(){return x(this,"_basenameOnly",oe(this.body))}get regex(){let t=ee+P;return this[t]?this[t]:this._make(P,t)}get checkRegex(){let t=ee+N;return this[t]?this[t]:this._make(N,t)}_make(t,n){let r=ot(this.regexPrefix.replace(st,it[t])),s=this.ignoreCase?new RegExp(r,"i"):new RegExp(r);return x(this,n,s)}},dt=({pattern:e,mark:t},n)=>{let r=!1,s=e;s.indexOf("!")===0&&(r=!0,s=s.substr(1)),s=s.replace(Be,"!").replace(Me,"#");let i=at(s);return new D(e,t,s,n,r,i)},H=class{constructor(t){this._ignoreCase=t,this._rules=[],this._basenameCount=0}_add(t){if(t&&t[se]){this._rules=this._rules.concat(t._rules._rules),this._basenameCount+=t._rules._basenameCount,this._added=!0;return}if(v(t)&&(t={pattern:t}),ut(t.pattern)){let n=dt(t,this._ignoreCase);this._added=!0,this._rules.push(n),oe(n.body)&&this._basenameCount++}}add(t){return this._added=!1,ne(v(t)?lt(t):t).forEach(this._add,this),this._added}test(t,n,r){let s=!1,i=!1,o,a=this._rules,{length:u}=a,c=this._basenameCount*2>=u,d=c?ct(t):t;for(let f=0;f<u;f++){let g=a[f],{negative:E}=g;!(i===E&&s!==i||E&&!s&&!i&&!n)&&g[r].test(c&&g._basenameOnly?d:t)&&(s=!E,i=E,o=E?_:g)}let l={ignored:s,unignored:i};return o&&(l.rule=o),l}},ft=(e,t)=>{throw new t(e)},w=(e,t,n)=>v(e)?e?w.isNotRelative(e)?n(`path should be a \`path.relative()\`d string, but got "${t}"`,RangeError):!0:n("path must not be empty",TypeError):n(`path must be a string, but got \`${t}\``,TypeError),ae=e=>{let t=e.charCodeAt(0);if(t===S)return!0;if(t!==Z)return!1;if(e.length===1)return!0;let n=e.charCodeAt(1);return n===S?!0:n!==Z?!1:e.length===2||e.charCodeAt(2)===S};w.isNotRelative=ae;w.convert=e=>e;var T=class{constructor({ignorecase:t=!0,ignoreCase:n=t,allowRelativePaths:r=!1}={}){x(this,se,!0),this._rules=new H(n),this._strictPathCheck=!r,this._initCache()}_initCache(){this._ignoreCache=Object.create(null),this._testCache=Object.create(null)}add(t){return this._rules.add(t)&&this._initCache(),this}addPattern(t){return this.add(t)}_test(t,n,r){let s=t&&w.convert(t);return w(s,t,this._strictPathCheck?ft:ie),this._t(s,n,r)}checkIgnore(t){if(t.charCodeAt(t.length-1)!==S)return this.test(t);let n=te(t);if(n){let r=this._t(n,this._testCache,!0);if(r.ignored)return r}return this._rules.test(t,!1,N)}_t(t,n,r){if(t in n)return n[t];let s=te(t),i=s?this._t(s,n,r):_;return n[t]=i&&i.ignored?i:this._rules.test(t,r,P)}ignores(t){return this._test(t,this._ignoreCache,!1).ignored}createFilter(){return t=>!this.ignores(t)}filter(t){return ne(t).filter(this.createFilter())}test(t){return this._test(t,this._testCache,!0)}},V=e=>new T(e),ht=e=>w(e&&w.convert(e),e,ie),ce=()=>{let e=n=>/^\\\\\?\\/.test(n)||/["<>|\u0000-\u001F]+/u.test(n)?n:n.replace(/\\/g,"/");w.convert=e;let t=/^[a-z]:\//i;w.isNotRelative=n=>t.test(n)||ae(n)};typeof process<"u"&&process.platform==="win32"&&ce();$.exports=V;V.default=V;$.exports.isPathValid=ht;x($.exports,Symbol.for("setupWindows"),ce)});import{parseArgs as Nt}from"node:util";var A=["syntax","duplicates","dangling","unowned"];function j(e,t){return e.errorCount>0||t==="warning"&&e.warningCount>0}var C=class{constructor(t){this.limit=t;if(!Number.isSafeInteger(t)||t<0)throw new Error("Issue retention limit must be a non-negative integer")}limit;issues=[];issueCount=0;errorCount=0;warningCount=0;add(t){this.issueCount+=1,t.severity==="error"?this.errorCount+=1:this.warningCount+=1,this.insert(t)}merge(t){this.issueCount+=t.issueCount,this.errorCount+=t.errorCount,this.warningCount+=t.warningCount;for(let n of t.issues)this.insert(n)}insert(t){if(this.limit===0)return;let n=0,r=this.issues.length;for(;n<r;){let s=n+r>>>1,i=this.issues[s];i!==void 0&&ve(i,t)<=0?n=s+1:r=s}n<this.limit&&(this.issues.splice(n,0,t),this.issues.length>this.limit&&this.issues.pop())}};function ve(e,t){return M(e.severity)-M(t.severity)||I(e.path,t.path)||(e.line??0)-(t.line??0)||I(e.check,t.check)||I(e.code,t.code)}function M(e){return e==="error"?0:1}function I(e,t){return e===t?0:e<t?-1:1}var X=["duplicates","dangling","unowned"];function z(e,t){let n=e.split(/[\s,]+/u).map(s=>s.trim().toLowerCase()).filter(Boolean);if(n.length===0)return new Set(t);let r=new Set;for(let s of n){if(!A.includes(s))throw new Error(`Unknown check ${JSON.stringify(s)}. Expected one of: ${A.join(", ")}`);r.add(s)}return r}function q(e,t){let n=e.trim().toLowerCase()||t;if(n!=="error"&&n!=="warning")throw new Error('fail-on must be either "error" or "warning"');return n}function J(e,t,n,r){if(e.trim()==="")return t;let s=Number(e);if(!Number.isSafeInteger(s)||s<0||s>n)throw new Error(`${r} must be a non-negative integer up to ${n}`);return s}function R(e){let t="";for(let n of e){let r=n.codePointAt(0)??0;t+=$e(r)?`\\u${r.toString(16).padStart(4,"0")}`:n}return t}function $e(e){return e<=31||e>=127&&e<=159||e===1564||e===8206||e===8207||e>=8234&&e<=8238||e>=8294&&e<=8297}function K(e){let t=`${e.issueCount} issue${e.issueCount===1?"":"s"} in ${R(e.codeownersPath)}`,n=e.issues.map(i=>{let o=[R(i.path),i.line,i.column].filter(a=>a!==void 0).join(":");return`${i.severity.toUpperCase()} [${i.check}] ${o}: ${R(Ie(i))}`}),r=`${e.stats.files} files, ${e.stats.rules} rules, ${e.stats.matchedRules} matched rules`,s=e.issueCount-e.issues.length;return[t,...n,...s>0?[`${s} additional issue${s===1?"":"s"} omitted`]:[],r].join(`
3
+ `)}function Ie(e){return e.suggestion===void 0?e.message:`${e.message} Suggestion: ${e.suggestion}`}var Ae=new Set([429,502,503,504]);async function Y(e,t=fetch,n=r=>new Promise(s=>setTimeout(s,r))){let[r,s,i]=e.repository.split("/");if(r===void 0||r===""||s===void 0||s===""||i!==void 0)throw new Error(`Repository must use the owner/name format: ${e.repository}`);let o=De(e.apiUrl,r,s),a=e.ref?.trim();a!==void 0&&a!==""&&o.searchParams.set("ref",a);let u=new Headers({accept:"application/vnd.github+json","user-agent":"codeowners-guard"});e.token!==void 0&&e.token!==""&&u.set("authorization",`Bearer ${e.token}`);let c=await Le(o,u,e.repository,t,n),d=await Te(c);if(!Ve(d))throw new Error("GitHub returned an invalid CODEOWNERS error response");return d.errors.map(l=>{let f={check:"syntax",code:l.kind||"github-codeowners-error",severity:"error",path:l.path,line:l.line,column:l.column,message:l.message};return l.suggestion!==null&&l.suggestion!==""&&(f.suggestion=l.suggestion),f})}async function Le(e,t,n,r,s){for(let i=1;i<=3;i+=1){let o;try{o=await r(e,{headers:t,redirect:"error",signal:AbortSignal.timeout(15e3)})}catch(a){throw a instanceof Error&&(a.name==="TimeoutError"||a.name==="AbortError")?new Error(`GitHub CODEOWNERS validation timed out after ${15e3/1e3} seconds`,{cause:a}):new Error("GitHub CODEOWNERS validation request failed",{cause:a})}if(o.ok)return o;if(!Ae.has(o.status)||i===3)throw await o.body?.cancel().catch(()=>{}),He(o.status,n);await o.body?.cancel().catch(()=>{}),await s(Ge(o,i))}throw new Error("GitHub CODEOWNERS validation exhausted its retry budget")}function Ge(e,t){let n=e.headers.get("retry-after")?.trim(),r;if(n!==void 0&&/^\d+$/u.test(n))r=Number(n)*1e3;else if(n!==void 0){let s=Date.parse(n);Number.isFinite(s)&&(r=Math.max(0,s-Date.now()))}return Math.min(r??250*2**(t-1),1e4)}function De(e,t,n){let r;try{r=new URL(e)}catch(i){throw new Error(`Invalid GitHub API URL: ${e}`,{cause:i})}if(r.protocol!=="https:")throw new Error("GitHub API URL must use HTTPS");if(r.username!==""||r.password!=="")throw new Error("GitHub API URL must not contain credentials");if(r.search!==""||r.hash!=="")throw new Error("GitHub API URL must not contain a query or fragment");let s=r.pathname.replace(/\/+$/u,"");return r.pathname=`${s}/repos/${encodeURIComponent(t)}/${encodeURIComponent(n)}/codeowners/errors`,r}function He(e,t){return e===401?new Error("GitHub authentication failed; check the supplied token"):e===403?new Error("GitHub denied CODEOWNERS access; check token permissions and rate limits"):e===404?new Error(`GitHub could not find ${t}, its ref, or its CODEOWNERS file`):e===429?new Error("GitHub rate-limited the CODEOWNERS request; retry later"):e>=500?new Error(`GitHub CODEOWNERS service failed with ${e}; retry later`):new Error(`GitHub CODEOWNERS validation failed with ${e}`)}async function Te(e){let t=e.headers.get("content-length");if(t!==null&&Number.isFinite(Number(t))&&Number(t)>1048576)throw await e.body?.cancel(),new Error("GitHub CODEOWNERS response exceeds the 1 MiB limit");if(e.body===null)throw new Error("GitHub returned an empty CODEOWNERS response");let n=e.body.getReader(),r=new TextDecoder,s="",i=0;for(;;){let{done:o,value:a}=await n.read();if(o)break;if(i+=a.byteLength,i>1048576)throw await n.cancel(),new Error("GitHub CODEOWNERS response exceeds the 1 MiB limit");s+=r.decode(a,{stream:!0})}s+=r.decode();try{return JSON.parse(s)}catch(o){throw new Error("GitHub returned invalid JSON for CODEOWNERS validation",{cause:o})}}function Ve(e){if(typeof e!="object"||e===null||!("errors"in e))return!1;let{errors:t}=e;return Array.isArray(t)&&t.every(n=>typeof n=="object"&&n!==null&&Number.isSafeInteger(n.line)&&n.line>0&&Number.isSafeInteger(n.column)&&n.column>0&&typeof n.kind=="string"&&typeof n.message=="string"&&typeof n.path=="string"&&(typeof n.suggestion=="string"||n.suggestion===null))}var ge=B(W(),1);var de=B(W(),1);import{isAbsolute as mt,relative as gt,resolve as ue,sep as pt}from"node:path";function y(e){return e.replaceAll("\\","/").replace(/^(?:\.\/)+/u,"").replace(/^\/+|\/+$/gu,"")}function le(e,t){let n=ue(e),r=ue(n,t);return F(n,r,t),r}function F(e,t,n=t){let r=gt(e,t);if(r===".."||r.startsWith(`..${pt}`)||mt(r))throw new Error(`Path must stay within the repository: ${n}`)}function fe(e){return e.map(t=>{let n=(0,de.default)({ignorecase:!1}).add(t.pattern);return{rule:t,matches:r=>n.ignores(r)}})}function he(e){let t=[];for(let[n,r]of e.split(/\r?\n/u).entries()){let s=n+1,i=r.trim();if(i===""||i.startsWith("#"))continue;let o=wt(i),a=o[0];if(a===void 0)continue;let u=o.slice(1);t.push({line:s,pattern:a,owners:u})}return t}function*me(e){let t=new Map;for(let n of e){let r=t.get(n.pattern);if(r===void 0){t.set(n.pattern,n.line);continue}yield{line:n.line,message:`Pattern ${JSON.stringify(n.pattern)} duplicates line ${r}`}}}function wt(e){let t=[],n="",r=!1;for(let s of e){if(/\s/u.test(s)&&!r){n!==""&&(t.push(n),n="");continue}n+=s,r=s==="\\"&&!r,s!=="\\"&&(r=!1)}return n!==""&&t.push(n),t}function pe(e){let t=e.skipLines??new Set,n=he(e.source).filter(l=>!t.has(l.line)),r=e.checks.has("dangling"),s=e.checks.has("unowned"),i=r||s,o=i?fe(n):[],a=i?yt(e.files,e.exclude??[]):[],u=new Uint8Array(n.length),c=0,d=new C(e.maxIssues??1e3);if(e.checks.has("duplicates"))for(let l of me(n))d.add({check:"duplicates",code:"duplicate-pattern",severity:"warning",path:e.codeownersPath,line:l.line,message:l.message});for(let l of a){let f;if(i)for(let g=0;g<o.length;g+=1){let E=o[g];E?.matches(l)&&(u[g]===0&&(u[g]=1,c+=1),s&&(f=E.rule))}s&&(f===void 0||f.owners.length===0)&&d.add({check:"unowned",code:"unowned-file",severity:"warning",path:l,message:f===void 0?"File is not matched by any CODEOWNERS rule":`File is explicitly unowned by the rule on line ${f.line}`})}if(r)for(let l=0;l<n.length;l+=1){let f=n[l];f!==void 0&&u[l]===0&&d.add({check:"dangling",code:"dangling-pattern",severity:"warning",path:e.codeownersPath,line:f.line,message:`Pattern ${JSON.stringify(f.pattern)} does not match a tracked file`})}return{issues:d.issues,issueCount:d.issueCount,errorCount:d.errorCount,warningCount:d.warningCount,stats:{files:a.length,rules:n.length,matchedRules:c}}}function yt(e,t){let n=t.length===0?void 0:(0,ge.default)({ignorecase:!1}).add(t),r=new Set,s=[];for(let i of e){let o=y(i);o===""||r.has(o)||n?.ignores(o)||(r.add(o),s.push(o))}return s.sort()}import{spawn as Et}from"node:child_process";import{lstat as xt,readFile as Ct,realpath as we}from"node:fs/promises";import{relative as Rt,resolve as ye}from"node:path";import{StringDecoder as bt}from"node:string_decoder";var _t=3*1024*1024,St=[".github/CODEOWNERS","CODEOWNERS","docs/CODEOWNERS"];async function Ee(e,t){let n=ye(e),r=await we(n),s=t===void 0?St:[t];for(let o of s){let a=le(n,o),u=await Ot(a,o);if(u!==void 0){let c=await we(a);if(F(r,c,o),u.size>_t)throw new Error(`CODEOWNERS exceeds GitHub's 3 MiB limit: ${o}`);return{absolutePath:c,relativePath:y(Rt(n,a)),source:await Ct(c,"utf8")}}}let i=s.map(o=>JSON.stringify(o)).join(", ");throw new Error(`No CODEOWNERS file found at ${i}`)}async function xe(e){let t=ye(e);return kt(t)}async function Ot(e,t){try{let n=await xt(e);if(n.isSymbolicLink())throw new Error(`CODEOWNERS must not be a symbolic link: ${t}`);return n.isFile()?n:void 0}catch(n){if(Pt(n)&&n.code==="ENOENT")return;throw n}}function kt(e){return new Promise((t,n)=>{let r=Et("git",["-C",e,"ls-files","--cached","-z"],{stdio:["ignore","pipe","pipe"],windowsHide:!0}),s=new bt("utf8"),i=[],o="",a="",u=!1;r.stdout.on("data",c=>{o+=s.write(c);let d=o.indexOf("\0");for(;d!==-1;){let l=o.slice(0,d);l!==""&&i.push(y(l)),o=o.slice(d+1),d=o.indexOf("\0")}}),r.stderr.on("data",c=>{a.length<8192&&(a+=c.toString("utf8",0,8192-a.length))}),r.once("error",c=>{u=!0,n(new Error("Unable to start git while listing tracked files",{cause:c}))}),r.once("close",(c,d)=>{if(u)return;if(u=!0,o+=s.end(),c===0){o!==""&&i.push(y(o)),t(i.sort());return}let l=a.trim()||`git exited with ${c??d}`;n(new Error(`Unable to list tracked files: ${l}`))})})}function Pt(e){return e instanceof Error&&"code"in e}async function Ce(e){let t=new Set([...e.checks].filter(l=>l!=="syntax")),n=await Ee(e.repositoryPath,e.codeownersPath),r=t.has("dangling")||t.has("unowned"),s=Promise.resolve([]);if(e.checks.has("syntax")){if(e.github===void 0)throw new Error("The syntax check requires a GitHub repository");s=Y(e.github)}let i=r?xe(e.repositoryPath):Promise.resolve([]),[o,a]=await Promise.all([s,i]),u=new Set(o.filter(l=>y(l.path)===n.relativePath).flatMap(l=>l.line===void 0?[]:[l.line])),c=pe({source:n.source,codeownersPath:n.relativePath,files:a,checks:t,exclude:e.exclude??[],...e.maxIssues===void 0?{}:{maxIssues:e.maxIssues},skipLines:u}),d=new C(e.maxIssues??1e3);for(let l of o)d.add(l);return d.merge(c),{codeownersPath:n.relativePath,issues:d.issues,issueCount:d.issueCount,errorCount:d.errorCount,warningCount:d.warningCount,stats:c.stats}}var Re="0.1.1";async function vt(){let{values:e,positionals:t}=Nt({allowPositionals:!0,strict:!0,options:{"api-url":{type:"string"},checks:{type:"string",short:"c"},codeowners:{type:"string"},exclude:{type:"string",multiple:!0},"fail-on":{type:"string"},format:{type:"string",short:"f"},help:{type:"boolean",short:"h"},"max-issues":{type:"string"},ref:{type:"string"},repository:{type:"string",short:"r"},version:{type:"boolean",short:"v"}}});if(e.help===!0){console.log($t);return}if(e.version===!0){console.log(Re);return}let n=z(e.checks??"",X),r=q(e["fail-on"]??"","warning"),s=J(e["max-issues"]??"",1e3,1e4,"max-issues"),i=e.format??"text";if(i!=="text"&&i!=="json")throw new Error('format must be either "text" or "json"');let o=e.repository??process.env.GITHUB_REPOSITORY;if(n.has("syntax")&&(o===void 0||o===""))throw new Error("--repository is required when the syntax check is enabled");let a=await Ce({repositoryPath:t[0]??".",checks:n,exclude:e.exclude??[],maxIssues:s,...e.codeowners===void 0?{}:{codeownersPath:e.codeowners},...n.has("syntax")?{github:{apiUrl:e["api-url"]??process.env.GITHUB_API_URL??"https://api.github.com",repository:o??"",token:process.env.GITHUB_TOKEN??process.env.GH_TOKEN??"",...e.ref===void 0?{}:{ref:e.ref}}}:{}});console.log(i==="json"?JSON.stringify({valid:a.issueCount===0,...a},null,2):K(a)),process.exitCode=j(a,r)?1:0}var $t=`codeowners-guard [repository-path] [options]
4
+
5
+ Checks a repository's effective CODEOWNERS file.
6
+
7
+ Options:
8
+ -c, --checks <list> Comma-separated checks (duplicates,dangling,unowned,syntax)
9
+ --codeowners <path> Use a specific CODEOWNERS file
10
+ --exclude <pattern> Exclude files from local checks (repeatable)
11
+ --fail-on <severity> Failure threshold: warning or error (default: warning)
12
+ --max-issues <count> Maximum retained issue details (default: 1000, max: 10000)
13
+ -f, --format <format> Output format: text or json (default: text)
14
+ -r, --repository <repo> GitHub owner/name, required by the syntax check
15
+ --ref <ref> GitHub branch, tag, or commit to validate
16
+ --api-url <url> GitHub API URL (default: https://api.github.com)
17
+ -v, --version Print the version
18
+ -h, --help Show this help
19
+
20
+ Environment:
21
+ GITHUB_TOKEN or GH_TOKEN Token for authenticated GitHub API access`;vt().catch(e=>{console.error(R(e instanceof Error?e.message:String(e))),process.exitCode=2});
22
+ //# sourceMappingURL=cli.js.map
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "codeowners-guard",
3
+ "version": "0.1.1",
4
+ "description": "GitHub-native CODEOWNERS syntax, coverage, and rule validation",
5
+ "type": "module",
6
+ "license": "SEE LICENSE IN LICENSE.md",
7
+ "author": "Rares (rarepops)",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/rarepops/codeowners-guard.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/rarepops/codeowners-guard/issues"
14
+ },
15
+ "homepage": "https://github.com/rarepops/codeowners-guard#readme",
16
+ "packageManager": "npm@11.19.0",
17
+ "keywords": [
18
+ "codeowners",
19
+ "github-action",
20
+ "lint",
21
+ "validation"
22
+ ],
23
+ "bin": {
24
+ "codeowners-guard": "./dist/cli.js"
25
+ },
26
+ "files": [
27
+ "assets/codeowners-guard.png",
28
+ "dist/cli.js",
29
+ "THIRD_PARTY_NOTICES.md"
30
+ ],
31
+ "engines": {
32
+ "node": ">=24"
33
+ },
34
+ "scripts": {
35
+ "bench": "vitest bench --run",
36
+ "build": "node scripts/build.mjs",
37
+ "check": "npm run lint && npm run typecheck && npm run test:coverage && npm run build",
38
+ "format": "biome check --write src test scripts",
39
+ "lint": "biome check src test scripts",
40
+ "test": "vitest run",
41
+ "test:coverage": "vitest run --coverage",
42
+ "typecheck": "tsc --noEmit"
43
+ },
44
+ "devDependencies": {
45
+ "@actions/core": "3.0.1",
46
+ "@biomejs/biome": "2.5.12",
47
+ "@types/node": "24.13.3",
48
+ "@vitest/coverage-v8": "5.0.0",
49
+ "esbuild": "0.28.2",
50
+ "ignore": "7.0.8",
51
+ "typescript": "7.0.2",
52
+ "vitest": "5.0.0",
53
+ "yaml": "2.9.0"
54
+ }
55
+ }