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 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.1 --dry-run # grouped findings with file:line
81
- npx deployhealth-scan@0.3.1 --dry-run --json # the same, as JSON
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 the repository secret
89
- `DEPLOYHEALTH_TOKEN`, then add `.github/workflows/deployhealth.yml`:
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.1 \
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.1`) so an update never runs in your CI unreviewed. For a self-hosted
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 Un}from"child_process";import{resolve as Hn}from"path";import{parseArgs as Gn}from"util";import{readFile as Ln,stat as $n}from"fs/promises";import{join as Le,posix as ne}from"path";function j(e){return/^(?:docker-)?compose(?:[.-][A-Za-z0-9_.-]+)?\.ya?ml$/.test(e)}var je=/\$(?:\{([A-Za-z_][A-Za-z0-9_]*)(?=[}:?+-])|([A-Za-z_][A-Za-z0-9_]*))/g;function ie(e){let n=new Set;for(let t of e.split(/\r?\n/)){if(t.trimStart().startsWith("#"))continue;let s=t.replace(/\$\$/g,"");for(let r of s.matchAll(je))n.add(r[1]??r[2])}return n}var oe=["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 Yn}from"fs/promises";var We=/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_.-]*)\s*=(.*)$/,ze=/^\s*#+\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/,Ve=new Set(['"',"'","`"]),ae="-----BEGIN ",Ke="-----END ";function le(e){let n=e.replace(/^\uFEFF/,"").split(/\r?\n/),t=[],s=[],r=[],a=null,o=i=>{for(let l=i+1;l<n.length;l++)if((n[l]??"").trim().startsWith(Ke))return l;return-1};for(let i=0;i<n.length;i++){let l=n[i]??"",c=l.trim();if(c.startsWith("#")){let m=ze.exec(l)?.[1];m&&s.push({key:m,line:i+1});continue}if(c==="")continue;if(c.startsWith(ae)){let m=o(i);if(m===-1){a={line:i+1,kind:"block"};break}i=m;continue}let u=We.exec(l);if(!u){r.push({line:i+1,text:l});continue}let p=u[1],d=(u[2]??"").trimStart(),E=i+1,T=d[0];if(T!==void 0&&Ve.has(T)){let m=Ze(n,i,d,T);if(t.push({key:p,value:m.value,line:E}),m.endIndex===-1){a={line:E,kind:"quote"};break}i=m.endIndex;continue}let _=qe(d).trim();if(t.push({key:p,value:_,line:E}),_.startsWith(ae)){let m=o(i);if(m===-1){a={line:E,kind:"block"};break}i=m}}return{entries:t,commented:s,invalid:r,unterminated:a}}function Ze(e,n,t,s){let r=[],a=t.slice(1);for(let o=n;;){let i=Ye(a,s);if(i!==-1){r.push(a.slice(0,i));let l=r.join(`
3
- `);return{value:s==='"'?ce(l):l,endIndex:o}}if(r.push(a),o++,o>=e.length){let l=r.join(`
4
- `);return{value:s==='"'?ce(l):l,endIndex:-1}}a=e[o]??""}}function Ye(e,n){for(let t=0;t<e.length;t++){let s=e[t];if(s==="\\"){t++;continue}if(s===n)return t}return-1}function ce(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 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 c(p,d,E){let T=p===""?e:ee(e,p),_;try{_=await we(T,{withFileTypes:!0})}catch(m){a.push({file:p||".",message:`cannot read directory: ${m.message}`});return}s&&_.some(m=>m.name===".gitignore"&&m.isFile())&&(d=d.extend(p,await On(ee(T,".gitignore"),"utf8")));for(let m of _){if(m.isSymbolicLink())continue;let v=p===""?m.name:`${p}/${m.name}`;if(m.isDirectory()){if(l.ignores(v,!0)||d.ignores(v,!0))continue;if(t.has(m.name)){X.has(m.name)&&!E&&await u(v)&&i.push(`${v}/`);continue}await c(v,d,E||n.skipTests===!0&&Ie.has(m.name))}else if(m.isFile()){if(!n.include(v,m.name)||l.ignores(v,!1)||d.ignores(v,!1)&&!n.keepIgnored?.(m.name))continue;J(m.name)?E||i.push(v):n.skipTests&&(E||Ne(m.name))?o.push(v):r.push(v)}}}async function u(p){let d;try{d=await we(ee(e,p),{withFileTypes:!0})}catch{return!1}for(let E of d){let T=`${p}/${E.name}`;if(E.isFile()&&n.include(T,E.name))return!0}for(let E of d)if(E.isDirectory()&&!E.isSymbolicLink()&&await u(`${p}/${E.name}`))return!0;return!1}return await c("",P.empty(),!1),r.sort(),o.sort(),i.sort(),{files:r,warnings:a,testFiles:o,vendored:i}}async function Ce(e,n={}){let{files:t,warnings:s,testFiles:r,vendored:a}=await Oe(e,{include:Cn,exclude:n.exclude,keepIgnored:$,skipTests:!n.includeTests});return Dn(t,{read:i=>Ln(Le(e,i),"utf8"),size:async i=>(await $n(Le(e,i))).size},s,n,r,a)}function Cn(e,n){return $(n)||j(n)||be.has(ne.extname(e).toLowerCase())}var Pn={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 Dn(e,{read:n,size:t},s,r,a,o){let i=[],l=[],c=[],u=[];for(let f of e){let g=ne.basename(f);if(!$(g)){await t(f)>Q?u.push(f):(j(g)?c:l).push(f);continue}let{entries:R,commented:N,invalid:k,unterminated:O}=le(await n(f));for(let{line:U}of k)s.push({file:f,line:U,message:"ignored a line that is not KEY=value"});O&&s.push({file:f,line:O.line,message:Pn[O.kind]}),i.push({path:f,name:g,entries:R,commented:pe(g)?N:[]})}let p=new Set(i.map(f=>D(f.path))),d=new Map;for(let f of l){let g=Y(f);if(!g)continue;let R=q(await n(f),g,f);if(R.length===0)continue;let N=$e(D(f),p),k=d.get(N);if(k)for(let O of R)k.push(O);else d.set(N,R)}let E=new Map,T=(f,g)=>{let R=$e(D(f),p),N=E.get(R);N||E.set(R,N=new Set);for(let k of g)N.add(k)};for(let f of a){let g=Y(f);!g||await t(f)>Q||T(f,q(await n(f),g,f).map(R=>R.name))}for(let f of c)T(f,ie(await n(f)));let _=z(r.ignore??[]),m=z(r.defaultIgnore===!1?[]:oe),v=f=>_(f)||m(f),h=new Set;for(let f of d.values())for(let g of f)h.add(g.name);for(let f of i)for(let g of f.entries)h.add(g.key);let y=[...h].filter(f=>m(f)&&!_(f)).sort(),x=new Map;for(let f of i){let g=D(f.path),R=x.get(g);R?R.push(f):x.set(g,[f])}let b=f=>x.get(f)??[],S=[...new Set([...p,...d.keys()])].sort(),L=S.map(f=>{let g=H(b(f).map(R=>R.name));return g.length>64&&s.push({file:f||".",message:`${g.length} env files; only the first ${64} are reported`}),{scope:f,env_files:g.slice(0,64)}}),I=S.flatMap(f=>Ee({references:d.get(f)??[],envFiles:b(f),isIgnored:v,usedOutsideCode:E.get(f)})).sort(ye),F=S.flatMap(f=>_e({scope:f,references:d.get(f)??[],envFiles:b(f),isIgnored:v}).map(g=>({...g,defined_in:g.defined_in.slice(0,64)}))),C=S.flatMap(f=>(d.get(f)??[]).filter(g=>!v(g.name)).map(g=>({...g,scope:f}))).sort((f,g)=>f.file.localeCompare(g.file)||f.line-g.line||f.column-g.column);return{findings:I,references:C,variables:F,counts:Se(I),scopes:S,envScopes:L,defaultIgnored:y,testFilesSkipped:a.length,vendoredSkipped:o,tooLargeSkipped:u,sourceFiles:l.length,envFiles:i.map(f=>f.path),warnings:s}}function D(e){let n=ne.dirname(e);return n==="."?"":n}function $e(e,n){for(let t=e;t!=="";t=D(t))if(n.has(t))return t;return""}var Pe="DEPLOYHEALTH_TOKEN";var De="0.3.1";var A={ok:0,failed:1,usage:2},Ue=`Usage: deployhealth-scan [options]
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
- `,Bn={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 Ge(e,n){let t;try{({values:t}=Gn({args:[...e],options:Bn,strict:!0,allowPositionals:!1}))}catch(_){return n.stderr(`deployhealth-scan: ${_.message}
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
- ${Ue}`),A.usage}if(t.help)return n.stdout(Ue),A.ok;if(t.version)return n.stdout(`${De}
31
- `),A.ok;let s=Hn(n.cwd,t.dir??"."),r=await Ce(s,{ignore:t.ignore,exclude:t.exclude,defaultIgnore:!t["no-default-ignore"],includeTests:t["include-tests"]});for(let _ of r.warnings)n.stderr(`warning ${_.file}${_.line?`:${_.line}`:""}: ${_.message}
32
- `);if(t["dry-run"])return n.stdout(t.json?`${JSON.stringify(jn(r),null,2)}
33
- `:zn(r)),A.ok;let a=t.token||n.env.DEPLOYHEALTH_TOKEN||void 0;if(!t.url||!a)return n.stderr(`deployhealth-scan: --url and --token (or ${Pe}) are required (or pass --dry-run)
34
- `),A.usage;Mn(t.url)&&n.stderr(`deployhealth-scan: warning: --url is plain http; the token and the report are sent unencrypted
35
- `);let o=t.sha??n.git(["rev-parse","HEAD"],s),i=t.branch??n.git(["rev-parse","--abbrev-ref","HEAD"],s);if(!o||!i||i==="HEAD")return n.stderr(`deployhealth-scan: could not determine the commit and branch from git; pass --sha and --branch
36
- `),A.usage;if(!de.test(o))return n.stderr(`deployhealth-scan: --sha must be a hex commit id, got "${o}"
37
- `),A.usage;let l={sha:o,branch:i,timestamp:n.now().toISOString(),findings:r.findings,variables:r.variables,env_scopes:r.envScopes},c=`${t.url.replace(/\/+$/,"")}/api/ingest/scan`,u;try{u=await n.fetch(c,{method:"POST",headers:{authorization:`Bearer ${a}`,"content-type":"application/json"},body:JSON.stringify(l)})}catch(_){return n.stderr(`deployhealth-scan: could not reach ${c}: ${_.message}
38
- `),A.failed}if(!u.ok){let _=(await u.text()).slice(0,500);return n.stderr(`deployhealth-scan: ingest failed with HTTP ${u.status}: ${_}
39
- `),A.failed}let p=await u.json(),{missing:d,unused:E,mismatch:T}=p.counts;return n.stdout(`deployhealth: reported ${o.slice(0,7)} on ${i}: ${d} missing, ${E} unused, ${T} mismatch (deploy ${p.deployId})
40
- `),A.ok}function Mn(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 jn(e){return{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 Wn={missing:"MISSING",unused:"UNUSED",mismatch:"MISMATCH"};function zn(e){let n=e.scopes.map(r=>r||"(root)").join(", "),t=[`deployhealth-scan: ${e.sourceFiles} source files, scopes: ${n}`,""];for(let r of["missing","unused","mismatch"]){let a=e.findings.filter(o=>o.kind===r);t.push(`${Wn[r]} (${e.counts[r]})`),a.length===0&&t.push(" none");for(let o of a){let i=o.file?`${o.file}${o.line?`:${o.line}`:""}`:"",l=r==="mismatch"?` (missing from ${o.env_file})`:"";t.push(` ${o.var_name.padEnd(28)} ${i}${l}`)}t.push("")}let s=e.variables.filter(r=>r.optional&&r.defined_in.length===0);if(s.length>0){t.push(`OPTIONAL (${s.length}): a default in code, not defined in an env file`);for(let r of s)t.push(` ${r.var_name.padEnd(28)} ${r.scope||"(root)"}`);t.push("")}for(let{scope:r}of e.envScopes.filter(a=>a.env_files.length===0)){let a=e.variables.filter(o=>o.scope===r).length;a!==0&&(t.push(`No .env.example in ${r||"the repository root"}: ${a} variable${a===1?"":"s"} referenced (--json lists them).`),t.push(""))}if(e.testFilesSkipped>0){let r=e.testFilesSkipped===1?"file":"files";t.push(`Skipped ${e.testFilesSkipped} test and fixture ${r} (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: ${He(e.vendoredSkipped)}.`),t.push("")),e.tooLargeSkipped.length>0){let r=e.tooLargeSkipped.length;t.push(`Skipped ${r} file${r===1?"":"s"} over 512 KB (bundles, not code people wrote): ${He(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(`
41
- `)}`}function He(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 Be=()=>({cwd:process.cwd(),stdout:e=>process.stdout.write(e),stderr:e=>process.stderr.write(e),fetch:globalThis.fetch,git:(e,n)=>{try{return Un("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}});Ge(process.argv.slice(2),Be()).then(e=>{process.exitCode=e},e=>{process.stderr.write(`deployhealth-scan: unexpected error: ${e instanceof Error?e.stack:String(e)}
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.1",
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",