deployhealth-scan 0.3.1 → 0.3.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 +15 -6
- package/dist/deployhealth-scan.mjs +20 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,6 +12,14 @@ It scans JavaScript/TypeScript (`.js`, `.jsx`, `.mjs`, `.cjs`, `.ts`, `.tsx`, `.
|
|
|
12
12
|
Python (including pydantic-settings classes), Go and Ruby, respects `.gitignore`, and treats each
|
|
13
13
|
folder with its own env files as a separate scope (monorepos). One file, no dependencies, Node 20+.
|
|
14
14
|
|
|
15
|
+
Reads JS/TS, Python, Go and Ruby. Java/Kotlin, PHP, Rust, C# and others aren't read yet: on a repo in those, the pull request check and the CLI say they can't check it instead of passing it.
|
|
16
|
+
|
|
17
|
+
On a directory with no such source file (after the rules below), it prints
|
|
18
|
+
`No JS/TS, Python, Go or Ruby source files found: deployhealth can't check this directory yet.`,
|
|
19
|
+
reports no UNUSED variables (nothing it reads could use them), still reports MISMATCH, and exits
|
|
20
|
+
`0`. With `--json` the line goes to stderr, and the JSON says `"can_check": false` with
|
|
21
|
+
`"source_files": 0`.
|
|
22
|
+
|
|
15
23
|
Env files it reads: `.env`, `.env.local`, `.env.development`, `.env.production`, `.env.test` and
|
|
16
24
|
their `.local` variants, plus **declaration files**: `.env.example`, `.env.sample`,
|
|
17
25
|
`.env.template`, `.env.dist`, `.env.defaults`, `example.env`, `sample.env`, `env.example` and
|
|
@@ -77,16 +85,17 @@ Same-line, regex-based scanning misses some things on purpose:
|
|
|
77
85
|
## Try it locally
|
|
78
86
|
|
|
79
87
|
```sh
|
|
80
|
-
npx deployhealth-scan@0.3.
|
|
81
|
-
npx deployhealth-scan@0.3.
|
|
88
|
+
npx deployhealth-scan@0.3.2 --dry-run # grouped findings with file:line
|
|
89
|
+
npx deployhealth-scan@0.3.2 --dry-run --json # the same, as JSON
|
|
82
90
|
```
|
|
83
91
|
|
|
84
92
|
With `--dry-run` nothing leaves your machine.
|
|
85
93
|
|
|
86
94
|
## Report every push from GitHub Actions
|
|
87
95
|
|
|
88
|
-
Create a project in deployhealth, add its ingest token as
|
|
89
|
-
`DEPLOYHEALTH_TOKEN
|
|
96
|
+
Create a project in deployhealth, add its ingest token as a repository secret named
|
|
97
|
+
`DEPLOYHEALTH_TOKEN` (Settings → Secrets and variables → Actions → Secrets tab → New repository
|
|
98
|
+
secret; not a Variable, not an environment secret), then add `.github/workflows/deployhealth.yml`:
|
|
90
99
|
|
|
91
100
|
```yaml
|
|
92
101
|
name: deployhealth
|
|
@@ -113,14 +122,14 @@ jobs:
|
|
|
113
122
|
env:
|
|
114
123
|
DEPLOYHEALTH_TOKEN: ${{ secrets.DEPLOYHEALTH_TOKEN }}
|
|
115
124
|
run: |
|
|
116
|
-
npx --yes deployhealth-scan@0.3.
|
|
125
|
+
npx --yes deployhealth-scan@0.3.2 \
|
|
117
126
|
--url https://deployhealth.dev \
|
|
118
127
|
--token "$DEPLOYHEALTH_TOKEN" \
|
|
119
128
|
--sha "$GITHUB_SHA" \
|
|
120
129
|
--branch "$GITHUB_REF_NAME"
|
|
121
130
|
```
|
|
122
131
|
|
|
123
|
-
Pin the version (`@0.3.
|
|
132
|
+
Pin the version (`@0.3.2`) so an update never runs in your CI unreviewed. For a self-hosted
|
|
124
133
|
deployhealth, change `--url` to your instance.
|
|
125
134
|
|
|
126
135
|
## What it sends
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{execFileSync as
|
|
3
|
-
`);return{value:
|
|
4
|
-
`);return{value:
|
|
5
|
-
`;case"r":return"\r";case"t":return" ";default:return t}})}function qe(e){let n=/(^|\s)#/.exec(e);return n?e.slice(0,n.index):e}var Xe=[".env.example",".env",".env.local",".env.development",".env.development.local",".env.production",".env.production.local",".env.test",".env.test.local"],ue=[".env.sample",".env.template",".env.dist",".env.defaults","example.env","sample.env","env.example"],fe=/^\.env\.[A-Za-z0-9_-]{1,64}\.(?:example|sample|template)$/;var W=[...Xe,...ue],Je=new Set(W),Qe=new Set([".env.example",...ue]);function $(e){return Je.has(e)||fe.test(e)}function pe(e){return Qe.has(e)||fe.test(e)}function en(e,n){let t=s=>{let r=W.indexOf(s);return r===-1?W.length:r};return t(e)-t(n)||(e<n?-1:e>n?1:0)}function H(e){return[...new Set(e)].sort(en)}var de=/^[0-9a-f]{7,64}$/i;function Ee({references:e,envFiles:n,isIgnored:t,usedOutsideCode:s}){let r=[],a=new Set(n.flatMap(c=>[...c.entries,...c.commented??[]].map(u=>u.key))),o=new Set([...e.map(c=>c.name),...s??[]]);for(let c of n.length===0?[]:e)t(c.name)||c.hasDefault||a.has(c.name)||r.push({kind:"missing",var_name:c.name,file:c.file,line:c.line,env_file:null});for(let c of n)for(let{key:u,line:p}of ge(c.entries))t(u)||o.has(u)||r.push({kind:"unused",var_name:u,file:c.path,line:p,env_file:c.path});let i=n.find(c=>c.name===".env"),l=n.find(c=>c.name===".env.example");if(i&&l)for(let[c,u]of[[i,l],[l,i]]){let p=new Set(u.entries.map(d=>d.key));for(let{key:d,line:E}of ge(c.entries))t(d)||p.has(d)||r.push({kind:"mismatch",var_name:d,file:c.path,line:E,env_file:u.path})}return r}function ge(e){let n=new Set;return e.filter(t=>n.has(t.key)?!1:(n.add(t.key),!0))}var me={missing:0,unused:1,mismatch:2};function _e({scope:e,references:n,envFiles:t,isIgnored:s}){let r=new Map;for(let i of n)r.set(i.name,(r.get(i.name)??!0)&&i.hasDefault===!0);let a=new Map;for(let i of t)for(let l of new Set([...i.entries,...i.commented??[]].map(c=>c.key))){let c=a.get(l);c?c.push(i.name):a.set(l,[i.name])}return[...r.keys()].filter(i=>!s(i)).sort().map(i=>{let l={var_name:i,scope:e,defined_in:H(a.get(i)??[])};return r.get(i)&&(l.optional=!0),l})}function ye(e,n){return me[e.kind]-me[n.kind]||he(e.var_name,n.var_name)||he(e.file??"",n.file??"")||(e.line??0)-(n.line??0)}function Se(e){let n={missing:new Set,unused:new Set,mismatch:new Set};for(let t of e)n[t.kind].add(t.var_name);return{missing:n.missing.size,unused:n.unused.size,mismatch:n.mismatch.size}}function he(e,n){return e<n?-1:e>n?1:0}function nn(e){let n="";for(let t of e)t==="*"?n+=".*":t==="?"?n+=".":n+=t.replace(/[.+^${}()|[\]\\]/g,"\\$&");return new RegExp(`^${n}$`)}function z(e){let n=e.map(nn);return t=>n.some(s=>s.test(t))}import{extname as fn}from"path";function tn(e){let n=e.split(/\r?\n/),t=[],s=null,r=0,a=null;for(let o=0;o<n.length;o++){let i=n[o];if(!s){if(i.trim()==="")continue;s={line:o+1,indent:i.length-i.trimStart().length,code:""}}let l="";for(let u=0;u<i.length;){let p=i[u];if(a){p==="\\"?(l+=i.slice(u,u+2),u+=2):i.startsWith(a,u)?(l+=a,u+=a.length,a=null):(l+=p,u++);continue}if(p==="#")break;if(p==='"'||p==="'"){a=i.startsWith(p.repeat(3),u)?p.repeat(3):p,l+=a,u+=a.length;continue}"([{".includes(p)?r++:")]}".includes(p)&&(r=Math.max(0,r-1)),l+=p,u++}let c=!a&&l.trimEnd().endsWith("\\");c&&(l=l.trimEnd().slice(0,-1)),a?.length===1&&(a=null),s.code=s.code?`${s.code} ${l.trim()}`:l.trim(),r===0&&!a&&!c&&(s.code&&t.push(s),s=null)}return s?.code&&t.push(s),t}function G(e,n){let t=[],s=0,r=null,a=0;for(let o=0;o<e.length;o++){let i=e[o];if(r){i==="\\"?o++:e.startsWith(r,o)&&(o+=r.length-1,r=null);continue}if(i==='"'||i==="'")r=e.startsWith(i.repeat(3),o)?i.repeat(3):i;else if("([{".includes(i))s++;else if(")]}".includes(i))s--;else if(i===n&&s===0){if(n==="="&&("=!<>:".includes(e[o-1]??"")||e[o+1]==="="))continue;t.push(e.slice(a,o)),a=o+1}}return t.push(e.slice(a)),t.map(o=>o.trim())}function V(e,n){let t=0;for(let s=n;s<e.length;s++)if(e[s]==="(")t++;else if(e[s]===")"&&--t===0)return e.slice(n+1,s);return null}var sn=/^class\s+([A-Za-z_]\w*)\s*(\(?)/;function rn(e){let n=[],t=[];for(let s=0;s<e.length;s++){let r=e[s];for(;t.length>0&&r.indent<=t[t.length-1].indent;)t.pop().cls.end=s;let a=t[t.length-1];a&&(a.bodyIndent??=r.indent,r.indent===a.bodyIndent&&a.cls.direct.push(s));let o=sn.exec(r.code);if(!o)continue;let i=o[2]?V(r.code,o[0].length-1)??"":"",l=G(i,",").filter(u=>u&&!u.includes("=")).map(u=>u.replace(/\[.*$/s,"").split(".").pop().trim()),c={name:o[1],bases:l,header:s,end:e.length,direct:[]};n.push(c),t.push({cls:c,indent:r.indent,bodyIndent:null})}return n}var on=/^([A-Za-z_]\w*)\s*:(?!=)\s*(.+)$/s,an=/^(?:[A-Za-z_]\w*\.)*Field\s*\(/,ve=/^[rRbBuU]?(['"])([A-Za-z_][A-Za-z0-9_]*)\1$/,cn=/^[A-Za-z_][A-Za-z0-9_]*$/;function xe(e,n,t=new Set){if(!e.includes("BaseSettings")&&t.size===0)return[];let s=tn(e),r=rn(s),a=new Map;for(let h of r)for(let y of h.bases){let x=a.get(y);x?x.push(h):a.set(y,[h])}let o=new Set,i=["BaseSettings",...t],l=new Set(i);for(let h=0;h<i.length;h++)for(let y of a.get(i[h])??[])o.add(y),l.has(y.name)||(l.add(y.name),i.push(y.name));let c=new Map;for(let h of r)o.has(h)&&!c.has(h.name)&&c.set(h.name,h);if(c.size===0)return[];let u=new Set([...c.keys(),...r.filter(h=>h.bases.includes("BaseModel")).map(h=>h.name)]),p=h=>{let y=new Int32Array(s.length+1).fill(s.length);for(let x=s.length-1;x>=0;x--)y[x]=h.test(s[x].code)?x:y[x+1];return y},d=p(/\benv_prefix\s*=\s*[rRuU]?(['"])([A-Za-z0-9_]*)\1/),E=p(/\bcase_sensitive\s*=\s*True\b/),T=h=>{let y=d[h.header];return y<h.end?/\benv_prefix\s*=\s*[rRuU]?(['"])([A-Za-z0-9_]*)\1/.exec(s[y].code)[2]:null},_=new Map,m=h=>{let y=[],x=new Set,b="";for(let S=h;S;){let L=_.get(S);if(L!==void 0){b=L;break}y.push(S),x.add(S);let I=T(S);if(I!==null){b=I;break}S=S.bases.map(F=>c.get(F)).find(F=>F&&!x.has(F))}for(let S of y)_.set(S,b);return b},v=[];for(let h of c.values()){if(h.direct.length===0)continue;let y=m(h),x=E[h.header+1]<h.end;for(let b of h.direct){let S=s[b],L=on.exec(S.code);if(!L)continue;let[,I,F]=L;if(I.startsWith("_")||I==="model_config")continue;let[C="",f]=G(F,"=");if(/^(?:typing\.)?ClassVar\b/.test(C)||ln(C,u))continue;let{value:g,aliases:R}=un(f),N=/\bNone\b|\bOptional\s*\[/.test(C),k=g!==null&&(g!=="None"||N),O=S.indent+1;if(R.length>0){let Me=R.length>1;for(let se of R){let re={name:x?se:se.toUpperCase(),file:n,line:S.line,column:O,syntax:"BaseSettings"};(k||Me)&&(re.hasDefault=!0),v.push(re)}continue}let U=x?`${y}${I}`:`${y}${I}`.toUpperCase();if(!cn.test(U))continue;let te={name:U,file:n,line:S.line,column:O,syntax:"BaseSettings"};k&&(te.hasDefault=!0),v.push(te)}}return v}function ln(e,n){let t=e.replace(/^(?:typing\.)?Optional\s*\[(.*)\]$/s,"$1").split("|").map(s=>s.trim()).filter(s=>s!=="None");return t.length===1&&n.has(t[0].replace(/^['"]|['"]$/g,""))}function un(e){if(e===void 0||e==="")return{value:null,aliases:[]};let n=an.exec(e);if(!n)return{value:e,aliases:[]};let t=G(V(e,n[0].length-1)??"",",").filter(Boolean),s=null,r=[];for(let[a,o]of t.entries()){let i=/^([A-Za-z_]\w*)\s*=(?!=)\s*(.*)$/s.exec(o);if(!i){a===0&&o!=="..."&&o!=="Ellipsis"&&(s=o);continue}let[,l,c]=i;if(l==="default")s=c==="..."||c==="Ellipsis"?null:c;else if(l==="default_factory")s="factory";else if(l==="alias"||l==="validation_alias"||l==="env"){let u=ve.exec(c);u&&r.push(u[2]);let p=/^(?:[A-Za-z_]\w*\.)*AliasChoices\s*\(/.exec(c);if(p)for(let d of G(V(c,p[0].length-1)??"",",")){let E=ve.exec(d);E&&r.push(E[2])}}}return{value:s,aliases:[...new Set(r)]}}var Te={".ts":"javascript",".tsx":"javascript",".mts":"javascript",".cts":"javascript",".js":"javascript",".jsx":"javascript",".mjs":"javascript",".cjs":"javascript",".py":"python",".go":"go",".rb":"ruby"},be=new Set(Object.keys(Te)),Z="(?<name>[A-Za-z_][A-Za-z0-9_]*)",w=e=>`(?<q>[${e}])${Z}\\k<q>`,B=/^\s*(?:\?\?|\|\|)=?\s*(?!(?:undefined|null)\b)\S/,Re=/^\s*(?:,|\)\s*or\b)\s*(?!None\b)[^\s)]/,pn=/^\s*(?:,\s*(?!nil\b)\S|\)\s*(?:\{|do\b))/,dn=/^\s*\|\|\s*(?!nil\b)\S/,gn={javascript:[{syntax:"process.env",regex:new RegExp(`\\bprocess\\.env(?:\\?\\.|\\.)${Z}\\b`,"g"),defaultAfter:B},{syntax:"process.env",regex:new RegExp(`\\bprocess\\.env(?:\\?\\.)?\\[\\s*${w("'\"`")}\\s*\\]`,"g"),defaultAfter:B},{syntax:"import.meta.env",regex:new RegExp(`\\bimport\\.meta\\.env(?:\\?\\.|\\.)${Z}\\b`,"g"),defaultAfter:B},{syntax:"import.meta.env",regex:new RegExp(`\\bimport\\.meta\\.env(?:\\?\\.)?\\[\\s*${w("'\"`")}\\s*\\]`,"g"),defaultAfter:B}],python:[{syntax:"os.environ",regex:new RegExp(`\\bos\\.environ\\s*\\[\\s*${w(`'"`)}\\s*\\]`,"g")},{syntax:"os.environ",regex:new RegExp(`\\bos\\.environ\\.get\\s*\\(\\s*${w(`'"`)}`,"g"),defaultAfter:Re},{syntax:"os.getenv",regex:new RegExp(`\\bos\\.getenv\\s*\\(\\s*${w(`'"`)}`,"g"),defaultAfter:Re}],go:[{syntax:"os.Getenv",regex:new RegExp(`\\bos\\.Getenv\\s*\\(\\s*${w('"`')}\\s*\\)`,"g")},{syntax:"os.LookupEnv",regex:new RegExp(`\\bos\\.LookupEnv\\s*\\(\\s*${w('"`')}\\s*\\)`,"g")}],ruby:[{syntax:"ENV",regex:new RegExp(`\\bENV\\s*\\[\\s*${w(`'"`)}\\s*\\]`,"g"),defaultAfter:dn},{syntax:"ENV",regex:new RegExp(`\\bENV\\.fetch(?:\\s*\\(\\s*|\\s+)${w(`'"`)}`,"g"),defaultAfter:pn}]};function Y(e){return Te[fn(e).toLowerCase()]}function q(e,n,t){let s=[],r=e.split(/\r?\n/);for(let a=0;a<r.length;a++){let o=r[a];for(let{syntax:i,regex:l,defaultAfter:c}of gn[n]){l.lastIndex=0;for(let u=l.exec(o);u;u=l.exec(o)){let p=u.groups?.name;if(!p||yn(o,u.index,u.index+u[0].length))continue;let d={name:p,file:t,line:a+1,column:u.index+1,syntax:i};c?.test(o.slice(u.index+u[0].length))&&(d.hasDefault=!0),s.push(d)}}if(n==="javascript")for(let i of En(o,a+1,t))s.push(i)}if(n==="python")for(let a of xe(e,t))s.push(a);return Sn(s).sort((a,o)=>a.line-o.line||a.column-o.column)}var K=/\{([^{}]*)\}\s*(?::[^=;{}]+)?=\s*(process\.env|import\.meta\.env)\b(?!\s*\.|\s*\[|\s*\?\.)/g,mn=/^\s*(?:(['"])([A-Za-z_][A-Za-z0-9_]*)\1|([A-Za-z_][A-Za-z0-9_]*))\s*(?::\s*[A-Za-z_$][\w$]*\s*)?(=\s*(.*))?$/s,hn=/^(?!(?:undefined|null)\b)\S/;function En(e,n,t){let s=[];K.lastIndex=0;for(let r=K.exec(e);r;r=K.exec(e)){let a=r[2]==="process.env"?"process.env":"import.meta.env",o=r.index+1;for(let i of r[1].split(",")){let l=mn.exec(i),c=l?.[2]??l?.[3];if(l&&c){let u={name:c,file:t,line:n,column:o+i.indexOf(c)+1,syntax:a};l[4]&&hn.test(l[5].trim())&&(u.hasDefault=!0),s.push(u)}o+=i.length+1}}return s}var _n=new Set(['"',"'","`"]);function yn(e,n,t){let s=e[n-1];return s!==void 0&&_n.has(s)&&e[t]===s}function Sn(e){let n=new Map;for(let t of e.slice().sort((s,r)=>s.line-r.line||s.column-r.column)){let s=`${t.line}\0${t.name}`,r=n.get(s);r?t.hasDefault||delete r.hasDefault:n.set(s,{...t})}return[...n.values()]}var Ie=new Set(["test","tests","__tests__","spec","e2e","fixtures","__fixtures__","testdata","playwright","cypress","mocks","__mocks__","testing"]),vn=[/.\.test\./,/.\.spec\./,/.\.e2e\./,/.\.e2e-spec\./,/.\.cy\./,/_test\.go$/,/^test_.*\.py$/,/_test\.py$/,/^conftest\.py$/,/_spec\.rb$/,/^(?:playwright|vitest|jest|cypress)\.config\./,/^vitest\.workspace\./,/^(?:vitest|jest)\.setup\./];function Ne(e){return!$(e)&&vn.some(n=>n.test(e))}var X=new Set(["node_modules","dist",".next","venv",".venv",".yarn","vendor","third_party","bower_components","out","coverage",".turbo",".vercel",".output",".svelte-kit",".nuxt",".cache",".pnpm-store","__pycache__","site-packages"]);function J(e){return e===".pnp.cjs"||e===".pnp.loader.mjs"||/\.min\.(?:js|mjs|cjs)$/.test(e)}var Q=512*1024;import{readdir as we,readFile as On}from"fs/promises";import{join as ee}from"path";var P=class e{constructor(n){this.rules=n}rules;static empty(){return new e([])}extend(n,t){let s=xn(t).map(r=>({...r,base:n}));return s.length===0?this:new e([...this.rules,...s])}ignores(n,t){let s=!1;for(let r of this.rules){if(r.dirOnly&&!t||s!==r.negate)continue;let a=Fn(r.base,n);a===null||!r.glob||Nn(r.glob,a)&&(s=!r.negate)}return s}};function xn(e){let n=[];for(let t of e.split(/\r?\n/)){if(n.length>=1e3)break;if(t.length>1024)continue;let s=Rn(t);s&&n.push(s)}return n}function Rn(e){let n=Tn(e);if(n===""||n.startsWith("#"))return null;let t=!1;n.startsWith("!")?(t=!0,n=n.slice(1)):(n.startsWith("\\!")||n.startsWith("\\#"))&&(n=n.slice(1));let s=!1;if(n.endsWith("/")&&(s=!0,n=n.replace(/\/+$/,"")),n==="")return null;let r=n.includes("/");return n=n.replace(/^\/+/,""),!r&&!n.startsWith("**/")&&(n=`**/${n}`),{glob:In(n),negate:t,dirOnly:s,source:e}}function Tn(e){let n=e.length;for(;n>0&&e[n-1]===" "&&e[n-2]!=="\\";)n--;return e.slice(0,n)}var ke=e=>e!=="/",Ae=()=>!0,bn=e=>e==="/";function In(e){let n=An(e);if(!n)return null;let t=["accept"],s=0;for(let o=n.length-1;o>=0;o--){let i=n[o],l=t.length;switch(i.kind){case"literal":{let c=i.ch;t.push({test:u=>u===c,next:s});break}case"one":t.push({test:i.test,next:s});break;case"star":case"rest":t.push({a:l+1,b:s},{test:i.kind==="star"?ke:Ae,next:l});break;case"dirs":t.push({a:l+1,b:s},{a:l+2,b:l+3},{test:Ae,next:l+1},{test:bn,next:s});break}s=l}let r="";for(let o of n){if(o.kind!=="literal")break;r+=o.ch}let a="";for(let o=n.length-1;o>=0;o--){let i=n[o];if(i.kind!=="literal")break;a=i.ch+a}return{states:t,start:s,prefix:r,suffix:a}}function Nn(e,n){if(!n.startsWith(e.prefix)||!n.endsWith(e.suffix))return!1;let{states:t}=e,s=new Int32Array(t.length).fill(-1),r=[],a=0,o=(i,l)=>{let c=[l];for(;c.length>0;){let u=c.pop();if(s[u]===a)continue;s[u]=a;let p=t[u];typeof p=="object"&&"a"in p?c.push(p.b,p.a):i.push(u)}};o(r,e.start);for(let i=0;i<n.length&&r.length>0;i++){let l=n[i];a++;let c=[];for(let u of r){let p=t[u];typeof p=="object"&&"test"in p&&p.test(l)&&o(c,p.next)}r=c}return r.some(i=>t[i]==="accept")}function An(e){let n=[],t=0;for(;t<e.length;){let s=e[t];if(s==="*"){if(e[t+1]==="*"){let r=t===0,a=r||e[t-1]==="/",o=e[t+2]==="/",i=t+2===e.length;if(a&&o){n.push({kind:"dirs"}),t+=3;continue}if(a&&i&&!r){n.push({kind:"rest"}),t+=2;continue}n.push({kind:"star"}),t+=2;continue}n.push({kind:"star"}),t++;continue}if(s==="?"){n.push({kind:"one",test:ke}),t++;continue}if(s==="["){let r=wn(e,t);if(!r)return null;n.push({kind:"one",test:r.test}),t=r.end;continue}if(s==="\\"&&t+1<e.length){n.push({kind:"literal",ch:e[t+1]}),t+=2;continue}n.push({kind:"literal",ch:s}),t++}return n}var kn={alnum:e=>/[A-Za-z0-9]/.test(e),alpha:e=>/[A-Za-z]/.test(e),blank:e=>e===" "||e===" ",cntrl:e=>e.charCodeAt(0)<32||e.charCodeAt(0)===127,digit:e=>e>="0"&&e<="9",graph:e=>e.charCodeAt(0)>32&&e.charCodeAt(0)<127,lower:e=>e>="a"&&e<="z",print:e=>e.charCodeAt(0)>=32&&e.charCodeAt(0)<127,punct:e=>/[!-/:-@[-`{-~]/.test(e),space:e=>/[ \t\n\r\f\v]/.test(e),upper:e=>e>="A"&&e<="Z",xdigit:e=>/[0-9A-Fa-f]/.test(e)};function wn(e,n){let t=n+1,s=e[t]==="!"||e[t]==="^";s&&t++;let r=[];for(let o=!0;;o=!1){if(t>=e.length)return null;let i=e[t];if(i==="]"&&!o)break;if(i==="["&&e[t+1]===":"){let c=e.indexOf(":]",t+2);if(c!==-1){let u=kn[e.slice(t+2,c)];if(!u)return null;r.push(u),t=c+2;continue}}if(i==="\\"){if(t++,t>=e.length)return null;i=e[t]}if(e[t+1]==="-"&&t+2<e.length&&e[t+2]!=="]"){let c=t+2;if(e[c]==="\\"&&c++,c>=e.length)return null;let[u,p]=[i,e[c]];r.push(d=>d>=u&&d<=p),t=c+1;continue}let l=i;r.push(c=>c===l),t++}return{test:o=>o!=="/"&&r.some(i=>i(o))!==s,end:t+1}}function Fn(e,n){return e===""?n:n.startsWith(`${e}/`)?n.slice(e.length+1):null}var Fe=new Set([".git",...X]);async function Oe(e,n){let t=n.skipDirs??Fe,s=n.respectGitignore??!0,r=[],a=[],o=[],i=[],l=P.empty().extend("",(n.exclude??[]).join(`
|
|
6
|
-
`));async function
|
|
2
|
+
import{execFileSync as Vn}from"child_process";import{resolve as Kn}from"path";import{parseArgs as Zn}from"util";import{readFile as Bn,stat as Mn}from"fs/promises";import{join as Ue,posix as se}from"path";function j(e){return/^(?:docker-)?compose(?:[.-][A-Za-z0-9_.-]+)?\.ya?ml$/.test(e)}var Ke=/\$(?:\{([A-Za-z_][A-Za-z0-9_]*)(?=[}:?+-])|([A-Za-z_][A-Za-z0-9_]*))/g;function ae(e){let n=new Set;for(let t of e.split(/\r?\n/)){if(t.trimStart().startsWith("#"))continue;let r=t.replace(/\$\$/g,"");for(let s of r.matchAll(Ke))n.add(s[1]??s[2])}return n}var ce=["NODE_ENV","NEXT_RUNTIME","npm_*","HOME","PATH","PWD","USER","SHELL","TMPDIR","HOSTNAME","CI","GITHUB_ACTION","GITHUB_ACTION_PATH","GITHUB_ACTION_REPOSITORY","GITHUB_ACTIONS","GITHUB_ACTOR","GITHUB_ACTOR_ID","GITHUB_API_URL","GITHUB_ARTIFACTS","GITHUB_ARTIFACTS_LIST","GITHUB_BASE_REF","GITHUB_ENV","GITHUB_EVENT_NAME","GITHUB_EVENT_PATH","GITHUB_GRAPHQL_URL","GITHUB_HEAD_REF","GITHUB_JOB","GITHUB_OUTPUT","GITHUB_PATH","GITHUB_REF","GITHUB_REF_NAME","GITHUB_REF_PROTECTED","GITHUB_REF_TYPE","GITHUB_REPOSITORY","GITHUB_REPOSITORY_ID","GITHUB_REPOSITORY_OWNER","GITHUB_REPOSITORY_OWNER_ID","GITHUB_RETENTION_DAYS","GITHUB_RUN_ATTEMPT","GITHUB_RUN_ID","GITHUB_RUN_NUMBER","GITHUB_SERVER_URL","GITHUB_SHA","GITHUB_STEP_SUMMARY","GITHUB_TRIGGERING_ACTOR","GITHUB_WORKFLOW","GITHUB_WORKFLOW_REF","GITHUB_WORKFLOW_SHA","GITHUB_WORKSPACE","RUNNER_*","VERCEL","VERCEL_*","NEXT_PUBLIC_VERCEL_*","RAILWAY_*","RENDER","RENDER_*","FLY_*"];import{readFile as st}from"fs/promises";var Ze=/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_.-]*)\s*=(.*)$/,Ye=/^\s*#+\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/,qe=new Set(['"',"'","`"]),le="-----BEGIN ",Xe="-----END ";function pe(e){let n=e.replace(/^\uFEFF/,"").split(/\r?\n/),t=[],r=[],s=[],a=null,o=i=>{for(let c=i+1;c<n.length;c++)if((n[c]??"").trim().startsWith(Xe))return c;return-1};for(let i=0;i<n.length;i++){let c=n[i]??"",l=c.trim();if(l.startsWith("#")){let d=Ye.exec(c)?.[1];d&&r.push({key:d,line:i+1});continue}if(l==="")continue;if(l.startsWith(le)){let d=o(i);if(d===-1){a={line:i+1,kind:"block"};break}i=d;continue}let u=Ze.exec(c);if(!u){s.push({line:i+1,text:c});continue}let f=u[1],g=(u[2]??"").trimStart(),E=i+1,v=g[0];if(v!==void 0&&qe.has(v)){let d=Je(n,i,g,v);if(t.push({key:f,value:d.value,line:E}),d.endIndex===-1){a={line:E,kind:"quote"};break}i=d.endIndex;continue}let T=en(g).trim();if(t.push({key:f,value:T,line:E}),T.startsWith(le)){let d=o(i);if(d===-1){a={line:E,kind:"block"};break}i=d}}return{entries:t,commented:r,invalid:s,unterminated:a}}function Je(e,n,t,r){let s=[],a=t.slice(1);for(let o=n;;){let i=Qe(a,r);if(i!==-1){s.push(a.slice(0,i));let c=s.join(`
|
|
3
|
+
`);return{value:r==='"'?ue(c):c,endIndex:o}}if(s.push(a),o++,o>=e.length){let c=s.join(`
|
|
4
|
+
`);return{value:r==='"'?ue(c):c,endIndex:-1}}a=e[o]??""}}function Qe(e,n){for(let t=0;t<e.length;t++){let r=e[t];if(r==="\\"){t++;continue}if(r===n)return t}return-1}function ue(e){return e.replace(/\\([nrt"\\])/g,(n,t)=>{switch(t){case"n":return`
|
|
5
|
+
`;case"r":return"\r";case"t":return" ";default:return t}})}function en(e){let n=/(^|\s)#/.exec(e);return n?e.slice(0,n.index):e}var nn=[".env.example",".env",".env.local",".env.development",".env.development.local",".env.production",".env.production.local",".env.test",".env.test.local"],fe=[".env.sample",".env.template",".env.dist",".env.defaults","example.env","sample.env","env.example"],de=/^\.env\.[A-Za-z0-9_-]{1,64}\.(?:example|sample|template)$/;var V=[...nn,...fe],tn=new Set(V),sn=new Set([".env.example",...fe]);function P(e){return tn.has(e)||de.test(e)}function ge(e){return sn.has(e)||de.test(e)}function rn(e,n){let t=r=>{let s=V.indexOf(r);return s===-1?V.length:s};return t(e)-t(n)||(e<n?-1:e>n?1:0)}function G(e){return[...new Set(e)].sort(rn)}var me=/^[0-9a-f]{7,64}$/i;function ye({references:e,envFiles:n,isIgnored:t,usedOutsideCode:r}){let s=[],a=new Set(n.flatMap(l=>[...l.entries,...l.commented??[]].map(u=>u.key))),o=new Set([...e.map(l=>l.name),...r??[]]);for(let l of n.length===0?[]:e)t(l.name)||l.hasDefault||a.has(l.name)||s.push({kind:"missing",var_name:l.name,file:l.file,line:l.line,env_file:null});for(let l of n)for(let{key:u,line:f}of he(l.entries))t(u)||o.has(u)||s.push({kind:"unused",var_name:u,file:l.path,line:f,env_file:l.path});let i=n.find(l=>l.name===".env"),c=n.find(l=>l.name===".env.example");if(i&&c)for(let[l,u]of[[i,c],[c,i]]){let f=new Set(u.entries.map(g=>g.key));for(let{key:g,line:E}of he(l.entries))t(g)||f.has(g)||s.push({kind:"mismatch",var_name:g,file:l.path,line:E,env_file:u.path})}return s}function he(e){let n=new Set;return e.filter(t=>n.has(t.key)?!1:(n.add(t.key),!0))}var Ee={missing:0,unused:1,mismatch:2};function Se({scope:e,references:n,envFiles:t,isIgnored:r}){let s=new Map;for(let i of n)s.set(i.name,(s.get(i.name)??!0)&&i.hasDefault===!0);let a=new Map;for(let i of t)for(let c of new Set([...i.entries,...i.commented??[]].map(l=>l.key))){let l=a.get(c);l?l.push(i.name):a.set(c,[i.name])}return[...s.keys()].filter(i=>!r(i)).sort().map(i=>{let c={var_name:i,scope:e,defined_in:G(a.get(i)??[])};return s.get(i)&&(c.optional=!0),c})}function xe(e,n){return Ee[e.kind]-Ee[n.kind]||_e(e.var_name,n.var_name)||_e(e.file??"",n.file??"")||(e.line??0)-(n.line??0)}function Re(e){let n={missing:new Set,unused:new Set,mismatch:new Set};for(let t of e)n[t.kind].add(t.var_name);return{missing:n.missing.size,unused:n.unused.size,mismatch:n.mismatch.size}}function _e(e,n){return e<n?-1:e>n?1:0}function on(e){let n="";for(let t of e)t==="*"?n+=".*":t==="?"?n+=".":n+=t.replace(/[.+^${}()|[\]\\]/g,"\\$&");return new RegExp(`^${n}$`)}function K(e){let n=e.map(on);return t=>n.some(r=>r.test(t))}import{extname as yn}from"path";var H=[{id:"javascript",label:"JS/TS",extensions:[".ts",".tsx",".mts",".cts",".js",".jsx",".mjs",".cjs"]},{id:"python",label:"Python",extensions:[".py"]},{id:"go",label:"Go",extensions:[".go"]},{id:"ruby",label:"Ruby",extensions:[".rb"]}];function ve(e,n){return e.length<=1?e.join(""):`${e.slice(0,-1).join(", ")} ${n} ${e.at(-1)}`}var Te=H.map(e=>e.label),an=ve(Te,"or"),cn=ve(Te,"and"),ln=[".java",".kt",".kts",".scala",".groovy",".properties",".php",".rs",".cs",".ex",".exs",".swift",".dart",".c",".cpp",".h",".m",".sh",".lua",".pl",".r"],un="Java/Kotlin, PHP, Rust, C# and others";var pt=`Reads ${cn}. ${un} aren't read yet: on a repo in those, the pull request check and the CLI say they can't check it instead of passing it.`,B=`No ${an} source files found: deployhealth can't check this directory yet.`,ft=new Map(H.flatMap(e=>e.extensions.map(n=>[n,e]))),dt=new Set(ln);function pn(e){let n=e.split(/\r?\n/),t=[],r=null,s=0,a=null;for(let o=0;o<n.length;o++){let i=n[o];if(!r){if(i.trim()==="")continue;r={line:o+1,indent:i.length-i.trimStart().length,code:""}}let c="";for(let u=0;u<i.length;){let f=i[u];if(a){f==="\\"?(c+=i.slice(u,u+2),u+=2):i.startsWith(a,u)?(c+=a,u+=a.length,a=null):(c+=f,u++);continue}if(f==="#")break;if(f==='"'||f==="'"){a=i.startsWith(f.repeat(3),u)?f.repeat(3):f,c+=a,u+=a.length;continue}"([{".includes(f)?s++:")]}".includes(f)&&(s=Math.max(0,s-1)),c+=f,u++}let l=!a&&c.trimEnd().endsWith("\\");l&&(c=c.trimEnd().slice(0,-1)),a?.length===1&&(a=null),r.code=r.code?`${r.code} ${c.trim()}`:c.trim(),s===0&&!a&&!l&&(r.code&&t.push(r),r=null)}return r?.code&&t.push(r),t}function M(e,n){let t=[],r=0,s=null,a=0;for(let o=0;o<e.length;o++){let i=e[o];if(s){i==="\\"?o++:e.startsWith(s,o)&&(o+=s.length-1,s=null);continue}if(i==='"'||i==="'")s=e.startsWith(i.repeat(3),o)?i.repeat(3):i;else if("([{".includes(i))r++;else if(")]}".includes(i))r--;else if(i===n&&r===0){if(n==="="&&("=!<>:".includes(e[o-1]??"")||e[o+1]==="="))continue;t.push(e.slice(a,o)),a=o+1}}return t.push(e.slice(a)),t.map(o=>o.trim())}function Z(e,n){let t=0;for(let r=n;r<e.length;r++)if(e[r]==="(")t++;else if(e[r]===")"&&--t===0)return e.slice(n+1,r);return null}var fn=/^class\s+([A-Za-z_]\w*)\s*(\(?)/;function dn(e){let n=[],t=[];for(let r=0;r<e.length;r++){let s=e[r];for(;t.length>0&&s.indent<=t[t.length-1].indent;)t.pop().cls.end=r;let a=t[t.length-1];a&&(a.bodyIndent??=s.indent,s.indent===a.bodyIndent&&a.cls.direct.push(r));let o=fn.exec(s.code);if(!o)continue;let i=o[2]?Z(s.code,o[0].length-1)??"":"",c=M(i,",").filter(u=>u&&!u.includes("=")).map(u=>u.replace(/\[.*$/s,"").split(".").pop().trim()),l={name:o[1],bases:c,header:r,end:e.length,direct:[]};n.push(l),t.push({cls:l,indent:s.indent,bodyIndent:null})}return n}var gn=/^([A-Za-z_]\w*)\s*:(?!=)\s*(.+)$/s,mn=/^(?:[A-Za-z_]\w*\.)*Field\s*\(/,be=/^[rRbBuU]?(['"])([A-Za-z_][A-Za-z0-9_]*)\1$/,hn=/^[A-Za-z_][A-Za-z0-9_]*$/;function Ie(e,n,t=new Set){if(!e.includes("BaseSettings")&&t.size===0)return[];let r=pn(e),s=dn(r),a=new Map;for(let h of s)for(let _ of h.bases){let x=a.get(_);x?x.push(h):a.set(_,[h])}let o=new Set,i=["BaseSettings",...t],c=new Set(i);for(let h=0;h<i.length;h++)for(let _ of a.get(i[h])??[])o.add(_),c.has(_.name)||(c.add(_.name),i.push(_.name));let l=new Map;for(let h of s)o.has(h)&&!l.has(h.name)&&l.set(h.name,h);if(l.size===0)return[];let u=new Set([...l.keys(),...s.filter(h=>h.bases.includes("BaseModel")).map(h=>h.name)]),f=h=>{let _=new Int32Array(r.length+1).fill(r.length);for(let x=r.length-1;x>=0;x--)_[x]=h.test(r[x].code)?x:_[x+1];return _},g=f(/\benv_prefix\s*=\s*[rRuU]?(['"])([A-Za-z0-9_]*)\1/),E=f(/\bcase_sensitive\s*=\s*True\b/),v=h=>{let _=g[h.header];return _<h.end?/\benv_prefix\s*=\s*[rRuU]?(['"])([A-Za-z0-9_]*)\1/.exec(r[_].code)[2]:null},T=new Map,d=h=>{let _=[],x=new Set,b="";for(let y=h;y;){let F=T.get(y);if(F!==void 0){b=F;break}_.push(y),x.add(y);let I=v(y);if(I!==null){b=I;break}y=y.bases.map(L=>l.get(L)).find(L=>L&&!x.has(L))}for(let y of _)T.set(y,b);return b},S=[];for(let h of l.values()){if(h.direct.length===0)continue;let _=d(h),x=E[h.header+1]<h.end;for(let b of h.direct){let y=r[b],F=gn.exec(y.code);if(!F)continue;let[,I,L]=F;if(I.startsWith("_")||I==="model_config")continue;let[C="",p]=M(L,"=");if(/^(?:typing\.)?ClassVar\b/.test(C)||En(C,u))continue;let{value:m,aliases:R}=_n(p),N=/\bNone\b|\bOptional\s*\[/.test(C),k=m!==null&&(m!=="None"||N),O=y.indent+1;if(R.length>0){let Ve=R.length>1;for(let ie of R){let oe={name:x?ie:ie.toUpperCase(),file:n,line:y.line,column:O,syntax:"BaseSettings"};(k||Ve)&&(oe.hasDefault=!0),S.push(oe)}continue}let D=x?`${_}${I}`:`${_}${I}`.toUpperCase();if(!hn.test(D))continue;let re={name:D,file:n,line:y.line,column:O,syntax:"BaseSettings"};k&&(re.hasDefault=!0),S.push(re)}}return S}function En(e,n){let t=e.replace(/^(?:typing\.)?Optional\s*\[(.*)\]$/s,"$1").split("|").map(r=>r.trim()).filter(r=>r!=="None");return t.length===1&&n.has(t[0].replace(/^['"]|['"]$/g,""))}function _n(e){if(e===void 0||e==="")return{value:null,aliases:[]};let n=mn.exec(e);if(!n)return{value:e,aliases:[]};let t=M(Z(e,n[0].length-1)??"",",").filter(Boolean),r=null,s=[];for(let[a,o]of t.entries()){let i=/^([A-Za-z_]\w*)\s*=(?!=)\s*(.*)$/s.exec(o);if(!i){a===0&&o!=="..."&&o!=="Ellipsis"&&(r=o);continue}let[,c,l]=i;if(c==="default")r=l==="..."||l==="Ellipsis"?null:l;else if(c==="default_factory")r="factory";else if(c==="alias"||c==="validation_alias"||c==="env"){let u=be.exec(l);u&&s.push(u[2]);let f=/^(?:[A-Za-z_]\w*\.)*AliasChoices\s*\(/.exec(l);if(f)for(let g of M(Z(l,f[0].length-1)??"",",")){let E=be.exec(g);E&&s.push(E[2])}}}return{value:r,aliases:[...new Set(s)]}}var Ae=Object.fromEntries(H.flatMap(e=>e.extensions.map(n=>[n,e.id]))),ke=new Set(Object.keys(Ae)),q="(?<name>[A-Za-z_][A-Za-z0-9_]*)",w=e=>`(?<q>[${e}])${q}\\k<q>`,W=/^\s*(?:\?\?|\|\|)=?\s*(?!(?:undefined|null)\b)\S/,Ne=/^\s*(?:,|\)\s*or\b)\s*(?!None\b)[^\s)]/,Sn=/^\s*(?:,\s*(?!nil\b)\S|\)\s*(?:\{|do\b))/,xn=/^\s*\|\|\s*(?!nil\b)\S/,Rn={javascript:[{syntax:"process.env",regex:new RegExp(`\\bprocess\\.env(?:\\?\\.|\\.)${q}\\b`,"g"),defaultAfter:W},{syntax:"process.env",regex:new RegExp(`\\bprocess\\.env(?:\\?\\.)?\\[\\s*${w("'\"`")}\\s*\\]`,"g"),defaultAfter:W},{syntax:"import.meta.env",regex:new RegExp(`\\bimport\\.meta\\.env(?:\\?\\.|\\.)${q}\\b`,"g"),defaultAfter:W},{syntax:"import.meta.env",regex:new RegExp(`\\bimport\\.meta\\.env(?:\\?\\.)?\\[\\s*${w("'\"`")}\\s*\\]`,"g"),defaultAfter:W}],python:[{syntax:"os.environ",regex:new RegExp(`\\bos\\.environ\\s*\\[\\s*${w(`'"`)}\\s*\\]`,"g")},{syntax:"os.environ",regex:new RegExp(`\\bos\\.environ\\.get\\s*\\(\\s*${w(`'"`)}`,"g"),defaultAfter:Ne},{syntax:"os.getenv",regex:new RegExp(`\\bos\\.getenv\\s*\\(\\s*${w(`'"`)}`,"g"),defaultAfter:Ne}],go:[{syntax:"os.Getenv",regex:new RegExp(`\\bos\\.Getenv\\s*\\(\\s*${w('"`')}\\s*\\)`,"g")},{syntax:"os.LookupEnv",regex:new RegExp(`\\bos\\.LookupEnv\\s*\\(\\s*${w('"`')}\\s*\\)`,"g")}],ruby:[{syntax:"ENV",regex:new RegExp(`\\bENV\\s*\\[\\s*${w(`'"`)}\\s*\\]`,"g"),defaultAfter:xn},{syntax:"ENV",regex:new RegExp(`\\bENV\\.fetch(?:\\s*\\(\\s*|\\s+)${w(`'"`)}`,"g"),defaultAfter:Sn}]};function X(e){return Ae[yn(e).toLowerCase()]}function J(e,n,t){let r=[],s=e.split(/\r?\n/);for(let a=0;a<s.length;a++){let o=s[a];for(let{syntax:i,regex:c,defaultAfter:l}of Rn[n]){c.lastIndex=0;for(let u=c.exec(o);u;u=c.exec(o)){let f=u.groups?.name;if(!f||Nn(o,u.index,u.index+u[0].length))continue;let g={name:f,file:t,line:a+1,column:u.index+1,syntax:i};l?.test(o.slice(u.index+u[0].length))&&(g.hasDefault=!0),r.push(g)}}if(n==="javascript")for(let i of bn(o,a+1,t))r.push(i)}if(n==="python")for(let a of Ie(e,t))r.push(a);return An(r).sort((a,o)=>a.line-o.line||a.column-o.column)}var Y=/\{([^{}]*)\}\s*(?::[^=;{}]+)?=\s*(process\.env|import\.meta\.env)\b(?!\s*\.|\s*\[|\s*\?\.)/g,vn=/^\s*(?:(['"])([A-Za-z_][A-Za-z0-9_]*)\1|([A-Za-z_][A-Za-z0-9_]*))\s*(?::\s*[A-Za-z_$][\w$]*\s*)?(=\s*(.*))?$/s,Tn=/^(?!(?:undefined|null)\b)\S/;function bn(e,n,t){let r=[];Y.lastIndex=0;for(let s=Y.exec(e);s;s=Y.exec(e)){let a=s[2]==="process.env"?"process.env":"import.meta.env",o=s.index+1;for(let i of s[1].split(",")){let c=vn.exec(i),l=c?.[2]??c?.[3];if(c&&l){let u={name:l,file:t,line:n,column:o+i.indexOf(l)+1,syntax:a};c[4]&&Tn.test(c[5].trim())&&(u.hasDefault=!0),r.push(u)}o+=i.length+1}}return r}var In=new Set(['"',"'","`"]);function Nn(e,n,t){let r=e[n-1];return r!==void 0&&In.has(r)&&e[t]===r}function An(e){let n=new Map;for(let t of e.slice().sort((r,s)=>r.line-s.line||r.column-s.column)){let r=`${t.line}\0${t.name}`,s=n.get(r);s?t.hasDefault||delete s.hasDefault:n.set(r,{...t})}return[...n.values()]}var we=new Set(["test","tests","__tests__","spec","e2e","fixtures","__fixtures__","testdata","playwright","cypress","mocks","__mocks__","testing"]),kn=[/.\.test\./,/.\.spec\./,/.\.e2e\./,/.\.e2e-spec\./,/.\.cy\./,/_test\.go$/,/^test_.*\.py$/,/_test\.py$/,/^conftest\.py$/,/_spec\.rb$/,/^(?:playwright|vitest|jest|cypress)\.config\./,/^vitest\.workspace\./,/^(?:vitest|jest)\.setup\./];function Le(e){return!P(e)&&kn.some(n=>n.test(e))}var Q=new Set(["node_modules","dist",".next","venv",".venv",".yarn","vendor","third_party","bower_components","out","coverage",".turbo",".vercel",".output",".svelte-kit",".nuxt",".cache",".pnpm-store","__pycache__","site-packages"]);function ee(e){return e===".pnp.cjs"||e===".pnp.loader.mjs"||/\.min\.(?:js|mjs|cjs)$/.test(e)}var ne=512*1024;import{readdir as Pe,readFile as Hn}from"fs/promises";import{join as te}from"path";var $=class e{constructor(n){this.rules=n}rules;static empty(){return new e([])}extend(n,t){let r=wn(t).map(s=>({...s,base:n}));return r.length===0?this:new e([...this.rules,...r])}ignores(n,t){let r=!1;for(let s of this.rules){if(s.dirOnly&&!t||r!==s.negate)continue;let a=Gn(s.base,n);a===null||!s.glob||Cn(s.glob,a)&&(r=!s.negate)}return r}};function wn(e){let n=[];for(let t of e.split(/\r?\n/)){if(n.length>=1e3)break;if(t.length>1024)continue;let r=Ln(t);r&&n.push(r)}return n}function Ln(e){let n=On(e);if(n===""||n.startsWith("#"))return null;let t=!1;n.startsWith("!")?(t=!0,n=n.slice(1)):(n.startsWith("\\!")||n.startsWith("\\#"))&&(n=n.slice(1));let r=!1;if(n.endsWith("/")&&(r=!0,n=n.replace(/\/+$/,"")),n==="")return null;let s=n.includes("/");return n=n.replace(/^\/+/,""),!s&&!n.startsWith("**/")&&(n=`**/${n}`),{glob:Pn(n),negate:t,dirOnly:r,source:e}}function On(e){let n=e.length;for(;n>0&&e[n-1]===" "&&e[n-2]!=="\\";)n--;return e.slice(0,n)}var Fe=e=>e!=="/",Oe=()=>!0,Fn=e=>e==="/";function Pn(e){let n=$n(e);if(!n)return null;let t=["accept"],r=0;for(let o=n.length-1;o>=0;o--){let i=n[o],c=t.length;switch(i.kind){case"literal":{let l=i.ch;t.push({test:u=>u===l,next:r});break}case"one":t.push({test:i.test,next:r});break;case"star":case"rest":t.push({a:c+1,b:r},{test:i.kind==="star"?Fe:Oe,next:c});break;case"dirs":t.push({a:c+1,b:r},{a:c+2,b:c+3},{test:Oe,next:c+1},{test:Fn,next:r});break}r=c}let s="";for(let o of n){if(o.kind!=="literal")break;s+=o.ch}let a="";for(let o=n.length-1;o>=0;o--){let i=n[o];if(i.kind!=="literal")break;a=i.ch+a}return{states:t,start:r,prefix:s,suffix:a}}function Cn(e,n){if(!n.startsWith(e.prefix)||!n.endsWith(e.suffix))return!1;let{states:t}=e,r=new Int32Array(t.length).fill(-1),s=[],a=0,o=(i,c)=>{let l=[c];for(;l.length>0;){let u=l.pop();if(r[u]===a)continue;r[u]=a;let f=t[u];typeof f=="object"&&"a"in f?l.push(f.b,f.a):i.push(u)}};o(s,e.start);for(let i=0;i<n.length&&s.length>0;i++){let c=n[i];a++;let l=[];for(let u of s){let f=t[u];typeof f=="object"&&"test"in f&&f.test(c)&&o(l,f.next)}s=l}return s.some(i=>t[i]==="accept")}function $n(e){let n=[],t=0;for(;t<e.length;){let r=e[t];if(r==="*"){if(e[t+1]==="*"){let s=t===0,a=s||e[t-1]==="/",o=e[t+2]==="/",i=t+2===e.length;if(a&&o){n.push({kind:"dirs"}),t+=3;continue}if(a&&i&&!s){n.push({kind:"rest"}),t+=2;continue}n.push({kind:"star"}),t+=2;continue}n.push({kind:"star"}),t++;continue}if(r==="?"){n.push({kind:"one",test:Fe}),t++;continue}if(r==="["){let s=Dn(e,t);if(!s)return null;n.push({kind:"one",test:s.test}),t=s.end;continue}if(r==="\\"&&t+1<e.length){n.push({kind:"literal",ch:e[t+1]}),t+=2;continue}n.push({kind:"literal",ch:r}),t++}return n}var Un={alnum:e=>/[A-Za-z0-9]/.test(e),alpha:e=>/[A-Za-z]/.test(e),blank:e=>e===" "||e===" ",cntrl:e=>e.charCodeAt(0)<32||e.charCodeAt(0)===127,digit:e=>e>="0"&&e<="9",graph:e=>e.charCodeAt(0)>32&&e.charCodeAt(0)<127,lower:e=>e>="a"&&e<="z",print:e=>e.charCodeAt(0)>=32&&e.charCodeAt(0)<127,punct:e=>/[!-/:-@[-`{-~]/.test(e),space:e=>/[ \t\n\r\f\v]/.test(e),upper:e=>e>="A"&&e<="Z",xdigit:e=>/[0-9A-Fa-f]/.test(e)};function Dn(e,n){let t=n+1,r=e[t]==="!"||e[t]==="^";r&&t++;let s=[];for(let o=!0;;o=!1){if(t>=e.length)return null;let i=e[t];if(i==="]"&&!o)break;if(i==="["&&e[t+1]===":"){let l=e.indexOf(":]",t+2);if(l!==-1){let u=Un[e.slice(t+2,l)];if(!u)return null;s.push(u),t=l+2;continue}}if(i==="\\"){if(t++,t>=e.length)return null;i=e[t]}if(e[t+1]==="-"&&t+2<e.length&&e[t+2]!=="]"){let l=t+2;if(e[l]==="\\"&&l++,l>=e.length)return null;let[u,f]=[i,e[l]];s.push(g=>g>=u&&g<=f),t=l+1;continue}let c=i;s.push(l=>l===c),t++}return{test:o=>o!=="/"&&s.some(i=>i(o))!==r,end:t+1}}function Gn(e,n){return e===""?n:n.startsWith(`${e}/`)?n.slice(e.length+1):null}var Ce=new Set([".git",...Q]);async function $e(e,n){let t=n.skipDirs??Ce,r=n.respectGitignore??!0,s=[],a=[],o=[],i=[],c=$.empty().extend("",(n.exclude??[]).join(`
|
|
6
|
+
`));async function l(f,g,E){let v=f===""?e:te(e,f),T;try{T=await Pe(v,{withFileTypes:!0})}catch(d){a.push({file:f||".",message:`cannot read directory: ${d.message}`});return}r&&T.some(d=>d.name===".gitignore"&&d.isFile())&&(g=g.extend(f,await Hn(te(v,".gitignore"),"utf8")));for(let d of T){if(d.isSymbolicLink())continue;let S=f===""?d.name:`${f}/${d.name}`;if(d.isDirectory()){if(c.ignores(S,!0)||g.ignores(S,!0))continue;if(t.has(d.name)){Q.has(d.name)&&!E&&await u(S)&&i.push(`${S}/`);continue}await l(S,g,E||n.skipTests===!0&&we.has(d.name))}else if(d.isFile()){if(!n.include(S,d.name)||c.ignores(S,!1)||g.ignores(S,!1)&&!n.keepIgnored?.(d.name))continue;ee(d.name)?E||i.push(S):n.skipTests&&(E||Le(d.name))?o.push(S):s.push(S)}}}async function u(f){let g;try{g=await Pe(te(e,f),{withFileTypes:!0})}catch{return!1}for(let E of g){let v=`${f}/${E.name}`;if(E.isFile()&&n.include(v,E.name))return!0}for(let E of g)if(E.isDirectory()&&!E.isSymbolicLink()&&await u(`${f}/${E.name}`))return!0;return!1}return await l("",$.empty(),!1),s.sort(),o.sort(),i.sort(),{files:s,warnings:a,testFiles:o,vendored:i}}async function Ge(e,n={}){let{files:t,warnings:r,testFiles:s,vendored:a}=await $e(e,{include:Wn,exclude:n.exclude,keepIgnored:P,skipTests:!n.includeTests});return jn(t,{read:i=>Bn(Ue(e,i),"utf8"),size:async i=>(await Mn(Ue(e,i))).size},r,n,s,a)}function Wn(e,n){return P(n)||j(n)||ke.has(se.extname(e).toLowerCase())}var zn={quote:"a quote that never closes: the rest of the file was read as this value",block:"a -----BEGIN block with no -----END line: the rest of the file was skipped"};async function jn(e,{read:n,size:t},r,s,a,o){let i=[],c=[],l=[],u=[];for(let p of e){let m=se.basename(p);if(!P(m)){await t(p)>ne?u.push(p):(j(m)?l:c).push(p);continue}let{entries:R,commented:N,invalid:k,unterminated:O}=pe(await n(p));for(let{line:D}of k)r.push({file:p,line:D,message:"ignored a line that is not KEY=value"});O&&r.push({file:p,line:O.line,message:zn[O.kind]}),i.push({path:p,name:m,entries:R,commented:ge(m)?N:[]})}let f=new Set(i.map(p=>U(p.path))),g=new Map;for(let p of c){let m=X(p);if(!m)continue;let R=J(await n(p),m,p);if(R.length===0)continue;let N=De(U(p),f),k=g.get(N);if(k)for(let O of R)k.push(O);else g.set(N,R)}let E=new Map,v=(p,m)=>{let R=De(U(p),f),N=E.get(R);N||E.set(R,N=new Set);for(let k of m)N.add(k)};for(let p of a){let m=X(p);!m||await t(p)>ne||v(p,J(await n(p),m,p).map(R=>R.name))}for(let p of l)v(p,ae(await n(p)));let T=K(s.ignore??[]),d=K(s.defaultIgnore===!1?[]:ce),S=p=>T(p)||d(p),h=new Set;for(let p of g.values())for(let m of p)h.add(m.name);for(let p of i)for(let m of p.entries)h.add(m.key);let _=[...h].filter(p=>d(p)&&!T(p)).sort(),x=new Map;for(let p of i){let m=U(p.path),R=x.get(m);R?R.push(p):x.set(m,[p])}let b=p=>x.get(p)??[],y=[...new Set([...f,...g.keys()])].sort(),F=y.map(p=>{let m=G(b(p).map(R=>R.name));return m.length>64&&r.push({file:p||".",message:`${m.length} env files; only the first ${64} are reported`}),{scope:p,env_files:m.slice(0,64)}}),I=y.flatMap(p=>ye({references:g.get(p)??[],envFiles:b(p),isIgnored:S,usedOutsideCode:E.get(p)})).filter(p=>c.length>0||p.kind!=="unused").sort(xe),L=y.flatMap(p=>Se({scope:p,references:g.get(p)??[],envFiles:b(p),isIgnored:S}).map(m=>({...m,defined_in:m.defined_in.slice(0,64)}))),C=y.flatMap(p=>(g.get(p)??[]).filter(m=>!S(m.name)).map(m=>({...m,scope:p}))).sort((p,m)=>p.file.localeCompare(m.file)||p.line-m.line||p.column-m.column);return{findings:I,references:C,variables:L,counts:Re(I),scopes:y,envScopes:F,defaultIgnored:_,testFilesSkipped:a.length,vendoredSkipped:o,tooLargeSkipped:u,sourceFiles:c.length,envFiles:i.map(p=>p.path),warnings:r}}function U(e){let n=se.dirname(e);return n==="."?"":n}function De(e,n){for(let t=e;t!=="";t=U(t))if(n.has(t))return t;return""}var He="DEPLOYHEALTH_TOKEN";var Be="0.3.2";var A={ok:0,failed:1,usage:2},Me=`Usage: deployhealth-scan [options]
|
|
7
7
|
|
|
8
8
|
Scan a repository for env var drift and report it to deployhealth.
|
|
9
9
|
|
|
@@ -25,18 +25,20 @@ Options:
|
|
|
25
25
|
--json with --dry-run, print JSON
|
|
26
26
|
-v, --version print the version
|
|
27
27
|
-h, --help show this help
|
|
28
|
-
`,
|
|
28
|
+
`,Yn={url:{type:"string"},token:{type:"string"},sha:{type:"string"},branch:{type:"string"},dir:{type:"string"},ignore:{type:"string",multiple:!0},"no-default-ignore":{type:"boolean"},"include-tests":{type:"boolean"},exclude:{type:"string",multiple:!0},"dry-run":{type:"boolean"},json:{type:"boolean"},version:{type:"boolean",short:"v"},help:{type:"boolean",short:"h"}};async function ze(e,n){let t;try{({values:t}=Zn({args:[...e],options:Yn,strict:!0,allowPositionals:!1}))}catch(d){return n.stderr(`deployhealth-scan: ${d.message}
|
|
29
29
|
|
|
30
|
-
${
|
|
31
|
-
`),A.ok;let
|
|
32
|
-
`);if(t["dry-run"])return
|
|
33
|
-
|
|
34
|
-
`),A.
|
|
35
|
-
`);
|
|
36
|
-
`),
|
|
37
|
-
`),A.usage;
|
|
38
|
-
`),A.
|
|
39
|
-
`)
|
|
40
|
-
`),A.
|
|
41
|
-
`)}
|
|
30
|
+
${Me}`),A.usage}if(t.help)return n.stdout(Me),A.ok;if(t.version)return n.stdout(`${Be}
|
|
31
|
+
`),A.ok;let r=Kn(n.cwd,t.dir??"."),s=await Ge(r,{ignore:t.ignore,exclude:t.exclude,defaultIgnore:!t["no-default-ignore"],includeTests:t["include-tests"]});for(let d of s.warnings)n.stderr(`warning ${d.file}${d.line?`:${d.line}`:""}: ${d.message}
|
|
32
|
+
`);let a=s.sourceFiles>0;if(t["dry-run"])return t.json?(a||n.stderr(`${B}
|
|
33
|
+
`),n.stdout(`${JSON.stringify(Xn(s),null,2)}
|
|
34
|
+
`)):n.stdout(Qn(s)),A.ok;let o=t.token||n.env.DEPLOYHEALTH_TOKEN||void 0;if(!t.url||!o)return n.stderr(`deployhealth-scan: --url and --token (or ${He}) are required (or pass --dry-run)
|
|
35
|
+
`),A.usage;qn(t.url)&&n.stderr(`deployhealth-scan: warning: --url is plain http; the token and the report are sent unencrypted
|
|
36
|
+
`);let i=t.sha??n.git(["rev-parse","HEAD"],r),c=t.branch??n.git(["rev-parse","--abbrev-ref","HEAD"],r);if(!i||!c||c==="HEAD")return n.stderr(`deployhealth-scan: could not determine the commit and branch from git; pass --sha and --branch
|
|
37
|
+
`),A.usage;if(!me.test(i))return n.stderr(`deployhealth-scan: --sha must be a hex commit id, got "${i}"
|
|
38
|
+
`),A.usage;let l={sha:i,branch:c,timestamp:n.now().toISOString(),findings:s.findings,variables:s.variables,env_scopes:s.envScopes};a||n.stdout(`${B}
|
|
39
|
+
`);let u=`${t.url.replace(/\/+$/,"")}/api/ingest/scan`,f;try{f=await n.fetch(u,{method:"POST",headers:{authorization:`Bearer ${o}`,"content-type":"application/json"},body:JSON.stringify(l)})}catch(d){return n.stderr(`deployhealth-scan: could not reach ${u}: ${d.message}
|
|
40
|
+
`),A.failed}if(!f.ok){let d=(await f.text()).slice(0,500);return n.stderr(`deployhealth-scan: ingest failed with HTTP ${f.status}: ${d}
|
|
41
|
+
`),A.failed}let g=await f.json(),{missing:E,unused:v,mismatch:T}=g.counts;return n.stdout(`deployhealth: reported ${i.slice(0,7)} on ${c}: ${E} missing, ${v} unused, ${T} mismatch (deploy ${g.deployId})
|
|
42
|
+
`),A.ok}function qn(e){let n;try{n=new URL(e)}catch{return!1}if(n.protocol!=="http:")return!1;let t=n.hostname.replace(/^\[|\]$/g,"").toLowerCase();return!(t==="localhost"||t.endsWith(".localhost")||/^127(?:\.\d{1,3}){3}$/.test(t)||t==="::1")}function Xn(e){return{source_files:e.sourceFiles,can_check:e.sourceFiles>0,counts:e.counts,scopes:e.scopes,env_scopes:e.envScopes,findings:e.findings,variables:e.variables,default_ignored:e.defaultIgnored,test_files_skipped:e.testFilesSkipped,vendored_skipped:e.vendoredSkipped,too_large_skipped:e.tooLargeSkipped,warnings:e.warnings}}var Jn={missing:"MISSING",unused:"UNUSED",mismatch:"MISMATCH"};function Qn(e){let n=e.scopes.map(s=>s||"(root)").join(", "),t=[`deployhealth-scan: ${e.sourceFiles} source files, scopes: ${n}`,""];e.sourceFiles===0&&t.push(B,"");for(let s of["missing","unused","mismatch"]){let a=e.findings.filter(o=>o.kind===s);t.push(`${Jn[s]} (${e.counts[s]})`),a.length===0&&t.push(" none");for(let o of a){let i=o.file?`${o.file}${o.line?`:${o.line}`:""}`:"",c=s==="mismatch"?` (missing from ${o.env_file})`:"";t.push(` ${o.var_name.padEnd(28)} ${i}${c}`)}t.push("")}let r=e.variables.filter(s=>s.optional&&s.defined_in.length===0);if(r.length>0){t.push(`OPTIONAL (${r.length}): a default in code, not defined in an env file`);for(let s of r)t.push(` ${s.var_name.padEnd(28)} ${s.scope||"(root)"}`);t.push("")}for(let{scope:s}of e.envScopes.filter(a=>a.env_files.length===0)){let a=e.variables.filter(o=>o.scope===s).length;a!==0&&(t.push(`No .env.example in ${s||"the repository root"}: ${a} variable${a===1?"":"s"} referenced (--json lists them).`),t.push(""))}if(e.testFilesSkipped>0){let s=e.testFilesSkipped===1?"file":"files";t.push(`Skipped ${e.testFilesSkipped} test and fixture ${s} (read only to see which variables they use). --include-tests includes them.`),t.push("")}if(e.vendoredSkipped.length>0&&(t.push(`Skipped vendored and generated code: ${We(e.vendoredSkipped)}.`),t.push("")),e.tooLargeSkipped.length>0){let s=e.tooLargeSkipped.length;t.push(`Skipped ${s} file${s===1?"":"s"} over 512 KB (bundles, not code people wrote): ${We(e.tooLargeSkipped)}.`),t.push("")}return e.defaultIgnored.length>0&&(t.push(`Skipped (the platform or runtime provides them): ${e.defaultIgnored.join(", ")}. --no-default-ignore includes them.`),t.push("")),`${t.join(`
|
|
43
|
+
`)}`}function We(e,n=5){let t=e.length-n;return t>0?`${e.slice(0,n).join(", ")} and ${t} more (--json lists them)`:e.join(", ")}var je=()=>({cwd:process.cwd(),stdout:e=>process.stdout.write(e),stderr:e=>process.stderr.write(e),fetch:globalThis.fetch,git:(e,n)=>{try{return Vn("git",e,{cwd:n,encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim()||null}catch{return null}},now:()=>new Date,env:{DEPLOYHEALTH_TOKEN:process.env.DEPLOYHEALTH_TOKEN}});ze(process.argv.slice(2),je()).then(e=>{process.exitCode=e},e=>{process.stderr.write(`deployhealth-scan: unexpected error: ${e instanceof Error?e.stack:String(e)}
|
|
42
44
|
`),process.exitCode=1});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deployhealth-scan",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Find env var drift in a repo (read in code but never defined, defined but never read, out of sync between env files) and report it to deployhealth.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Utsav Mishra",
|