codeowners-guard 0.1.2 → 0.1.3
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 +19 -3
- package/dist/cli.js +5 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -84,7 +84,7 @@ jobs:
|
|
|
84
84
|
runs-on: ubuntu-latest
|
|
85
85
|
steps:
|
|
86
86
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
87
|
-
- uses: rarepops/codeowners-guard@v0.1.
|
|
87
|
+
- uses: rarepops/codeowners-guard@v0.1.3
|
|
88
88
|
with:
|
|
89
89
|
checks: syntax,duplicates,dangling,unowned
|
|
90
90
|
exclude: |
|
|
@@ -92,7 +92,7 @@ jobs:
|
|
|
92
92
|
coverage/
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
-
For the strongest supply-chain pinning, replace `v0.1.
|
|
95
|
+
For the strongest supply-chain pinning, replace `v0.1.3` with its full commit SHA. A complete least-privilege workflow is available in [examples/codeowners.yml](examples/codeowners.yml).
|
|
96
96
|
|
|
97
97
|
Released tags are exercised from the independent public [integration repository](https://github.com/rarepops/codeowners-guard-integration).
|
|
98
98
|
|
|
@@ -125,7 +125,7 @@ The action returns `valid`, `issue-count`, `error-count`, and `warning-count`.
|
|
|
125
125
|
Run the published CLI without installing it globally:
|
|
126
126
|
|
|
127
127
|
```shell
|
|
128
|
-
npx --yes codeowners-guard@0.1.
|
|
128
|
+
npx --yes codeowners-guard@0.1.3 . --checks duplicates,dangling,unowned
|
|
129
129
|
```
|
|
130
130
|
|
|
131
131
|
Use `codeowners-guard@latest` instead when you explicitly want the newest release. Pinning a version keeps local and CI runs reproducible.
|
|
@@ -166,6 +166,21 @@ Use `--max-issues` to retain up to 10,000 issue details in text or JSON output.
|
|
|
166
166
|
|
|
167
167
|
See [troubleshooting](docs/troubleshooting.md) for authentication, ref mismatch, missing file, and exit-code guidance.
|
|
168
168
|
|
|
169
|
+
### Explain Ownership
|
|
170
|
+
|
|
171
|
+
Explain which local rules match a file and which rule wins:
|
|
172
|
+
|
|
173
|
+
```shell
|
|
174
|
+
npx --yes codeowners-guard@0.1.3 . --explain src/example.ts
|
|
175
|
+
npx --yes codeowners-guard@0.1.3 . --explain src/example.ts --format json
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
This mode lists matching patterns in source order with their line numbers and owners. The last match wins, including ownerless rules that clear ownership. JSON includes `matches`, `winner` (or `null`), `owners`, and a `status` of `owned`, `cleared`, or `unmatched`.
|
|
179
|
+
|
|
180
|
+
Paths are relative to the repository and may name files that are not yet tracked or do not exist. This is a local explanation, not GitHub syntax or owner validation. It uses the effective CODEOWNERS file unless `--codeowners` selects another one. Validation flags such as `--checks`, `--exclude`, and `--ref` cannot be combined with `--explain`.
|
|
181
|
+
|
|
182
|
+
A completed explanation exits with `0`, even when the file has no owner. Invalid arguments or inability to read CODEOWNERS exit with `2`. Use the `unowned` check to enforce ownership in CI. Ownership explanations are available starting with version `0.1.3`.
|
|
183
|
+
|
|
169
184
|
## Design
|
|
170
185
|
|
|
171
186
|
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.
|
|
@@ -180,6 +195,7 @@ The syntax check targets `ref`, while local checks target the checked-out workin
|
|
|
180
195
|
- Terminal text, workflow annotations, and HTML summaries escape control and bidirectional characters.
|
|
181
196
|
- Dependency installation disables lifecycle scripts; CI checks advisories, registry signatures, and dependency diffs.
|
|
182
197
|
- Tagged release artifacts include SHA-256 checksums and GitHub build-provenance attestations.
|
|
198
|
+
- After publication, a read-only job compares npm and GitHub tarballs, verifies checksums and provenance against the release ref and commit, and smoke-tests the registry-installed CLI under Node.js 24.
|
|
183
199
|
|
|
184
200
|
### Performance
|
|
185
201
|
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
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]
|
|
2
|
+
var Se=Object.create;var j=Object.defineProperty;var ke=Object.getOwnPropertyDescriptor;var Pe=Object.getOwnPropertyNames;var Ne=Object.getPrototypeOf,ve=Object.prototype.hasOwnProperty;var $e=(e,t)=>()=>{try{return t||e((t={exports:{}}).exports,t),t.exports}catch(n){throw t=0,n}};var Ae=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of Pe(t))!ve.call(e,s)&&s!==n&&j(e,s,{get:()=>t[s],enumerable:!(r=ke(t,s))||r.enumerable});return e};var z=(e,t,n)=>(n=e!=null?Se(Ne(e)):{},Ae(t||!e||!e.__esModule?j(n,"default",{value:e,enumerable:!0}):n,e));var B=$e((Bt,A)=>{function re(e){return Array.isArray(e)?e:[e]}var O=void 0,m="",H=" ",h="\\",De=/[.*+?()[\]{}^$|\\/]/,Ge=/^ +$/,He=/(?:[^\\]|^)\\$/,Te=/^\\!/,We=/^\\#/,Ve=/\r?\n/g,Fe="//",_=47,Q=46,g="/",se="node-ignore";typeof Symbol<"u"&&(se=Symbol.for("node-ignore"));var ie=se,C=(e,t,n)=>(Object.defineProperty(e,t,{value:n}),n),oe=()=>!1,Ue=e=>{let{length:t}=e;return e.slice(0,t-t%2)},Be={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"},Me="\\]^-[",k=e=>Me.indexOf(e)<0?e:h+e,je="(?!\\/)",ze=(e,t)=>{if(e)return`[^\\/${t}]`;let n=`[${t}]`;return new RegExp(n).test("/")?je+n:n},Xe=(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 l=e[r];if(l===O)return null;if(l===h){let c=e[r+1];if(c===O)return null;o+=k(c),a=c,r++}else if(l==="-"&&a&&r+1<n&&e[r+1]!=="]"){r++;let c=e[r];c===h&&(c=e[r+=1]),a<=c&&(o+=`-${k(c)}`),a=m}else if(l==="["&&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 u=Be[e.slice(c,d-1)];if(u===O)return null;o+=u,a=m,r=d}else o+=k("["),a="[",r=c-2}else o+=k(l),a=l;if(r++,e[r]==="]")return{end:r,source:ze(s,o)}}},qe="[]",b="\0",Je=new RegExp(`${b}(\\d+)${b}`,"g"),ee="\uE000",Ke=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===H||a===h?s+=e.slice(i,i+2):s+=n(De.test(a)?h+a:a),i+=2}else if(o===b)s+=n(`[${b}]`),i++;else if(o==="["){let a=Xe(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}},T=null,Ye=/\/(?!$)/,Ze=[[/^\uFEFF/,()=>m,"\uFEFF"],[/[\r\n]+$/,()=>m],[/((?:\\\\)*?)(\\? +)$/,(e,t,n)=>t+(n.indexOf("\\")===0?H:m)],[/(\\+?) /g,(e,t)=>{let{length:n}=t;return t.slice(0,n-n%2)+H}],[/[\\$.|*+(){^]/g,e=>`\\${e}`],[/(?!\\)\?/g,()=>"[^/]","?"],[/^\//,()=>"^",g],[/\//g,()=>"\\/",g],[/^\^*(?:\\\*\\\*\\\/)+/,()=>"^(?:.*\\/)?","*"],[T,(e,t)=>!e||e[0]==="^"?e:(Ye.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}${Ue(n)}${r}`,"["],[T,e=>{let t=e[e.length-1];return!t||t===ee?e:t===g?`${e}$`:`${e}(?=$|\\/$)`}]],Qe=/(^|\\\/)?\uE000$/,N="regex",v="checkRegex",te="_",et={[N](e,t){return`${t?`${t}[^/]+`:"[^/]*"}(?=$|\\/$)`},[v](e,t){return`${t?`${t}[^/]*`:"[^/]*"}(?=$|\\/$)`}},P="[^\\/]*",tt=e=>{if(e.indexOf(P)<0)return e;let t=[],{length:n}=e,r=0;for(;r<n;){let a=e[r];if(e.startsWith(P,r))t.push({wildcard:!0}),r+=P.length;else if(a==="["){let l=r+1;for(e[l]==="^"&&l++,e[l]==="]"&&l++;l<n&&e[l]!=="]";)l+=e[l]===h?2:1;l++,t.push({single:e.slice(r,l)}),r=l}else if(a===h)t.push({single:e.slice(r,r+2)}),r+=2;else if(a==="("){let l=0,c=r;do e[c]===h?c++:e[c]==="("?l++:e[c]===")"&&l--,c++;while(c<n&&l>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((l,c)=>{l.wildcard&&(a=c)}),i.forEach((l,c)=>{if(!l.wildcard){s+=l.single;return}s+=c===a?P:`(?:(?!${i[c+1].single})[^\\/])*`}),i=[]};return t.forEach(a=>{if(a.boundary===void 0){i.push(a);return}o(),s+=a.boundary}),o(),s},nt=e=>{let{source:t,sources:n}=Ke(e),r=Ze.reduce((s,[i,o,a])=>i===T?o(s,e):a!==O&&s.indexOf(a)<0?s:i.test(s)?s.replace(i,o.bind(e)):s,t);return n.length?r.replace(Je,(s,i)=>n[i]):r},ae=e=>{let t=e.indexOf(g);return t<0||t===e.length-1},rt=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)===_||e.indexOf(Fe)>=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)===_?t-1:t);return n<0?m:e.slice(0,n+1)},$=e=>typeof e=="string",st=e=>e&&$(e)&&!Ge.test(e)&&!He.test(e)&&e.indexOf("#")!==0,it=e=>e.split(Ve).filter(Boolean),W=class{constructor(t,n,r,s,i,o){this.pattern=t,this.mark=n,this.negative=i,C(this,"body",r),C(this,"ignoreCase",s),C(this,"regexPrefix",o)}get _basenameOnly(){return C(this,"_basenameOnly",ae(this.body))}get regex(){let t=te+N;return this[t]?this[t]:this._make(N,t)}get checkRegex(){let t=te+v;return this[t]?this[t]:this._make(v,t)}_make(t,n){let r=tt(this.regexPrefix.replace(Qe,et[t])),s=this.ignoreCase?new RegExp(r,"i"):new RegExp(r);return C(this,n,s)}},ot=({pattern:e,mark:t},n)=>{let r=!1,s=e;s.indexOf("!")===0&&(r=!0,s=s.substr(1)),s=s.replace(Te,"!").replace(We,"#");let i=nt(s);return new W(e,t,s,n,r,i)},V=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($(t)&&(t={pattern:t}),st(t.pattern)){let n=ot(t,this._ignoreCase);this._added=!0,this._rules.push(n),ae(n.body)&&this._basenameCount++}}add(t){return this._added=!1,re($(t)?it(t):t).forEach(this._add,this),this._added}test(t,n,r){let s=!1,i=!1,o,a=this._rules,{length:l}=a,c=this._basenameCount*2>=l,d=c?rt(t):t;for(let f=0;f<l;f++){let p=a[f],{negative:x}=p;!(i===x&&s!==i||x&&!s&&!i&&!n)&&p[r].test(c&&p._basenameOnly?d:t)&&(s=!x,i=x,o=x?O:p)}let u={ignored:s,unignored:i};return o&&(u.rule=o),u}},at=(e,t)=>{throw new t(e)},y=(e,t,n)=>$(e)?e?y.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===_)return!0;if(t!==Q)return!1;if(e.length===1)return!0;let n=e.charCodeAt(1);return n===_?!0:n!==Q?!1:e.length===2||e.charCodeAt(2)===_};y.isNotRelative=ce;y.convert=e=>e;var F=class{constructor({ignorecase:t=!0,ignoreCase:n=t,allowRelativePaths:r=!1}={}){C(this,ie,!0),this._rules=new V(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&&y.convert(t);return y(s,t,this._strictPathCheck?at:oe),this._t(s,n,r)}checkIgnore(t){if(t.charCodeAt(t.length-1)!==_)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,v)}_t(t,n,r){if(t in n)return n[t];let s=ne(t),i=s?this._t(s,n,r):O;return n[t]=i&&i.ignored?i:this._rules.test(t,r,N)}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)}},U=e=>new F(e),ct=e=>y(e&&y.convert(e),e,oe),le=()=>{let e=n=>/^\\\\\?\\/.test(n)||/["<>|\u0000-\u001F]+/u.test(n)?n:n.replace(/\\/g,"/");y.convert=e;let t=/^[a-z]:\//i;y.isNotRelative=n=>t.test(n)||ce(n)};typeof process<"u"&&process.platform==="win32"&&le();A.exports=U;U.default=U;A.exports.isPathValid=ct;C(A.exports,Symbol.for("setupWindows"),le)});import{parseArgs as Lt}from"node:util";var G=["syntax","duplicates","dangling","unowned"];function q(e,t){return e.errorCount>0||t==="warning"&&e.warningCount>0}var R=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&&Ie(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 Ie(e,t){return X(e.severity)-X(t.severity)||D(e.path,t.path)||(e.line??0)-(t.line??0)||D(e.check,t.check)||D(e.code,t.code)}function X(e){return e==="error"?0:1}function D(e,t){return e===t?0:e<t?-1:1}var J=["duplicates","dangling","unowned"];function K(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(!G.includes(s))throw new Error(`Unknown check ${JSON.stringify(s)}. Expected one of: ${G.join(", ")}`);r.add(s)}return r}function Y(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 Z(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 E(e){let t="";for(let n of e){let r=n.codePointAt(0)??0;t+=Le(r)?`\\u${r.toString(16).padStart(4,"0")}`:n}return t}function Le(e){return e<=31||e>=127&&e<=159||e===1564||e===8206||e===8207||e>=8234&&e<=8238||e>=8294&&e<=8297}import{posix as bt,win32 as Ot}from"node:path";var fe=z(B(),1);import{isAbsolute as lt,relative as ut,resolve as ue,sep as dt}from"node:path";function w(e){return e.replaceAll("\\","/").replace(/^(?:\.\/)+/u,"").replace(/^\/+|\/+$/gu,"")}function de(e,t){let n=ue(e),r=ue(n,t);return M(n,r,t),r}function M(e,t,n=t){let r=ut(e,t);if(r===".."||r.startsWith(`..${dt}`)||lt(r))throw new Error(`Path must stay within the repository: ${n}`)}function I(e){return e.map(t=>{let n=(0,fe.default)({ignorecase:!1}).add(t.pattern);return{rule:t,matches:r=>n.ignores(r)}})}function he(e,t){let n=w(t);for(let r=e.length-1;r>=0;r-=1){let s=e[r];if(s?.matches(n))return s.rule}}function L(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=ft(i),a=o[0];if(a===void 0)continue;let l=o.slice(1);t.push({line:s,pattern:a,owners:l})}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 ft(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}import{spawn as ht}from"node:child_process";import{lstat as mt,readFile as pt,realpath as pe}from"node:fs/promises";import{relative as gt,resolve as ge}from"node:path";import{StringDecoder as wt}from"node:string_decoder";var yt=3*1024*1024,xt=[".github/CODEOWNERS","CODEOWNERS","docs/CODEOWNERS"];async function S(e,t){let n=ge(e),r=await pe(n),s=t===void 0?xt:[t];for(let o of s){let a=de(n,o),l=await Et(a,o);if(l!==void 0){let c=await pe(a);if(M(r,c,o),l.size>yt)throw new Error(`CODEOWNERS exceeds GitHub's 3 MiB limit: ${o}`);return{absolutePath:c,relativePath:w(gt(n,a)),source:await pt(c,"utf8")}}}let i=s.map(o=>JSON.stringify(o)).join(", ");throw new Error(`No CODEOWNERS file found at ${i}`)}async function we(e){let t=ge(e);return Ct(t)}async function Et(e,t){try{let n=await mt(e);if(n.isSymbolicLink())throw new Error(`CODEOWNERS must not be a symbolic link: ${t}`);return n.isFile()?n:void 0}catch(n){if(Rt(n)&&n.code==="ENOENT")return;throw n}}function Ct(e){return new Promise((t,n)=>{let r=ht("git",["-C",e,"ls-files","--cached","-z"],{stdio:["ignore","pipe","pipe"],windowsHide:!0}),s=new wt("utf8"),i=[],o="",a="",l=!1;r.stdout.on("data",c=>{o+=s.write(c);let d=o.indexOf("\0");for(;d!==-1;){let u=o.slice(0,d);u!==""&&i.push(w(u)),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=>{l=!0,n(new Error("Unable to start git while listing tracked files",{cause:c}))}),r.once("close",(c,d)=>{if(l)return;if(l=!0,o+=s.end(),c===0){o!==""&&i.push(w(o)),t(i.sort());return}let u=a.trim()||`git exited with ${c??d}`;n(new Error(`Unable to list tracked files: ${u}`))})})}function Rt(e){return e instanceof Error&&"code"in e}async function ye(e,t,n){let r=bt.normalize(t.replaceAll("\\","/"));if(Ot.isAbsolute(t)||/^[a-z]:/iu.test(t)||r==="."||r===".."||r.startsWith("../")||r.endsWith("/")||r.includes("\0"))throw new Error("--explain requires a repository-relative file path");let s=await S(e,n),i=I(L(s.source)),o=i.filter(c=>c.matches(r)).map(({rule:c})=>c),a=he(i,r)??null,l=a?.owners??[];return{path:r,codeownersPath:s.relativePath,matches:o,winner:a,owners:l,status:a===null?"unmatched":l.length===0?"cleared":"owned"}}function xe(e){let t=[`Ownership for ${e.path}`,`CODEOWNERS: ${e.codeownersPath}`,"Local rules only; syntax and owner validity are not checked."];for(let n of e.matches)t.push(`${e.codeownersPath}:${n.line} ${n.pattern} -> ${n.owners.join(" ")||"(no owners)"}${n.line===e.winner?.line?" [winner]":" [overridden]"}`);return t.push(e.status==="unmatched"?"Unowned: no matching rule.":e.status==="cleared"?"Unowned: the winning rule clears ownership.":`Effective owners: ${e.owners.join(" ")}`),t.map(E).join(`
|
|
3
|
+
`)}function Ee(e){let t=`${e.issueCount} issue${e.issueCount===1?"":"s"} in ${E(e.codeownersPath)}`,n=e.issues.map(i=>{let o=[E(i.path),i.line,i.column].filter(a=>a!==void 0).join(":");return`${i.severity.toUpperCase()} [${i.check}] ${o}: ${E(_t(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(`
|
|
4
|
+
`)}function _t(e){return e.suggestion===void 0?e.message:`${e.message} Suggestion: ${e.suggestion}`}var St=new Set([429,502,503,504]);async function Ce(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=Nt(e.apiUrl,r,s),a=e.ref?.trim();a!==void 0&&a!==""&&o.searchParams.set("ref",a);let l=new Headers({accept:"application/vnd.github+json","user-agent":"codeowners-guard"});e.token!==void 0&&e.token!==""&&l.set("authorization",`Bearer ${e.token}`);let c=await kt(o,l,e.repository,t,n),d=await $t(c);if(!At(d))throw new Error("GitHub returned an invalid CODEOWNERS error response");return d.errors.map(u=>{let f={check:"syntax",code:u.kind||"github-codeowners-error",severity:"error",path:u.path,line:u.line,column:u.column,message:u.message};return u.suggestion!==null&&u.suggestion!==""&&(f.suggestion=u.suggestion),f})}async function kt(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(!St.has(o.status)||i===3)throw await o.body?.cancel().catch(()=>{}),vt(o.status,n);await o.body?.cancel().catch(()=>{}),await s(Pt(o,i))}throw new Error("GitHub CODEOWNERS validation exhausted its retry budget")}function Pt(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 Nt(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 vt(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 $t(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 At(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 Re=z(B(),1);function be(e){let t=e.skipLines??new Set,n=L(e.source).filter(u=>!t.has(u.line)),r=e.checks.has("dangling"),s=e.checks.has("unowned"),i=r||s,o=i?I(n):[],a=i?It(e.files,e.exclude??[]):[],l=new Uint8Array(n.length),c=0,d=new R(e.maxIssues??1e3);if(e.checks.has("duplicates"))for(let u of me(n))d.add({check:"duplicates",code:"duplicate-pattern",severity:"warning",path:e.codeownersPath,line:u.line,message:u.message});for(let u of a){let f;if(i)for(let p=0;p<o.length;p+=1){let x=o[p];x?.matches(u)&&(l[p]===0&&(l[p]=1,c+=1),s&&(f=x.rule))}s&&(f===void 0||f.owners.length===0)&&d.add({check:"unowned",code:"unowned-file",severity:"warning",path:u,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 u=0;u<n.length;u+=1){let f=n[u];f!==void 0&&l[u]===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 It(e,t){let n=t.length===0?void 0:(0,Re.default)({ignorecase:!1}).add(t),r=new Set,s=[];for(let i of e){let o=w(i);o===""||r.has(o)||n?.ignores(o)||(r.add(o),s.push(o))}return s.sort()}async function Oe(e){let t=new Set([...e.checks].filter(u=>u!=="syntax")),n=await S(e.repositoryPath,e.codeownersPath);if(e.checks.has("syntax")&&e.codeownersPath!==void 0){let u=await S(e.repositoryPath);if(n.absolutePath!==u.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=Ce(e.github)}let i=r?we(e.repositoryPath):Promise.resolve([]),[o,a]=await Promise.all([s,i]),l=new Set(o.filter(u=>w(u.path)===n.relativePath).flatMap(u=>u.line===void 0?[]:[u.line])),c=be({source:n.source,codeownersPath:n.relativePath,files:a,checks:t,exclude:e.exclude??[],...e.maxIssues===void 0?{}:{maxIssues:e.maxIssues},skipLines:l}),d=new R(e.maxIssues??1e3);for(let u of o)d.add(u);return d.merge(c),{codeownersPath:n.relativePath,issues:d.issues,issueCount:d.issueCount,errorCount:d.errorCount,warningCount:d.warningCount,stats:c.stats}}var _e="0.1.3";async function Dt(){let{values:e,positionals:t}=Lt({allowPositionals:!0,strict:!0,options:{"api-url":{type:"string"},checks:{type:"string",short:"c"},codeowners:{type:"string"},exclude:{type:"string",multiple:!0},explain:{type:"string"},"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(Gt);return}if(e.version===!0){console.log(_e);return}let n=K(e.checks??"",J),r=Y(e["fail-on"]??"","warning"),s=Z(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"');if(e.explain!==void 0){for(let c of["checks","exclude","fail-on","max-issues","repository","ref","api-url"])if(e[c]!==void 0)throw new Error(`--explain cannot be combined with --${c}`);let l=await ye(t[0]??".",e.explain,e.codeowners);console.log(i==="json"?JSON.stringify(l,null,2):xe(l));return}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 Oe({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):Ee(a)),process.exitCode=q(a,r)?1:0}var Gt=`codeowners-guard [repository-path] [options]
|
|
4
5
|
|
|
5
6
|
Checks a repository's effective CODEOWNERS file.
|
|
6
7
|
|
|
@@ -8,6 +9,7 @@ Options:
|
|
|
8
9
|
-c, --checks <list> Comma-separated checks (default: duplicates,dangling,unowned)
|
|
9
10
|
--codeowners <path> Use a specific CODEOWNERS file for local checks
|
|
10
11
|
--exclude <pattern> Exclude files from local checks (repeatable)
|
|
12
|
+
--explain <path> Explain local ownership of a repository-relative file
|
|
11
13
|
--fail-on <severity> Failure threshold: warning or error (default: warning)
|
|
12
14
|
--max-issues <count> Maximum retained issue details (default: 1000, max: 10000)
|
|
13
15
|
-f, --format <format> Output format: text or json (default: text)
|
|
@@ -18,5 +20,5 @@ Options:
|
|
|
18
20
|
-h, --help Show this help
|
|
19
21
|
|
|
20
22
|
Environment:
|
|
21
|
-
GITHUB_TOKEN or GH_TOKEN Token for authenticated GitHub API access`;
|
|
23
|
+
GITHUB_TOKEN or GH_TOKEN Token for authenticated GitHub API access`;Dt().catch(e=>{console.error(E(e instanceof Error?e.message:String(e))),process.exitCode=2});
|
|
22
24
|
//# sourceMappingURL=cli.js.map
|