codeowners-guard 0.1.1 → 0.1.2
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/README.md +35 -3
- package/assets/codeowners-guard.png +0 -0
- package/dist/cli.js +4 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,6 +33,24 @@ CODEOWNERS Guard combines GitHub's own diagnostics with local repository checks.
|
|
|
33
33
|
- **No container startup.** The Action runs directly on Node.js 24 on Linux, macOS, and Windows runners.
|
|
34
34
|
- **Useful outside Actions.** The same core ships as a cross-platform CLI with deterministic text and JSON output.
|
|
35
35
|
|
|
36
|
+
## Feature Comparison
|
|
37
|
+
|
|
38
|
+
The closest tools overlap, but they optimize for different workflows. This table compares documented behavior in fixed releases rather than treating every difference as an advantage.
|
|
39
|
+
|
|
40
|
+
| Capability | CODEOWNERS Guard 0.1.2 | [`codeowners-validator` 0.7.4](https://github.com/mszostok/codeowners-validator/tree/v0.7.4) | [`codeowners-audit` 2.9.0](https://github.com/watson/codeowners-audit/tree/v2.9.0) |
|
|
41
|
+
| --- | --- | --- | --- |
|
|
42
|
+
| Delivery | Native Node.js 24 Action and npm CLI | Docker Action and Go CLI | npm CLI and CI command |
|
|
43
|
+
| Syntax approach | GitHub CODEOWNERS errors API at a selected ref | Built-in syntax checker | Local GitHub-parity checks |
|
|
44
|
+
| Duplicate patterns | Built in (`duplicates`) | Built in (`duppatterns`) | Not documented |
|
|
45
|
+
| Dangling or missing patterns | Built in (`dangling`) | Built in (`files`) | Opt-in (`--fail-on-missing-paths`) |
|
|
46
|
+
| Unowned tracked files | Built in (`unowned`) | Experimental (`notowned`) | Built in for non-interactive CI |
|
|
47
|
+
| Separate owner and team lookup | Uses GitHub diagnostics; no extra lookup | Built in (`owners`) | Opt-in (`--validate-github-owners`) |
|
|
48
|
+
| GitHub Actions feedback | File annotations, job summary, and outputs | Docker Action | Run the CLI in a workflow |
|
|
49
|
+
| Interactive HTML coverage report | Not included | Not documented | Built in |
|
|
50
|
+
| Team suggestions from Git history | Not included | Not documented | Opt-in (`--suggest-teams`) |
|
|
51
|
+
|
|
52
|
+
The comparison reflects the linked release documentation checked on 2026-09-04. "Not documented" means the capability is not described there, not that it is impossible. Review each project's current documentation before choosing a tool.
|
|
53
|
+
|
|
36
54
|
## Checks
|
|
37
55
|
|
|
38
56
|
| Check | What it reports | Severity |
|
|
@@ -66,7 +84,7 @@ jobs:
|
|
|
66
84
|
runs-on: ubuntu-latest
|
|
67
85
|
steps:
|
|
68
86
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
69
|
-
- uses: rarepops/codeowners-guard@v0.1.
|
|
87
|
+
- uses: rarepops/codeowners-guard@v0.1.2
|
|
70
88
|
with:
|
|
71
89
|
checks: syntax,duplicates,dangling,unowned
|
|
72
90
|
exclude: |
|
|
@@ -74,7 +92,9 @@ jobs:
|
|
|
74
92
|
coverage/
|
|
75
93
|
```
|
|
76
94
|
|
|
77
|
-
For the strongest supply-chain pinning, replace `v0.1.
|
|
95
|
+
For the strongest supply-chain pinning, replace `v0.1.2` with its full commit SHA. A complete least-privilege workflow is available in [examples/codeowners.yml](examples/codeowners.yml).
|
|
96
|
+
|
|
97
|
+
Released tags are exercised from the independent public [integration repository](https://github.com/rarepops/codeowners-guard-integration).
|
|
78
98
|
|
|
79
99
|
The action adds file annotations and a job summary. Its default token is `${{ github.token }}`, and the workflow only needs `contents: read`.
|
|
80
100
|
|
|
@@ -86,7 +106,7 @@ The Action takes its API endpoint from GitHub's runner environment. It does not
|
|
|
86
106
|
| --- | --- | --- |
|
|
87
107
|
| `github-token` | `${{ github.token }}` | Token used for GitHub diagnostics |
|
|
88
108
|
| `path` | `.` | Repository path relative to `GITHUB_WORKSPACE` |
|
|
89
|
-
| `codeowners` | auto-detect | Explicit CODEOWNERS path |
|
|
109
|
+
| `codeowners` | auto-detect | Explicit CODEOWNERS path for local checks; with `syntax`, it must select GitHub's effective file |
|
|
90
110
|
| `checks` | all checks | Comma-separated checks |
|
|
91
111
|
| `exclude` | none | Newline-separated gitignore patterns omitted from local checks |
|
|
92
112
|
| `repository` | `${{ github.repository }}` | Repository in `owner/name` form |
|
|
@@ -102,6 +122,14 @@ The action returns `valid`, `issue-count`, `error-count`, and `warning-count`.
|
|
|
102
122
|
|
|
103
123
|
## CLI
|
|
104
124
|
|
|
125
|
+
Run the published CLI without installing it globally:
|
|
126
|
+
|
|
127
|
+
```shell
|
|
128
|
+
npx --yes codeowners-guard@0.1.2 . --checks duplicates,dangling,unowned
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Use `codeowners-guard@latest` instead when you explicitly want the newest release. Pinning a version keeps local and CI runs reproducible.
|
|
132
|
+
|
|
105
133
|
Build and run the CLI locally:
|
|
106
134
|
|
|
107
135
|
```shell
|
|
@@ -110,6 +138,8 @@ npm run build
|
|
|
110
138
|
node dist/cli.js .
|
|
111
139
|
```
|
|
112
140
|
|
|
141
|
+
Without `--checks`, the CLI runs `duplicates`, `dangling`, and `unowned`. The `syntax` check is opt-in because it requires a GitHub repository and may require authentication.
|
|
142
|
+
|
|
113
143
|
Local checks require no network access:
|
|
114
144
|
|
|
115
145
|
```shell
|
|
@@ -130,6 +160,8 @@ GITHUB_TOKEN=ghp_example node dist/cli.js . \
|
|
|
130
160
|
|
|
131
161
|
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
162
|
|
|
163
|
+
GitHub's syntax endpoint always validates the effective CODEOWNERS file at the selected ref. When `--codeowners` is combined with `syntax`, the explicit path must resolve to the same effective file in the checkout.
|
|
164
|
+
|
|
133
165
|
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
166
|
|
|
135
167
|
See [troubleshooting](docs/troubleshooting.md) for authentication, ref mismatch, missing file, and exit-code guidance.
|
|
Binary file
|
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
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]
|
|
2
|
+
var be=Object.create;var B=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&&B(e,s,{get:()=>t[s],enumerable:!(r=_e(t,s))||r.enumerable});return e};var M=(e,t,n)=>(n=e!=null?be(Oe(e)):{},Ne(t||!e||!e.__esModule?B(n,"default",{value:e,enumerable:!0}):n,e));var W=Pe((Ft,$)=>{function re(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,Q=46,g="/",se="node-ignore";typeof Symbol<"u"&&(se=Symbol.for("node-ignore"));var ie=se,x=(e,t,n)=>(Object.defineProperty(e,t,{value:n}),n),oe=()=>!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"),ee="\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,()=>"[^/]","?"],[/^\//,()=>"^",g],[/\//g,()=>"\\/",g],[/^\^*(?:\\\*\\\*\\\/)+/,()=>"^(?:.*\\/)?","*"],[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+ee: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===ee?e:t===g?`${e}$`:`${e}(?=$|\\/$)`}]],st=/(^|\\\/)?\uE000$/,P="regex",N="checkRegex",te="_",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},ae=e=>{let t=e.indexOf(g);return t<0||t===e.length-1},ct=e=>{let t=e.length-1,n=e.lastIndexOf(g,e[t]===g?t-1:t);return n<0?e:e.slice(n+1)},ne=e=>{if(e.charCodeAt(0)===S||e.indexOf(Xe)>=0){let r=e.split(g).filter(Boolean);return r.pop(),r.length?r.join(g)+g:m}let t=e.length-1,n=e.lastIndexOf(g,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",ae(this.body))}get regex(){let t=te+P;return this[t]?this[t]:this._make(P,t)}get checkRegex(){let t=te+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[ie]){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),ae(n.body)&&this._basenameCount++}}add(t){return this._added=!1,re(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 p=a[f],{negative:E}=p;!(i===E&&s!==i||E&&!s&&!i&&!n)&&p[r].test(c&&p._basenameOnly?d:t)&&(s=!E,i=E,o=E?_:p)}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),ce=e=>{let t=e.charCodeAt(0);if(t===S)return!0;if(t!==Q)return!1;if(e.length===1)return!0;let n=e.charCodeAt(1);return n===S?!0:n!==Q?!1:e.length===2||e.charCodeAt(2)===S};w.isNotRelative=ce;w.convert=e=>e;var T=class{constructor({ignorecase:t=!0,ignoreCase:n=t,allowRelativePaths:r=!1}={}){x(this,ie,!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:oe),this._t(s,n,r)}checkIgnore(t){if(t.charCodeAt(t.length-1)!==S)return this.test(t);let n=ne(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=ne(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 re(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,oe),ue=()=>{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)||ce(n)};typeof process<"u"&&process.platform==="win32"&&ue();$.exports=V;V.default=V;$.exports.isPathValid=ht;x($.exports,Symbol.for("setupWindows"),ue)});import{parseArgs as Nt}from"node:util";var A=["syntax","duplicates","dangling","unowned"];function X(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 j(e.severity)-j(t.severity)||I(e.path,t.path)||(e.line??0)-(t.line??0)||I(e.check,t.check)||I(e.code,t.code)}function j(e){return e==="error"?0:1}function I(e,t){return e===t?0:e<t?-1:1}var z=["duplicates","dangling","unowned"];function q(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 J(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 K(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 Y(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 Z(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=M(W(),1);var fe=M(W(),1);import{isAbsolute as mt,relative as pt,resolve as le,sep as gt}from"node:path";function y(e){return e.replaceAll("\\","/").replace(/^(?:\.\/)+/u,"").replace(/^\/+|\/+$/gu,"")}function de(e,t){let n=le(e),r=le(n,t);return F(n,r,t),r}function F(e,t,n=t){let r=pt(e,t);if(r===".."||r.startsWith(`..${gt}`)||mt(r))throw new Error(`Path must stay within the repository: ${n}`)}function he(e){return e.map(t=>{let n=(0,fe.default)({ignorecase:!1}).add(t.pattern);return{rule:t,matches:r=>n.ignores(r)}})}function me(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*pe(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 we(e){let t=e.skipLines??new Set,n=me(e.source).filter(l=>!t.has(l.line)),r=e.checks.has("dangling"),s=e.checks.has("unowned"),i=r||s,o=i?he(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 pe(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 p=0;p<o.length;p+=1){let E=o[p];E?.matches(l)&&(u[p]===0&&(u[p]=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 ye}from"node:fs/promises";import{relative as Rt,resolve as Ee}from"node:path";import{StringDecoder as bt}from"node:string_decoder";var _t=3*1024*1024,St=[".github/CODEOWNERS","CODEOWNERS","docs/CODEOWNERS"];async function U(e,t){let n=Ee(e),r=await ye(n),s=t===void 0?St:[t];for(let o of s){let a=de(n,o),u=await Ot(a,o);if(u!==void 0){let c=await ye(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=Ee(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 U(e.repositoryPath,e.codeownersPath);if(e.checks.has("syntax")&&e.codeownersPath!==void 0){let l=await U(e.repositoryPath);if(n.absolutePath!==l.absolutePath)throw new Error("The syntax check can only validate GitHub's effective CODEOWNERS file; remove the explicit CODEOWNERS path or disable the syntax check")}let 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=Z(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=we({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.2";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(t.length>1)throw new Error("Expected at most one repository path");if(e.help===!0){console.log($t);return}if(e.version===!0){console.log(Re);return}let n=q(e.checks??"",z),r=J(e["fail-on"]??"","warning"),s=K(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):Y(a)),process.exitCode=X(a,r)?1:0}var $t=`codeowners-guard [repository-path] [options]
|
|
4
4
|
|
|
5
5
|
Checks a repository's effective CODEOWNERS file.
|
|
6
6
|
|
|
7
7
|
Options:
|
|
8
|
-
-c, --checks <list> Comma-separated checks (duplicates,dangling,unowned
|
|
9
|
-
--codeowners <path> Use a specific CODEOWNERS file
|
|
8
|
+
-c, --checks <list> Comma-separated checks (default: duplicates,dangling,unowned)
|
|
9
|
+
--codeowners <path> Use a specific CODEOWNERS file for local checks
|
|
10
10
|
--exclude <pattern> Exclude files from local checks (repeatable)
|
|
11
11
|
--fail-on <severity> Failure threshold: warning or error (default: warning)
|
|
12
12
|
--max-issues <count> Maximum retained issue details (default: 1000, max: 10000)
|