@visulima/vis 1.0.0-alpha.10 → 1.0.0-alpha.11
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/CHANGELOG.md +95 -42
- package/LICENSE.md +213 -0
- package/README.md +8 -4
- package/dist/bin.js +9 -1
- package/dist/config/index.d.ts +1818 -0
- package/dist/config/index.js +2 -0
- package/dist/generate/index.d.ts +1 -1
- package/dist/generate/index.js +3 -1
- package/dist/packem_chunks/applyDefaults.js +336 -0
- package/dist/packem_chunks/bin.js +9554 -64
- package/dist/packem_chunks/doctor-probe.js +112 -0
- package/dist/packem_chunks/fix.js +229 -48
- package/dist/packem_chunks/handler.js +99 -1
- package/dist/packem_chunks/handler10.js +53 -1
- package/dist/packem_chunks/handler11.js +32 -1
- package/dist/packem_chunks/handler12.js +100 -2
- package/dist/packem_chunks/handler13.js +25 -1
- package/dist/packem_chunks/handler14.js +916 -5
- package/dist/packem_chunks/handler15.js +206 -1
- package/dist/packem_chunks/handler16.js +122 -18
- package/dist/packem_chunks/handler17.js +13 -1
- package/dist/packem_chunks/handler18.js +106 -1
- package/dist/packem_chunks/handler19.js +19 -1
- package/dist/packem_chunks/handler2.js +75 -1
- package/dist/packem_chunks/handler20.js +29 -1
- package/dist/packem_chunks/handler21.js +222 -1
- package/dist/packem_chunks/handler22.js +237 -5
- package/dist/packem_chunks/handler23.js +101 -1
- package/dist/packem_chunks/handler24.js +110 -1
- package/dist/packem_chunks/handler25.js +402 -5
- package/dist/packem_chunks/handler26.js +13 -1
- package/dist/packem_chunks/handler27.js +63 -3
- package/dist/packem_chunks/handler28.js +34 -1
- package/dist/packem_chunks/handler29.js +458 -7
- package/dist/packem_chunks/handler3.js +95 -2
- package/dist/packem_chunks/handler30.js +168 -21
- package/dist/packem_chunks/handler31.js +530 -3
- package/dist/packem_chunks/handler32.js +214 -2
- package/dist/packem_chunks/handler33.js +119 -24
- package/dist/packem_chunks/handler34.js +630 -2
- package/dist/packem_chunks/handler35.js +283 -19
- package/dist/packem_chunks/handler36.js +521 -407
- package/dist/packem_chunks/handler37.js +762 -22
- package/dist/packem_chunks/handler38.js +989 -22
- package/dist/packem_chunks/handler39.js +574 -22
- package/dist/packem_chunks/handler4.js +90 -4
- package/dist/packem_chunks/handler40.js +1685 -3
- package/dist/packem_chunks/handler41.js +1088 -10
- package/dist/packem_chunks/handler42.js +785 -141
- package/dist/packem_chunks/handler43.js +2658 -42
- package/dist/packem_chunks/handler44.js +3886 -3
- package/dist/packem_chunks/handler45.js +2568 -21
- package/dist/packem_chunks/handler46.js +3769 -0
- package/dist/packem_chunks/handler47.js +1491 -0
- package/dist/packem_chunks/handler5.js +174 -2
- package/dist/packem_chunks/handler6.js +95 -13
- package/dist/packem_chunks/handler7.js +115 -8
- package/dist/packem_chunks/handler8.js +12 -1
- package/dist/packem_chunks/handler9.js +29 -1
- package/dist/packem_chunks/heal-accept.js +522 -0
- package/dist/packem_chunks/heal.js +673 -0
- package/dist/packem_chunks/index.js +873 -7
- package/dist/packem_chunks/loader.js +23 -1
- package/dist/packem_shared/VisUpdateApp-D-Yz_wvg.js +1316 -0
- package/dist/packem_shared/_commonjsHelpers-BqLXS_qQ.js +5 -0
- package/dist/packem_shared/ai-analysis-CHeB1joD.js +367 -0
- package/dist/packem_shared/ai-cache-Be_jexe4.js +142 -0
- package/dist/packem_shared/ai-fix-B9iQVcD2.js +379 -0
- package/dist/packem_shared/cache-directory-2qvs4goY.js +98 -0
- package/dist/packem_shared/catalog-BJTtyi-O.js +1371 -0
- package/dist/packem_shared/dependency-scan-A0KSklpG.js +188 -0
- package/dist/packem_shared/docker-2iZzc280.js +181 -0
- package/dist/packem_shared/failure-log-Cz3Z4SKL.js +100 -0
- package/dist/packem_shared/flakiness-goTxXuCX.js +180 -0
- package/dist/packem_shared/otel-DCvqCTz_.js +158 -0
- package/dist/packem_shared/otelPlugin-DFaLDvJf.js +3 -0
- package/dist/packem_shared/registry-CbqXI0rc.js +272 -0
- package/dist/packem_shared/run-summary-utils-PVMl4aIh.js +130 -0
- package/dist/packem_shared/runtime-check-Cobi3p6l.js +127 -0
- package/dist/packem_shared/selectors-SM69TfqC.js +194 -0
- package/dist/packem_shared/symbols-Ta7g2nU-.js +14 -0
- package/dist/packem_shared/toolchain-BdZd9eBi.js +975 -0
- package/dist/packem_shared/typosquats-C-bCh3PX.js +1210 -0
- package/dist/packem_shared/use-measured-height-CNP0vT4M.js +20 -0
- package/dist/packem_shared/utils-CthVdBPS.js +40 -0
- package/dist/packem_shared/xxh3-Ck8mXNg1.js +239 -0
- package/index.js +727 -555
- package/package.json +35 -17
- package/schemas/project.schema.json +8 -10
- package/schemas/vis-config.schema.json +132 -8
- package/skills/vis/SKILL.md +96 -0
- package/templates/buildkite-ci/.buildkite/pipeline.yml.tera +85 -0
- package/templates/buildkite-ci/template.yml +20 -0
- package/dist/errors/index.d.ts +0 -26
- package/dist/errors/index.js +0 -1
- package/dist/packem_chunks/config.js +0 -2
- package/dist/packem_shared/VisConfigCycleError-CAYNC7d-.js +0 -1
- package/dist/packem_shared/VisConfigError-B5LP1zRf.js +0 -1
- package/dist/packem_shared/VisConfigLoadError-CeqBSd2Z.js +0 -2
- package/dist/packem_shared/VisConfigNotFoundError-DZ9KC527.js +0 -5
- package/dist/packem_shared/VisUpdateApp-D-L4_-Iu.js +0 -1
- package/dist/packem_shared/_commonjsHelpers-D6W6KoPK.js +0 -1
- package/dist/packem_shared/ai-analysis-CGuy7dfE.js +0 -67
- package/dist/packem_shared/ai-cache-Bynt6Y9x.js +0 -1
- package/dist/packem_shared/cache-directory-D72ZEag2.js +0 -1
- package/dist/packem_shared/catalog-BVPerCwG.js +0 -12
- package/dist/packem_shared/dependency-scan-Du0tBu64.js +0 -2
- package/dist/packem_shared/docker-BcfqH4Av.js +0 -2
- package/dist/packem_shared/failure-log-DqYen0LC.js +0 -2
- package/dist/packem_shared/flakiness-DSIHZGBT.js +0 -1
- package/dist/packem_shared/run-summary-utils-C24Aaf9E.js +0 -1
- package/dist/packem_shared/runtime-check-CGHal8SO.js +0 -1
- package/dist/packem_shared/selectors-CfH9ZY08.js +0 -3
- package/dist/packem_shared/symbols-CQmER5MT.js +0 -1
- package/dist/packem_shared/target-merge-DNa-6eWu.js +0 -1
- package/dist/packem_shared/toolchain-DQfTQY8E.js +0 -5
- package/dist/packem_shared/typosquats-DOR8izpX.js +0 -1
- package/dist/packem_shared/use-measured-height-DjYgUOKk.js +0 -1
- package/dist/packem_shared/utils-DrNg0XTR.js +0 -1
- package/dist/packem_shared/xxh3-DrAUNq4n.js +0 -1
|
@@ -1,22 +1,574 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
`)}}},"writePkgJsonOverrides"),Ve=T((r,e,t,n)=>{const a=_(e),o=JSON.parse(a),{overrides:s,source:y}=qe(r,o,n),f=[],u=[],S=new Set;if(n.name==="npm")for(const g of["dependencies","devDependencies"]){const w=o[g];if(w)for(const k of Object.keys(w))S.add(k)}for(const g of t){const w=s[g.original];if(typeof w=="object")continue;let k=g.spec;n.name==="npm"&&S.has(g.original)&&(k=`$${g.original}`),w!==k&&(w?u.push(g.original):f.push(g.original),s[g.original]=k)}if(f.length===0&&u.length===0)return{added:f,updated:u};const x=Object.fromEntries(Object.entries(s).sort(([g],[w])=>g.localeCompare(w)));return y==="pnpm-workspace.yaml"?Be(r,x):Ue(e,o,x,n.name),{added:f,updated:u}},"applyOverrides"),Xe=T((r,e)=>{const t={bun:["bun.lock"],npm:["npm-shrinkwrap.json","package-lock.json"],pnpm:["pnpm-lock.yaml"],yarn:["yarn.lock"]};for(const n of t[e]??[]){const a=A(r,n);try{return _(a)}catch{continue}}return""},"readLockfileText"),He=T((r,e,t)=>{if(!r)return!1;const n=e.replaceAll(/[.*+?^${}()|[\]\\]/g,String.raw`\$&`);switch(t){case"bun":return r.includes(`"${e}":`)||new RegExp(String.raw`(^|\s|[",])${n}@`,"m").test(r);case"npm":return r.includes(`"${e}":`)||r.includes(`"node_modules/${e}":`);case"pnpm":return new RegExp(String.raw`(^|\s|['"/])${n}(@|['"]?:)`,"m").test(r);case"yarn":return new RegExp(String.raw`(^|\s|[",])${n}@`,"m").test(r);default:return!1}},"lockfileContainsPackage");var We=Object.defineProperty,M=P((r,e)=>We(r,"name",{value:e,configurable:!0}),"s");const Ye=M((r,e,t)=>{let n=r;if(e!=="all"&&(n=n.filter(a=>a.category===e)),t){const a=t.toLowerCase();n=n.filter(o=>o.packageName.toLowerCase().includes(a))}return n},"filterEntries");class Ke{static{P(this,"h")}static{M(this,"OptimizeStore")}#e;#t=new Set;constructor(e){this.#e={applyProgress:null,checkedEntries:new Set,entries:e,error:null,filterActive:!1,filterText:"",filterType:"all",focusedPanel:"list",phase:"browsing",selectedIndex:0}}getSnapshot=M(()=>this.#e,"getSnapshot");subscribe=M(e=>(this.#t.add(e),()=>{this.#t.delete(e)}),"subscribe");getFilteredEntries=M(()=>Ye(this.#e.entries,this.#e.filterType,this.#e.filterText),"getFilteredEntries");#r(){this.#e={...this.#e};for(const e of this.#t)e()}select(e){const t=this.getFilteredEntries();this.#e.selectedIndex=t.length===0?-1:Math.max(0,Math.min(e,t.length-1)),this.#r()}toggleCheck(e){const t=new Set(this.#e.checkedEntries);t.has(e)?t.delete(e):t.add(e),this.#e.checkedEntries=t,this.#r()}toggleAll(){const e=this.getFilteredEntries(),t=new Set(this.#e.checkedEntries);if(e.every(n=>t.has(n.packageName)))for(const n of e)t.delete(n.packageName);else for(const n of e)t.add(n.packageName);this.#e.checkedEntries=t,this.#r()}setFilter(e){this.#e.filterType=e,this.#e.selectedIndex=0,this.#r()}setFilterText(e){this.#e.filterText=e,this.#e.selectedIndex=0,this.#r()}setFilterActive(e){this.#e.filterActive=e,this.#r()}setFocusedPanel(e){this.#e.focusedPanel=e,this.#r()}setPhase(e){this.#e.phase=e,this.#r()}setProgress(e,t){this.#e.applyProgress={current:e,total:t},this.#r()}setError(e){this.#e.error=e,this.#e.phase="error",this.#r()}getCheckedEntries(){return this.#e.entries.filter(e=>this.#e.checkedEntries.has(e.packageName))}}const ee={"micro-utility":"gray",native:"green",preferred:"yellow",socket:"cyan"},Ze={"micro-utility":"MICRO",native:"NATIVE",preferred:"PREF",socket:"SOCKET"},Qe={"micro-utility":"Trivial utility package that can be replaced with inline code.",native:"Polyfill for a native JS/Node.js API. Use the built-in instead.",preferred:"A lighter or faster alternative package exists.",socket:"Security-hardened replacement from Socket.dev's @socketregistry."};var er=Object.defineProperty,rr=P((r,e)=>er(r,"name",{value:e,configurable:!0}),"d");const tr=rr(({entry:r,focused:e,scrollRef:t})=>{const n=e?"white":"gray";if(!r)return i(p,{alignItems:"center",borderColor:"gray",borderStyle:"single",flexDirection:"column",flexGrow:1,justifyContent:"center",children:i(c,{dimColor:!0,children:"No entry selected"})});const a=ee[r.category]??"gray";return d(p,{borderColor:n,borderStyle:"single",flexDirection:"column",flexGrow:1,children:[i(p,{flexShrink:0,paddingTop:1,paddingX:2,children:i(c,{bold:!0,color:"white",children:r.packageName})}),d(ge,{flexGrow:1,flexShrink:1,paddingX:2,ref:t,scrollbar:!0,scrollbarColor:"gray",children:[i(c,{}),d(p,{children:[i(p,{width:14,children:i(c,{dimColor:!0,children:"Category:"})}),i(c,{bold:!0,color:a,children:r.category})]}),d(p,{children:[i(p,{width:14,children:i(c,{dimColor:!0,children:"Replace with:"})}),i(c,{children:r.replacement})]}),r.overrideSpec&&d(p,{children:[i(p,{width:14,children:i(c,{dimColor:!0,children:"Override:"})}),i(c,{color:"cyan",children:r.overrideSpec})]}),d(p,{children:[i(p,{width:14,children:i(c,{dimColor:!0,children:"Codemod:"})}),i(c,{color:r.hasCodemod?"green":"gray",children:r.hasCodemod?"available ⚙":"not available"})]}),d(p,{flexDirection:"column",marginTop:1,children:[i(c,{dimColor:!0,children:"── "}),i(c,{bold:!0,color:"white",children:"DESCRIPTION"}),i(p,{marginTop:1,paddingLeft:2,children:i(c,{children:Qe[r.category]??""})})]}),r.category==="native"&&d(p,{flexDirection:"column",marginTop:1,children:[i(c,{dimColor:!0,children:"── "}),i(c,{bold:!0,color:"green",children:"ACTION"}),i(p,{flexDirection:"column",marginTop:1,paddingLeft:2,children:r.hasCodemod?d(q,{children:[d(c,{color:"green",children:["✓"," ","Codemod will rewrite imports to use native API."]}),i(c,{dimColor:!0,children:" The package can then be removed from dependencies."})]}):d(q,{children:[d(c,{color:"yellow",children:["ℹ"," ","No automated codemod available."]}),i(c,{dimColor:!0,children:" Manual migration required — replace usage with native equivalent."})]})})]}),r.category==="socket"&&d(p,{flexDirection:"column",marginTop:1,children:[i(c,{dimColor:!0,children:"── "}),i(c,{bold:!0,color:"cyan",children:"ACTION"}),d(p,{flexDirection:"column",marginTop:1,paddingLeft:2,children:[d(c,{color:"cyan",children:["✓"," ","Override will redirect resolution to the hardened package."]}),i(c,{dimColor:!0,children:" No source code changes needed — drop-in replacement."})]})]}),(r.category==="preferred"||r.category==="micro-utility")&&d(p,{flexDirection:"column",marginTop:1,children:[i(c,{dimColor:!0,children:"── "}),i(c,{bold:!0,color:"yellow",children:"ACTION"}),i(p,{flexDirection:"column",marginTop:1,paddingLeft:2,children:r.hasCodemod?d(q,{children:[d(c,{color:"green",children:["✓"," ","Codemod will rewrite imports to the recommended alternative."]}),i(c,{dimColor:!0,children:" The original package can then be removed from dependencies."})]}):d(q,{children:[d(c,{color:"yellow",children:["ℹ"," ","Manual migration required."]}),r.docUrl?i(c,{dimColor:!0,children:" Open the migration guide below for the recommended alternative and steps."}):i(c,{dimColor:!0,children:" Consult the package's docs or the e18e module-replacements guide for an alternative."})]})})]}),d(p,{flexDirection:"column",marginTop:1,children:[i(c,{dimColor:!0,children:"── "}),i(c,{bold:!0,color:"white",children:"LINKS"}),d(p,{flexDirection:"column",marginTop:1,paddingLeft:2,children:[d(c,{color:"cyan",underline:!0,children:["https://npmx.dev/",r.packageName]}),r.docUrl&&i(c,{color:"cyan",underline:!0,children:r.docUrl})]})]})]})]})},"OptimizeDetailPanel");var nr=Object.defineProperty,re=P((r,e)=>nr(r,"name",{value:e,configurable:!0}),"b$1");const ir=[{key:"all",label:"All",shortcut:"1"},{key:"native",label:"Native",shortcut:"2"},{key:"preferred",label:"Preferred",shortcut:"3"},{key:"micro-utility",label:"Micro",shortcut:"4"},{key:"socket",label:"Socket",shortcut:"5"}],or=re(({checked:r,entry:e,isSelected:t})=>{const n=ee[e.category]??"white",a=Ze[e.category]??e.category,o=r?"☑":"☐",s=e.hasCodemod?"⚙":" ";return d(p,{flexShrink:0,height:1,children:[i(c,{children:t?">":" "}),d(c,{color:r?"white":"gray",children:[" ",o," "]}),i(c,{bold:!0,color:n,children:`[${a}]`.padEnd(9)}),d(c,{children:[" ",s," "]}),i(p,{flexGrow:1,children:i(c,{bold:t,inverse:t,wrap:"truncate",children:e.packageName})}),i(c,{dimColor:!0,children:" → "}),i(c,{wrap:"truncate",children:e.replacement})]})},"EntryRow"),cr=re(({checkedEntries:r,entries:e,filterActive:t,filterText:n,filterType:a,focused:o,isDryRun:s,scrollOffset:y,selectedIndex:f,totalEntries:u,viewportHeight:S})=>{const x=o?"white":"gray";let g=0,w=0,k=0,N=0;for(const O of e)switch(O.category){case"micro-utility":{k++;break}case"native":{g++;break}case"preferred":{w++;break}case"socket":{N++;break}}const h=[];g>0&&h.push(`${g} native`),w>0&&h.push(`${w} preferred`),k>0&&h.push(`${k} micro`),N>0&&h.push(`${N} socket`);const b=h.length>0?` (${h.join(", ")})`:"",C=r.size,D=[];for(const[O,$]of e.entries())D.push(i(or,{checked:r.has($.packageName),entry:$,isSelected:O===f},$.packageName));const I=e.length,l=I>S&&S>0;return d(p,{borderColor:x,borderStyle:"single",flexDirection:"column",flexGrow:1,children:[d(p,{flexShrink:0,gap:1,paddingX:1,children:[i(c,{bold:!0,inverse:!0,children:" VIS OPTIMIZE "}),d(c,{wrap:"truncate",children:[u," ","optimizations",b]}),!s&&C>0&&d(c,{dimColor:!0,children:[" ","—",C," ","selected"]})]}),i(p,{flexShrink:0,gap:1,paddingX:1,paddingY:1,children:ir.map(O=>{const $=a===O.key;return d(p,{children:[i(c,{dimColor:!$,children:"["}),i(c,{bold:$,color:$?"cyan":"gray",children:O.shortcut}),i(c,{dimColor:!$,children:"]"}),d(c,{color:$?"white":"gray",children:[" ",O.label]})]},O.key)})}),t&&d(p,{flexShrink:0,paddingX:1,children:[i(c,{bold:!0,color:"white",children:"/ "}),i(c,{children:n}),i(c,{inverse:!0,children:" "})]}),d(p,{flexDirection:"row",flexGrow:1,overflow:"hidden",children:[i(p,{flexDirection:"column",flexGrow:1,overflow:"hidden",paddingLeft:1,children:i(p,{flexDirection:"column",marginTop:-y,children:D})}),l&&i(p,{flexShrink:0,marginLeft:1,marginRight:1,children:i(ye,{contentHeight:I,placement:"inset",scrollOffset:y,style:"block",viewportHeight:S})})]})]})},"OptimizeListPanel");var sr=Object.defineProperty,ar=P((r,e)=>sr(r,"name",{value:e,configurable:!0}),"S");const lr=100,Y=10,K={1:"all",2:"native",3:"preferred",4:"micro-utility",5:"socket"},dr=ar(({isDryRun:r,store:e})=>{const{exit:t}=ke(),{columns:n,rows:a}=ve(),o=Pe(e.subscribe,e.getSnapshot),s=Ae(null),[y,f]=H(0),[u,S]=H(!1),x=e.getFilteredEntries(),g=x[o.selectedIndex]??null,w=n>=lr,k=Math.max(0,a-5),N=De(h=>{t(h)},[t]);return we((h,b)=>{if(u){h==="y"||h==="Y"?N():S(!1);return}if(o.filterActive){b.escape?(e.setFilterActive(!1),e.setFilterText("")):b.return?e.setFilterActive(!1):b.backspace||b.delete?e.setFilterText(o.filterText.slice(0,-1)):h&&!b.ctrl&&!b.meta&&e.setFilterText(o.filterText+h);return}if(h==="q"){!r&&o.checkedEntries.size>0?S(!0):N();return}if(h==="/"){e.setFilterActive(!0);return}if(K[h]){e.setFilter(K[h]);return}if(b.tab){e.setFocusedPanel(o.focusedPanel==="list"?"detail":"list");return}if(o.focusedPanel==="list")if(b.upArrow||h==="k"){const C=Math.max(0,o.selectedIndex-1);e.select(C),C<y&&f(C)}else if(b.downArrow||h==="j"){const C=Math.min(x.length-1,o.selectedIndex+1);e.select(C),C>=y+k&&f(C-k+1)}else h===" "?g&&e.toggleCheck(g.packageName):h==="a"?e.toggleAll():b.return&&!r&&o.checkedEntries.size>0&&N(e.getCheckedEntries());else o.focusedPanel==="detail"&&(b.upArrow||h==="k"?s.current?.scrollBy(-1):(b.downArrow||h==="j")&&s.current?.scrollBy(1))},{isActive:o.phase==="browsing"}),a<Y?i(p,{alignItems:"center",justifyContent:"center",children:d(c,{color:"yellow",children:["Terminal too small. Resize to at least",Y," ","rows."]})}):d(p,{flexDirection:"column",height:a,width:n,children:[d(p,{flexDirection:w?"row":"column",flexGrow:1,children:[i(p,{flexBasis:w?"50%":void 0,flexGrow:1,children:i(cr,{checkedEntries:o.checkedEntries,entries:x,filterActive:o.filterActive,filterText:o.filterText,filterType:o.filterType,focused:o.focusedPanel==="list",isDryRun:r,scrollOffset:y,selectedIndex:o.selectedIndex,totalEntries:o.entries.length,viewportHeight:k})}),i(p,{flexBasis:w?"50%":void 0,flexGrow:1,children:i(tr,{entry:g,focused:o.focusedPanel==="detail",scrollRef:s})})]}),i(p,{flexShrink:0,paddingX:1,children:d(c,{dimColor:!0,children:[r?"Preview mode":"space:toggle a:all enter:apply"," ","| tab:switch panel /: filter q:quit |","⚙","=codemod available"]})}),i($e,{autoExitSeconds:3,onCancel:P(()=>{S(!1)},"onCancel"),visible:u})]})},"VisOptimizeApp");var pr=Object.defineProperty,j=P((r,e)=>pr(r,"name",{value:e,configurable:!0}),"p");const U=j((r,e)=>{const t=new Set;try{const n=Z(r),a=e?[n.dependencies,n.optionalDependencies]:[n.dependencies,n.devDependencies,n.peerDependencies,n.optionalDependencies];for(const o of a)if(o)for(const s of Object.keys(o))t.add(s)}catch{}return t},"collectDepsFromPkgJson"),te=j(r=>{const e=xe(r);if(e)return X(r,e);const t=A(r,"package.json");if(!V(t))return[];try{const n=Z(t),a=Array.isArray(n.workspaces)?n.workspaces:n.workspaces?.packages;return a?X(r,a):[]}catch{return[]}},"discoverWorkspacePackages"),fr="https://github.com/es-tooling/module-replacements/blob/main/docs/modules",mr=j(r=>`${fr}/${r}.md`,"buildE18eDocUrl"),hr=j(r=>{if(r.type==="simple"&&r.replacement)return r.replacement;if(r.type==="native"){const e=r.nodeVersion?` (Node ${r.nodeVersion}+)`:"";return r.replacement?`${r.replacement}${e}`:`Native API${e}`}return r.type==="documented"&&r.docPath?"see migration guide":""},"e18eReplacementHint"),ne=j(r=>{const e=[],t=j((n,a)=>{const o=n.moduleReplacements;if(Array.isArray(o))for(const s of o)r.has(s.moduleName)&&e.push({category:a,docUrl:s.type==="documented"&&s.docPath?mr(s.docPath):void 0,hasCodemod:!1,overrideSpec:void 0,packageName:s.moduleName,replacement:hr(s)})},"scanManifest");return t(je,"native"),t(Te,"preferred"),t(Oe,"micro-utility"),e},"buildE18eEntries"),ie=j((r,e,t,n)=>{const a=me("npm")??[],o=[];for(const[,s]of a){if(s.deprecated)continue;const y=r.has(s.package),f=e?He(e,s.package,t.name):!1;if(!y&&!f)continue;const u=Q(s.version)?.major;if(u===void 0)continue;const S=n?`npm:${s.name}@${s.version}`:`npm:${s.name}@^${String(u)}`;o.push({category:"socket",hasCodemod:!1,overrideSpec:S,packageName:s.package,replacement:s.name})}return o},"buildSocketEntries");let G;const oe=j(async()=>{if(G)return G;try{return G=(await import("module-replacements-codemods")).codemods,G}catch{return{}}},"loadCodemods"),ce=j(async r=>{try{const e=await oe();for(const t of r)t.category!=="socket"&&e[t.packageName]&&(t.hasCodemod=!0)}catch{}},"markCodemodAvailability"),se=j(async(r,e)=>{let t=0;try{const n=(await oe())[e];if(!n)return{filesChanged:0,packageName:e};const a=n({}),o=await ur(r);for(const s of o){const y=_(s);if(y.includes(e))try{const f=await a.transform({file:{filename:s,source:y}});f!==y&&(fe(s,f,"utf8"),t++)}catch(f){process.stderr.write(`warn: codemod transform failed for ${s}: ${f instanceof Error?f.message:String(f)}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
1
|
+
import { createRequire as __cjs_createRequire } from "node:module";
|
|
2
|
+
|
|
3
|
+
const __cjs_require = __cjs_createRequire(import.meta.url);
|
|
4
|
+
|
|
5
|
+
const __cjs_getProcess = typeof globalThis !== "undefined" && typeof globalThis.process !== "undefined" ? globalThis.process : process;
|
|
6
|
+
|
|
7
|
+
const __cjs_getBuiltinModule = (module) => {
|
|
8
|
+
// Check if we're in Node.js and version supports getBuiltinModule
|
|
9
|
+
if (typeof __cjs_getProcess !== "undefined" && __cjs_getProcess.versions && __cjs_getProcess.versions.node) {
|
|
10
|
+
const [major, minor] = __cjs_getProcess.versions.node.split(".").map(Number);
|
|
11
|
+
// Node.js 20.16.0+ and 22.3.0+
|
|
12
|
+
if (major > 22 || (major === 22 && minor >= 3) || (major === 20 && minor >= 16)) {
|
|
13
|
+
return __cjs_getProcess.getBuiltinModule(module);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
// Fallback to createRequire
|
|
17
|
+
return __cjs_require(module);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
import { dim, cyan, yellow, red, green } from '@visulima/colorize';
|
|
21
|
+
import { writeFileSync, isAccessibleSync, readJsonSync, readFileSync } from '@visulima/fs';
|
|
22
|
+
import { isAbsolute, relative, resolve, join } from '@visulima/path';
|
|
23
|
+
import { redact } from '@visulima/redact';
|
|
24
|
+
import { fingerprint, scan, scanFiles, inspectRuleset, listRules, listRequiredValidators } from '@visulima/secret-scanner';
|
|
25
|
+
import { p as pail } from './bin.js';
|
|
26
|
+
import { InteractiveManager, InteractiveStreamHook } from '@visulima/interactive-manager';
|
|
27
|
+
import { Spinner } from '@visulima/spinner';
|
|
28
|
+
const {
|
|
29
|
+
pathToFileURL
|
|
30
|
+
} = __cjs_getBuiltinModule("node:url");
|
|
31
|
+
const {
|
|
32
|
+
execFileSync
|
|
33
|
+
} = __cjs_getBuiltinModule("node:child_process");
|
|
34
|
+
|
|
35
|
+
const createSpinner = (options = {}) => {
|
|
36
|
+
const manager = new InteractiveManager(new InteractiveStreamHook(process.stdout), new InteractiveStreamHook(process.stderr));
|
|
37
|
+
return new Spinner(options, manager);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const toRelative = (file, root) => {
|
|
41
|
+
if (!isAbsolute(file)) {
|
|
42
|
+
return file;
|
|
43
|
+
}
|
|
44
|
+
const rel = relative(root, file);
|
|
45
|
+
return rel === "" || rel.startsWith("..") ? file : rel;
|
|
46
|
+
};
|
|
47
|
+
const toRelativeFinding = (f, root) => {
|
|
48
|
+
const relativeFile = toRelative(f.file, root);
|
|
49
|
+
return relativeFile === f.file ? f : { ...f, file: relativeFile };
|
|
50
|
+
};
|
|
51
|
+
const readBaseline = (baselinePath) => {
|
|
52
|
+
if (!isAccessibleSync(baselinePath)) {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
const parsed = readJsonSync(baselinePath);
|
|
57
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
58
|
+
} catch {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
const diffBaseline = (findings, baselinePath, root) => {
|
|
63
|
+
const existing = readBaseline(baselinePath).map((f) => toRelativeFinding(f, root));
|
|
64
|
+
const existingKeys = new Set(existing.map((f) => fingerprint(f)));
|
|
65
|
+
const currentRelative = findings.map((f) => toRelativeFinding(f, root));
|
|
66
|
+
const currentKeys = new Set(currentRelative.map((f) => fingerprint(f)));
|
|
67
|
+
return {
|
|
68
|
+
fresh: currentRelative.filter((f) => !existingKeys.has(fingerprint(f))),
|
|
69
|
+
resolved: existing.filter((f) => !currentKeys.has(fingerprint(f))),
|
|
70
|
+
surviving: currentRelative.filter((f) => existingKeys.has(fingerprint(f)))
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
const writeBaseline = (findings, baselinePath, root, options = {}) => {
|
|
74
|
+
const incoming = findings.map((f) => toRelativeFinding(f, root));
|
|
75
|
+
let final;
|
|
76
|
+
if (options.replace) {
|
|
77
|
+
final = incoming;
|
|
78
|
+
} else {
|
|
79
|
+
const existing = readBaseline(baselinePath).map((f) => toRelativeFinding(f, root));
|
|
80
|
+
const seen = /* @__PURE__ */ new Set();
|
|
81
|
+
final = [];
|
|
82
|
+
for (const f of [...existing, ...incoming]) {
|
|
83
|
+
const key = fingerprint(f);
|
|
84
|
+
if (seen.has(key)) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
seen.add(key);
|
|
88
|
+
final.push(f);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
writeFileSync(baselinePath, `${JSON.stringify(final, null, 4)}
|
|
92
|
+
`);
|
|
93
|
+
return final.length;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const CONTEXT_RADIUS = 1;
|
|
97
|
+
const groupByFile = (findings) => {
|
|
98
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
99
|
+
for (const f of findings) {
|
|
100
|
+
const list = byFile.get(f.file);
|
|
101
|
+
if (list) {
|
|
102
|
+
list.push(f);
|
|
103
|
+
} else {
|
|
104
|
+
byFile.set(f.file, [f]);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return byFile;
|
|
108
|
+
};
|
|
109
|
+
const loadLines = (file) => {
|
|
110
|
+
try {
|
|
111
|
+
return readFileSync(file).split(/\r?\n/);
|
|
112
|
+
} catch {
|
|
113
|
+
return void 0;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
const caretFor = (line, col, len) => {
|
|
117
|
+
const clampedCol = Math.max(1, col);
|
|
118
|
+
const prefix = line.slice(0, clampedCol - 1).replaceAll(/[^\t]/g, " ");
|
|
119
|
+
return `${prefix}${"^".repeat(Math.max(1, len))}`;
|
|
120
|
+
};
|
|
121
|
+
const formatText = (findings, root, useColor, options = {}) => {
|
|
122
|
+
if (findings.length === 0) {
|
|
123
|
+
return useColor ? dim("No secrets detected.") : "No secrets detected.";
|
|
124
|
+
}
|
|
125
|
+
const color = useColor ? { cyan, dim, green, red, yellow } : {
|
|
126
|
+
cyan: (s) => s,
|
|
127
|
+
dim: (s) => s,
|
|
128
|
+
green: (s) => s,
|
|
129
|
+
red: (s) => s,
|
|
130
|
+
yellow: (s) => s
|
|
131
|
+
};
|
|
132
|
+
const lines = [];
|
|
133
|
+
const byFile = groupByFile(findings);
|
|
134
|
+
for (const [file, items] of byFile) {
|
|
135
|
+
const relativeFile = relative(root, file) || file;
|
|
136
|
+
lines.push(color.cyan(relativeFile));
|
|
137
|
+
const sourceLines = options.redact ? void 0 : loadLines(file);
|
|
138
|
+
for (const f of items) {
|
|
139
|
+
const provenance = [f.source, f.confidence].filter(Boolean).join(", ");
|
|
140
|
+
const provenanceSuffix = provenance ? ` ${color.dim(`(${provenance})`)}` : "";
|
|
141
|
+
const alternates = f.alternateMatches && f.alternateMatches.length > 0 ? ` ${color.dim(`also: ${f.alternateMatches.join(", ")}`)}` : "";
|
|
142
|
+
let validationBadge = "";
|
|
143
|
+
switch (f.validation) {
|
|
144
|
+
case "error": {
|
|
145
|
+
validationBadge = ` ${color.yellow("! error")}`;
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
case "rejected": {
|
|
149
|
+
validationBadge = ` ${color.red("✗ rejected")}`;
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
case "skipped": {
|
|
153
|
+
validationBadge = ` ${color.dim("— unverifiable")}`;
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
case "verified": {
|
|
157
|
+
validationBadge = ` ${color.green("✓ verified")}`;
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
lines.push(
|
|
162
|
+
` ${color.red("✖")} ${color.yellow(`[${f.ruleId}]`)}${provenanceSuffix}${validationBadge} ${color.dim(`line ${String(f.startLine)}:${String(f.startColumn)}`)}${alternates}`
|
|
163
|
+
);
|
|
164
|
+
if (sourceLines) {
|
|
165
|
+
const start = Math.max(0, f.startLine - 1 - CONTEXT_RADIUS);
|
|
166
|
+
const end = Math.min(sourceLines.length, f.startLine + CONTEXT_RADIUS);
|
|
167
|
+
for (let n = start; n < end; n += 1) {
|
|
168
|
+
const lineNumber = String(n + 1).padStart(4, " ");
|
|
169
|
+
const isMatchLine = n + 1 === f.startLine;
|
|
170
|
+
const marker = isMatchLine ? color.red("▶") : " ";
|
|
171
|
+
lines.push(` ${marker} ${color.dim(lineNumber)} │ ${sourceLines[n] ?? ""}`);
|
|
172
|
+
if (isMatchLine) {
|
|
173
|
+
const matchLen = Math.max(1, (f.endColumn ?? f.startColumn + 1) - f.startColumn);
|
|
174
|
+
const caret = caretFor(sourceLines[n] ?? "", f.startColumn, matchLen);
|
|
175
|
+
lines.push(` ${color.dim(" │ ")}${color.red(caret)}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
lines.push("");
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return lines.join("\n").trimEnd();
|
|
183
|
+
};
|
|
184
|
+
const toSarifUri = (path, root) => {
|
|
185
|
+
if (!isAbsolute(path)) {
|
|
186
|
+
return path.replaceAll("\\", "/");
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
return pathToFileURL(path).toString();
|
|
190
|
+
} catch {
|
|
191
|
+
return `file://${resolve(root, path).replaceAll("\\", "/")}`;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
const shortText = (text, limit = 100) => {
|
|
195
|
+
if (text.length <= limit) {
|
|
196
|
+
return text;
|
|
197
|
+
}
|
|
198
|
+
return `${text.slice(0, limit - 1).trimEnd()}…`;
|
|
199
|
+
};
|
|
200
|
+
const formatSarif = (findings, toolVersion, root = process.cwd(), ruleMetadata = []) => {
|
|
201
|
+
const metaById = new Map(ruleMetadata.map((r) => [r.id, r]));
|
|
202
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
203
|
+
for (const f of findings) {
|
|
204
|
+
seenIds.add(f.ruleId);
|
|
205
|
+
}
|
|
206
|
+
const ruleIds = [.../* @__PURE__ */ new Set([...metaById.keys(), ...seenIds])].sort((a, b) => a.localeCompare(b));
|
|
207
|
+
const ruleIndex = new Map(ruleIds.map((id, i) => [id, i]));
|
|
208
|
+
return JSON.stringify(
|
|
209
|
+
{
|
|
210
|
+
$schema: "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0.json",
|
|
211
|
+
runs: [
|
|
212
|
+
{
|
|
213
|
+
originalUriBaseIds: {
|
|
214
|
+
SRCROOT: { uri: pathToFileURL(root).toString() }
|
|
215
|
+
},
|
|
216
|
+
results: findings.map((f) => {
|
|
217
|
+
const properties = {};
|
|
218
|
+
if (f.source) {
|
|
219
|
+
properties["source"] = f.source;
|
|
220
|
+
}
|
|
221
|
+
if (f.confidence) {
|
|
222
|
+
properties["confidence"] = f.confidence;
|
|
223
|
+
}
|
|
224
|
+
if (f.alternateMatches && f.alternateMatches.length > 0) {
|
|
225
|
+
properties["alternateRules"] = f.alternateMatches;
|
|
226
|
+
}
|
|
227
|
+
if (f.validation) {
|
|
228
|
+
properties["validation"] = f.validation;
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
level: "error",
|
|
232
|
+
locations: [
|
|
233
|
+
{
|
|
234
|
+
physicalLocation: {
|
|
235
|
+
artifactLocation: {
|
|
236
|
+
uri: toSarifUri(f.file, root),
|
|
237
|
+
uriBaseId: isAbsolute(f.file) ? void 0 : "SRCROOT"
|
|
238
|
+
},
|
|
239
|
+
region: {
|
|
240
|
+
endColumn: f.endColumn,
|
|
241
|
+
endLine: f.endLine,
|
|
242
|
+
snippet: { text: f.match },
|
|
243
|
+
startColumn: f.startColumn,
|
|
244
|
+
startLine: f.startLine
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
],
|
|
249
|
+
message: { text: f.description || f.ruleId },
|
|
250
|
+
properties: Object.keys(properties).length > 0 ? properties : void 0,
|
|
251
|
+
ruleId: f.ruleId,
|
|
252
|
+
ruleIndex: ruleIndex.get(f.ruleId) ?? -1
|
|
253
|
+
};
|
|
254
|
+
}),
|
|
255
|
+
tool: {
|
|
256
|
+
driver: {
|
|
257
|
+
informationUri: "https://visulima.com/packages/secret-scanner",
|
|
258
|
+
name: "visulima-secret-scanner",
|
|
259
|
+
rules: ruleIds.map((id) => {
|
|
260
|
+
const meta = metaById.get(id);
|
|
261
|
+
const description = meta?.description ?? `Detected by rule \`${id}\``;
|
|
262
|
+
const ruleProperties = {};
|
|
263
|
+
if (meta?.tags && meta.tags.length > 0) {
|
|
264
|
+
ruleProperties["tags"] = meta.tags;
|
|
265
|
+
}
|
|
266
|
+
if (meta?.source) {
|
|
267
|
+
ruleProperties["source"] = meta.source;
|
|
268
|
+
}
|
|
269
|
+
if (meta?.confidence) {
|
|
270
|
+
ruleProperties["confidence"] = meta.confidence;
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
defaultConfiguration: { level: "error" },
|
|
274
|
+
fullDescription: { text: description },
|
|
275
|
+
helpUri: "https://visulima.com/packages/secret-scanner",
|
|
276
|
+
id,
|
|
277
|
+
name: id,
|
|
278
|
+
properties: Object.keys(ruleProperties).length > 0 ? ruleProperties : void 0,
|
|
279
|
+
shortDescription: { text: shortText(description) }
|
|
280
|
+
};
|
|
281
|
+
}),
|
|
282
|
+
version: toolVersion
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
],
|
|
287
|
+
version: "2.1.0"
|
|
288
|
+
},
|
|
289
|
+
void 0,
|
|
290
|
+
2
|
|
291
|
+
);
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const runGit = (root, args) => {
|
|
295
|
+
try {
|
|
296
|
+
return execFileSync("git", args, { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
297
|
+
} catch {
|
|
298
|
+
return "";
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
const splitFiles = (stdout) => stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
302
|
+
const stagedFiles = (root) => splitFiles(runGit(root, ["diff", "--cached", "--name-only", "--diff-filter=ACMR"])).map((p) => isAbsolute(p) ? p : join(root, p));
|
|
303
|
+
const filesSince = (root, ref) => {
|
|
304
|
+
const stdout = runGit(root, ["diff", "--name-only", "--diff-filter=ACMR", `${ref}...HEAD`]);
|
|
305
|
+
const list = splitFiles(stdout);
|
|
306
|
+
if (list.length === 0) {
|
|
307
|
+
const fallback = runGit(root, ["diff", "--name-only", "--diff-filter=ACMR", ref]);
|
|
308
|
+
return splitFiles(fallback).map((p) => isAbsolute(p) ? p : join(root, p));
|
|
309
|
+
}
|
|
310
|
+
return list.map((p) => isAbsolute(p) ? p : join(root, p));
|
|
311
|
+
};
|
|
312
|
+
const hasGit = (root) => runGit(root, ["rev-parse", "--show-toplevel"]).length > 0;
|
|
313
|
+
|
|
314
|
+
const DEFAULT_BASELINE = ".secrets-baseline.json";
|
|
315
|
+
const toArray = (value) => {
|
|
316
|
+
if (!value) {
|
|
317
|
+
return [];
|
|
318
|
+
}
|
|
319
|
+
return Array.isArray(value) ? value : [value];
|
|
320
|
+
};
|
|
321
|
+
const validateFormat = (raw) => {
|
|
322
|
+
const allowed = /* @__PURE__ */ new Set(["json", "sarif", "text"]);
|
|
323
|
+
if (raw && !allowed.has(raw)) {
|
|
324
|
+
pail.error(`--format must be one of: ${[...allowed].join(", ")} (got "${raw}")`);
|
|
325
|
+
process.exit(2);
|
|
326
|
+
}
|
|
327
|
+
return raw ?? "text";
|
|
328
|
+
};
|
|
329
|
+
const validateConfidence = (raw) => {
|
|
330
|
+
if (raw === void 0) {
|
|
331
|
+
return void 0;
|
|
332
|
+
}
|
|
333
|
+
const allowed = /* @__PURE__ */ new Set(["high", "low", "medium"]);
|
|
334
|
+
if (!allowed.has(raw)) {
|
|
335
|
+
pail.error(`--min-confidence must be one of: ${[...allowed].join(", ")} (got "${raw}")`);
|
|
336
|
+
process.exit(2);
|
|
337
|
+
}
|
|
338
|
+
return raw;
|
|
339
|
+
};
|
|
340
|
+
const printListRules = async (scanOptions, useColor) => {
|
|
341
|
+
const rules = await listRules(scanOptions);
|
|
342
|
+
process.stdout.write(`${String(rules.length)} rules loaded
|
|
343
|
+
|
|
344
|
+
`);
|
|
345
|
+
for (const rule of rules) {
|
|
346
|
+
const id = useColor ? yellow(rule.id) : rule.id;
|
|
347
|
+
const tags = rule.tags.length > 0 ? ` ${useColor ? dim(`[${rule.tags.join(", ")}]`) : `[${rule.tags.join(", ")}]`}` : "";
|
|
348
|
+
process.stdout.write(`${id}${tags}
|
|
349
|
+
${rule.description}
|
|
350
|
+
`);
|
|
351
|
+
if (rule.keywords.length > 0) {
|
|
352
|
+
const kw = `keywords: ${rule.keywords.slice(0, 6).join(", ")}${rule.keywords.length > 6 ? ", ..." : ""}`;
|
|
353
|
+
process.stdout.write(` ${useColor ? dim(kw) : kw}
|
|
354
|
+
`);
|
|
355
|
+
}
|
|
356
|
+
process.stdout.write("\n");
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
const printListValidators = async (scanOptions, useColor) => {
|
|
360
|
+
const report = await listRequiredValidators(scanOptions);
|
|
361
|
+
if (report.length === 0) {
|
|
362
|
+
process.stdout.write(
|
|
363
|
+
useColor ? `${dim("No non-HTTP validators required by the current ruleset.")}
|
|
364
|
+
` : "No non-HTTP validators required by the current ruleset.\n"
|
|
365
|
+
);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
process.stdout.write(`${String(report.length)} non-HTTP validator type(s) referenced by the current ruleset:
|
|
369
|
+
|
|
370
|
+
`);
|
|
371
|
+
for (const entry of report) {
|
|
372
|
+
const title = useColor ? yellow(entry.displayName) : entry.displayName;
|
|
373
|
+
const typeLabel = `(${entry.type}, ${String(entry.ruleCount)} rule${entry.ruleCount === 1 ? "" : "s"})`;
|
|
374
|
+
process.stdout.write(`${title} ${useColor ? dim(typeLabel) : typeLabel}
|
|
375
|
+
`);
|
|
376
|
+
process.stdout.write(` ${entry.summary}
|
|
377
|
+
`);
|
|
378
|
+
const hint = entry.packageName ? `install: npm add ${entry.packageName}` : "no driver — bespoke implementation required";
|
|
379
|
+
process.stdout.write(` ${useColor ? dim(hint) : hint}
|
|
380
|
+
|
|
381
|
+
`);
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
const runInit = async (root, scanOptions, dryRun) => {
|
|
385
|
+
const baselinePath = join(root, DEFAULT_BASELINE);
|
|
386
|
+
if (!dryRun && isAccessibleSync(baselinePath)) {
|
|
387
|
+
pail.warn(`Detected existing ${DEFAULT_BASELINE} — refusing to overwrite. Delete it first to re-init.`);
|
|
388
|
+
process.exit(1);
|
|
389
|
+
}
|
|
390
|
+
pail.info(dryRun ? "[dry-run] Previewing init — no files will be written." : "Scanning workspace to seed baseline…");
|
|
391
|
+
const spinner = createSpinner();
|
|
392
|
+
spinner.start("scanning");
|
|
393
|
+
let findings;
|
|
394
|
+
try {
|
|
395
|
+
findings = await scan([root], scanOptions);
|
|
396
|
+
spinner.succeed();
|
|
397
|
+
} catch (error) {
|
|
398
|
+
spinner.failed();
|
|
399
|
+
throw error;
|
|
400
|
+
}
|
|
401
|
+
if (dryRun) {
|
|
402
|
+
pail.info(`[dry-run] Would create ${DEFAULT_BASELINE} with ${String(findings.length)} finding(s).`);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
const count = writeBaseline(findings, baselinePath, root, { replace: true });
|
|
406
|
+
pail.success(`Wrote ${DEFAULT_BASELINE} (${String(count)} findings).`);
|
|
407
|
+
pail.notice("Commit it. Use `vis secrets --baseline .secrets-baseline.json` in CI. Add path patterns to .gitignore to exclude directories from scanning.");
|
|
408
|
+
};
|
|
409
|
+
const resolveScanOptions = (flags, visSecrets, root) => {
|
|
410
|
+
const cfg = visSecrets ?? {};
|
|
411
|
+
const resolvePath = (p) => p ? resolve(root, p) : void 0;
|
|
412
|
+
const pickList = (flag, fallback) => {
|
|
413
|
+
const fromFlag = toArray(flag);
|
|
414
|
+
return fromFlag.length > 0 ? fromFlag : fallback;
|
|
415
|
+
};
|
|
416
|
+
const enableRules = pickList(flags.enableRule, cfg.rules?.enable);
|
|
417
|
+
const excludeRules = pickList(flags.excludeRule, cfg.rules?.exclude);
|
|
418
|
+
const includeRules = pickList(flags.includeRule, cfg.rules?.include);
|
|
419
|
+
const excludePatterns = pickList(flags.exclude, cfg.walk?.excludePatterns);
|
|
420
|
+
const excludeFromFlag = toArray(flags.excludeFrom).map((p) => resolve(root, p));
|
|
421
|
+
const excludeFromFiles = excludeFromFlag.length > 0 ? excludeFromFlag : cfg.walk?.excludeFromFiles?.map((p) => resolve(root, p));
|
|
422
|
+
const baselinePath = resolvePath(flags.baseline) ?? resolvePath(cfg.baseline);
|
|
423
|
+
const configPath = resolvePath(flags.config) ?? resolvePath(cfg.config?.path);
|
|
424
|
+
const minConfidence = validateConfidence(flags.minConfidence ?? cfg.config?.minConfidence);
|
|
425
|
+
return {
|
|
426
|
+
baseline: baselinePath,
|
|
427
|
+
concurrency: flags.concurrency,
|
|
428
|
+
config: {
|
|
429
|
+
extendBundled: flags.noExtendBundled ? false : cfg.config?.extendBundled,
|
|
430
|
+
inline: cfg.config?.inline,
|
|
431
|
+
minConfidence,
|
|
432
|
+
onlyVerified: flags.onlyVerified ?? cfg.config?.onlyVerified ?? false,
|
|
433
|
+
path: configPath,
|
|
434
|
+
validate: flags.validate ?? cfg.config?.validate ?? false
|
|
435
|
+
},
|
|
436
|
+
redact: flags.redact ?? cfg.redact,
|
|
437
|
+
rules: { enable: enableRules, exclude: excludeRules, include: includeRules },
|
|
438
|
+
verbose: flags.verbose ?? false,
|
|
439
|
+
walk: {
|
|
440
|
+
excludeFromFiles,
|
|
441
|
+
excludePatterns,
|
|
442
|
+
gitignore: flags.noGitignore ? false : cfg.walk?.gitignore ?? true,
|
|
443
|
+
includeHidden: flags.includeHidden ?? cfg.walk?.includeHidden,
|
|
444
|
+
maxFileSize: flags.maxSize ?? cfg.walk?.maxFileSize
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
};
|
|
448
|
+
const printDiff = (diff) => {
|
|
449
|
+
process.stderr.write(
|
|
450
|
+
`${dim("baseline diff: ")}${green(`+${String(diff.fresh.length)} new`)} · ${yellow(`${String(diff.surviving.length)} unchanged`)} · ${dim(`-${String(diff.resolved.length)} resolved`)}
|
|
451
|
+
`
|
|
452
|
+
);
|
|
453
|
+
};
|
|
454
|
+
const chooseScanPaths = async (flags, args, root) => {
|
|
455
|
+
if (flags.staged) {
|
|
456
|
+
if (!hasGit(root)) {
|
|
457
|
+
pail.error("--staged requires a git working tree, and none was detected.");
|
|
458
|
+
process.exit(2);
|
|
459
|
+
}
|
|
460
|
+
return { files: stagedFiles(root) };
|
|
461
|
+
}
|
|
462
|
+
if (flags.since) {
|
|
463
|
+
if (!hasGit(root)) {
|
|
464
|
+
pail.error("--since requires a git working tree, and none was detected.");
|
|
465
|
+
process.exit(2);
|
|
466
|
+
}
|
|
467
|
+
return { files: filesSince(root, flags.since) };
|
|
468
|
+
}
|
|
469
|
+
if (flags.affected) {
|
|
470
|
+
if (!hasGit(root)) {
|
|
471
|
+
pail.warn("--affected requires git; falling back to full scan");
|
|
472
|
+
return { paths: args && args.length > 0 ? args.map((p) => resolve(root, p)) : [root] };
|
|
473
|
+
}
|
|
474
|
+
const baseRef = process.env["VIS_BASE"] ?? "HEAD~1";
|
|
475
|
+
return { files: filesSince(root, baseRef) };
|
|
476
|
+
}
|
|
477
|
+
return { paths: args && args.length > 0 ? args.map((p) => resolve(root, p)) : [root] };
|
|
478
|
+
};
|
|
479
|
+
const emitFindings = (findings, format, root, useColor, toolVersion, ruleMetadata, redactFindings) => {
|
|
480
|
+
switch (format) {
|
|
481
|
+
case "json": {
|
|
482
|
+
const view = findings.map((f) => toRelativeFinding(f, root));
|
|
483
|
+
process.stdout.write(`${JSON.stringify(view, null, 2)}
|
|
484
|
+
`);
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
case "sarif": {
|
|
488
|
+
process.stdout.write(`${formatSarif(findings, toolVersion, root, ruleMetadata)}
|
|
489
|
+
`);
|
|
490
|
+
break;
|
|
491
|
+
}
|
|
492
|
+
default: {
|
|
493
|
+
process.stdout.write(`${formatText(findings, root, useColor, { redact: redactFindings })}
|
|
494
|
+
`);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
const execute = async ({ argument, options, visConfig, workspaceRoot }) => {
|
|
499
|
+
const flags = options;
|
|
500
|
+
const args = argument;
|
|
501
|
+
const root = workspaceRoot ?? process.cwd();
|
|
502
|
+
const useColor = !flags.quiet && process.stdout.isTTY;
|
|
503
|
+
const visSecrets = visConfig?.secrets;
|
|
504
|
+
const scanOptions = resolveScanOptions(flags, visSecrets, root);
|
|
505
|
+
const toolVersion = "0.0.0-alpha";
|
|
506
|
+
if (flags.listRules) {
|
|
507
|
+
await printListRules(scanOptions, useColor);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
if (flags.listValidators) {
|
|
511
|
+
await printListValidators(scanOptions, useColor);
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
if (flags.init) {
|
|
515
|
+
await runInit(root, scanOptions, flags.dryRun ?? false);
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
const target = await chooseScanPaths(flags, args ?? [], root);
|
|
519
|
+
if (target.files?.length === 0) {
|
|
520
|
+
if (!flags.quiet) {
|
|
521
|
+
pail.success("No files to scan.");
|
|
522
|
+
}
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
const isInteractive = !flags.quiet && !["json", "sarif"].includes(flags.format ?? "text");
|
|
526
|
+
const spinner = createSpinner({ verbose: isInteractive });
|
|
527
|
+
spinner.start("scanning for secrets");
|
|
528
|
+
let findings;
|
|
529
|
+
try {
|
|
530
|
+
findings = target.files === void 0 ? await scan(target.paths ?? [root], scanOptions) : await scanFiles(target.files, scanOptions);
|
|
531
|
+
spinner.succeed();
|
|
532
|
+
} catch (error) {
|
|
533
|
+
spinner.failed();
|
|
534
|
+
pail.error(`secret scan failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
535
|
+
process.exit(2);
|
|
536
|
+
}
|
|
537
|
+
if (flags.verbose) {
|
|
538
|
+
const skipped = await inspectRuleset(scanOptions);
|
|
539
|
+
if (skipped.length > 0) {
|
|
540
|
+
pail.warn(`${String(skipped.length)} rule(s) skipped due to invalid regex. First few:`);
|
|
541
|
+
for (const s of skipped.slice(0, 5)) {
|
|
542
|
+
process.stderr.write(` - ${s.ruleId}: ${s.reason}
|
|
543
|
+
`);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
const baselineFullPath = scanOptions.baseline ?? join(root, DEFAULT_BASELINE);
|
|
548
|
+
const showDiff = !flags.quiet && isAccessibleSync(baselineFullPath);
|
|
549
|
+
if (flags.updateBaseline) {
|
|
550
|
+
const count = writeBaseline(findings, baselineFullPath, root, { replace: flags.replaceBaseline });
|
|
551
|
+
pail.success(`Baseline updated: ${relative(root, baselineFullPath) || baselineFullPath} now contains ${String(count)} entries.`);
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
const format = validateFormat(flags.format);
|
|
555
|
+
const ruleMetadata = format === "sarif" ? await listRules(scanOptions).catch(() => []) : [];
|
|
556
|
+
const shouldRedact = scanOptions.redact === true;
|
|
557
|
+
const reportFindings = shouldRedact ? redact(findings, ["match", "secret"]) : findings;
|
|
558
|
+
emitFindings(reportFindings, format, root, useColor, toolVersion, ruleMetadata, shouldRedact);
|
|
559
|
+
if (format === "text" && showDiff) {
|
|
560
|
+
printDiff(diffBaseline(findings, baselineFullPath, root));
|
|
561
|
+
}
|
|
562
|
+
if (findings.length > 0) {
|
|
563
|
+
if (!flags.quiet) {
|
|
564
|
+
pail.warn(`${String(findings.length)} potential secret(s) found`);
|
|
565
|
+
pail.notice("Suppress individual lines with `gitleaks:allow` / `secret-scanner:allow`, or run `vis secrets --update-baseline`.");
|
|
566
|
+
}
|
|
567
|
+
process.exit(1);
|
|
568
|
+
}
|
|
569
|
+
if (!flags.quiet) {
|
|
570
|
+
pail.success("No secrets detected.");
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
export { execute as default };
|