@secureport/cli 0.0.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EACL,aAAa,EACb,UAAU,EACV,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,aAAa,EACb,UAAU,EACV,UAAU,EACV,cAAc,GAMf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,YAAY,EAAE,OAAO,EAAe,MAAM,aAAa,CAAC;AA2BjE;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;AAEjE,MAAM,YAAY,GAA0B,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;AACvF,MAAM,OAAO,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAU,CAAC;AAChD,MAAM,SAAS,GAAuB,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAEnE,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwCZ,CAAC;AAEF,0DAA0D;AAC1D,MAAM,QAAS,SAAQ,KAAK;IAGf;IAFX,YACE,OAAe,EACN,IAAY;QAErB,KAAK,CAAC,OAAO,CAAC,CAAC;QAFN,SAAI,GAAJ,IAAI,CAAQ;QAGrB,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;IACzB,CAAC;CACF;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,GAAG,CAAC,IAAuB,EAAE,EAAM,EAAE,OAAe;IAClE,IAAI,CAAC;QACH,OAAO,QAAQ,CAAC,IAAI,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC;IACrC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,QAAQ,EAAE,CAAC;YAC9B,EAAE,CAAC,GAAG,CAAC,eAAe,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;YACzC,OAAO,KAAK,CAAC,IAAI,CAAC;QACpB,CAAC;QACD,wEAAwE;QACxE,yEAAyE;QACzE,EAAE,CAAC,GAAG,CAAC,eAAe,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAClF,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,IAAuB,EAAE,EAAM,EAAE,OAAe;IAChE,IAAI,MAA+D,CAAC;IACpE,IAAI,WAAqB,CAAC;IAC1B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,SAAS,CAAC;YACvB,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;YACf,gBAAgB,EAAE,IAAI;YACtB,OAAO,EAAE;gBACP,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBAC1B,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBAC5B,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBAC1B,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBACvB,cAAc,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBAClC,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBACvB,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBAC1B,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE;gBACzC,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBAC9B,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBACzB,aAAa,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBACjC,cAAc,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBAClC,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBACvB,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE;gBACrC,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE;aACzC;SACF,CAAC,CAAC;QACH,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;QACvB,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC;IACnC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,QAAQ,CAChB,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,8BAA8B,EACvF,IAAI,CAAC,KAAK,CACX,CAAC;IACJ,CAAC;IAED,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,IAAI,EAAE,CAAC;QAC/B,EAAE,CAAC,GAAG,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC;QACvB,OAAO,IAAI,CAAC,EAAE,CAAC;IACjB,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,IAAI,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxD,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACb,OAAO,IAAI,CAAC,EAAE,CAAC;IACjB,CAAC;IAED,MAAM,OAAO,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;IAC/B,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,QAAQ;YACX,OAAO,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAClD,KAAK,QAAQ;YACX,OAAO,aAAa,CAAC,MAAM,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACzD,KAAK,UAAU;YACb,OAAO,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACpD;YACE,MAAM,IAAI,QAAQ,CAChB,qBAAqB,OAAO,0CAA0C,EACtE,IAAI,CAAC,KAAK,CACX,CAAC;IACN,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,SAAS,GAAG,CAAC,MAA+B,EAAE,IAAY;IACxD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC3B,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACvD,CAAC;AAED,4DAA4D;AAC5D,SAAS,KAAK,CACZ,MAA+B,EAC/B,IAAY,EACZ,OAAqB;IAErB,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAChC,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,IAAI,CAAE,OAA6B,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,QAAQ,CAChB,KAAK,IAAI,mBAAmB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,KAAK,KAAK,EACnE,IAAI,CAAC,KAAK,CACX,CAAC;IACJ,CAAC;IACD,OAAO,KAAU,CAAC;AACpB,CAAC;AAED,sEAAsE;AACtE,SAAS,cAAc,CAAC,IAAuB,EAAE,IAAY;IAC3D,MAAM,CAAC,KAAK,EAAE,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC;IAC/B,IAAI,KAAK,KAAK,SAAS;QACrB,MAAM,IAAI,QAAQ,CAAC,cAAc,IAAI,8BAA8B,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IACnF,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,IAAI,QAAQ,CAAC,yBAAyB,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5F,OAAO,KAAK,CAAC;AACf,CAAC;AAED,8EAA8E;AAC9E,SAAS,IAAI,CAAC,EAAM,EAAE,IAAY;IAChC,IAAI,CAAC;QACH,OAAO,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,QAAQ,CAAC,iBAAiB,IAAI,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5D,CAAC;AACH,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,MAAM,CAAC,MAAc,EAAE,QAAgB;IAC9C,OAAO,GAAG,MAAM,IAAI,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;AACzF,CAAC;AASD,+DAA+D;AAC/D,SAAS,IAAI,CAAC,MAA+B,EAAE,IAAY,EAAE,EAAM;IACjE,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAE5B,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;IACtE,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,IAAI,QAAQ,CAChB,wCAAwC,IAAI,sBAAsB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EACxF,IAAI,CAAC,KAAK,CACX,CAAC;IACJ,CAAC;IAED,MAAM,YAAY,GAAG,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAC7C,MAAM,QAAQ,GAAG,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC;IAEzF,2EAA2E;IAC3E,0EAA0E;IAC1E,wEAAwE;IACxE,0EAA0E;IAC1E,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC;IACvD,MAAM,QAAQ,GAAG,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACtE,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,QAAQ,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QAC9D,MAAM,IAAI,QAAQ,CAChB,+BAA+B,QAAQ,CAAC,MAAM,CAAC,GAAG,oBAAoB,GAAG,IAAI;YAC3E,kFAAkF;YAClF,iFAAiF;YACjF,iCAAiC,EACnC,IAAI,CAAC,KAAK,CACX,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACjC,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAClC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAE,MAAM,CAAC,OAAO,CAAc,CAAC,CAAC,CAAC,EAAE,CAAC;IAClF,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QACjD,EAAE,CAAC,GAAG,CACJ,uFAAuF;YACrF,8FAA8F,CACjG,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAkB;QAC7B,KAAK,EAAE,WAAW;QAClB,KAAK;QACL,QAAQ;QACR,GAAG;QACH,KAAK,EAAE,OAAO,CAAC,GAAG,KAAK,IAAI,CAAC;KAC7B,CAAC;IACF,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAE5D,MAAM,QAAQ,GAAG,aAAa,CAAC;QAC7B,MAAM,EAAE;YACN,EAAE,EAAE,QAAQ;YACZ,KAAK,EAAE,WAAW;YAClB,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,IAAI,QAAQ,EAAE,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,QAAQ;YAC/E,GAAG,EAAE,GAAG,IAAI,SAAS;SACtB;QACD,GAAG,EAAE;YACH,EAAE,EAAE,KAAK;YACT,KAAK,EAAE,WAAW;YAClB,QAAQ;YACR,IAAI,EAAE,KAAK,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,CAAC,IAAI,QAAQ;YACtD,OAAO,EAAE,KAAK;YACd,QAAQ,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC,MAAM,CAAC,EAAE;YACzE,SAAS,EAAE,GAAG;SACf;QACD,QAAQ;QACR,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC;QAC/C,GAAG;QACH,KAAK,EAAE,OAAO,CAAC,GAAG,KAAK,IAAI,CAAC;KAC7B,CAAC,CAAC;IAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AACxC,CAAC;AAED,2EAA2E;AAC3E,SAAS,OAAO,CAAC,MAAc;IAC7B,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,GAAG,EAAE,CAAC,GAAG,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC;AACjC,CAAC;AAED,SAAS,aAAa,CACpB,MAAc,EACd,IAAY,EACZ,OAAsB,EACtB,IAAY;IAEZ,IAAI,CAAC;QACH,QAAQ,MAAM,EAAE,CAAC;YACf,KAAK,QAAQ;gBACX,OAAO,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YACrC,KAAK,KAAK;gBACR,OAAO,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAClC,KAAK,MAAM;gBACT,OAAO,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YACnC,KAAK,QAAQ;gBACX,OAAO,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YACrC,KAAK,SAAS;gBACZ,OAAO,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,QAAQ,CAChB,KAAK,IAAI,mBAAmB,MAAM,YAAY,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EACtG,IAAI,CAAC,KAAK,CACX,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,EAAM,EAAE,IAAY;IACxC,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAC5B,IAAI,CAAC;QACH,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,QAAQ,CAChB,KAAK,IAAI,yBAAyB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAC1F,IAAI,CAAC,KAAK,CACX,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,MAA+B,EAAE,EAAM;IACvD,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC/B,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC,GAAG,EAAE,CAAC;IACvC,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC;IAC1B,IAAI,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;QAChC,MAAM,IAAI,QAAQ,CAAC,0BAA0B,GAAG,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,4EAA4E;AAC5E,SAAS,MAAM,CAAC,GAAuB;IACrC,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACxC,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,sCAAsC;AACtC,SAAS,IAAI,CAAC,MAA+B,EAAE,EAAM,EAAE,OAAe;IACpE,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC/B,IAAI,GAAG,KAAK,SAAS;QAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC;;QAC5E,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AAClC,CAAC;AAED,SAAS,MAAM,CAAC,MAA+B,EAAE,IAAuB,EAAE,EAAM;IAC9E,MAAM,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC;IACjC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,MAAM,IAAI,QAAQ,CAAC,2BAA2B,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IACxF,CAAC;IACD,IAAI,CAAE,YAAkC,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAI,QAAQ,CAChB,yBAAyB,OAAO,gBAAgB,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAC1E,IAAI,CAAC,KAAK,CACX,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAG,OAAqB,CAAC;IACnC,MAAM,IAAI,GAAG,cAAc,CAAC,KAAK,EAAE,mBAAmB,CAAC,CAAC;IAExD,2EAA2E;IAC3E,4EAA4E;IAC5E,2DAA2D;IAC3D,IAAI,IAAI,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,KAAK,SAAS,EAAE,CAAC;QAC/D,MAAM,IAAI,QAAQ,CAChB,kFAAkF;YAChF,oFAAoF,EACtF,IAAI,CAAC,KAAK,CACX,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC;IACxD,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;IAE5C,MAAM,OAAO,GAAG;QACd,IAAI;QACJ,GAAG,EAAE,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;QACzB,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAW,EAAE,CAAC;QACxF,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,KAAK,SAAS;YAC1C,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAW,EAAE,CAAC;QACzD,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,KAAK,SAAS;YAC3C,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAW,EAAE,CAAC;KAC5D,CAAC;IAEF,IAAI,QAAgB,CAAC;IACrB,IAAI,CAAC;QACH,QAAQ;YACN,MAAM,KAAK,IAAI;gBACb,CAAC,CAAC,cAAc,CAAC,QAAQ,EAAE,OAAO,CAAC;gBACnC,CAAC,CAAC,MAAM,KAAK,MAAM;oBACjB,CAAC,CAAC,UAAU,CAAC,QAAQ,EAAE,OAAO,CAAC;oBAC/B,CAAC,CAAC,UAAU,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACxC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,uEAAuE;QACvE,oEAAoE;QACpE,MAAM,IAAI,QAAQ,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IACzF,CAAC;IAED,MAAM,WAAW,GAAG,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAChD,IAAI,WAAW,KAAK,SAAS;QAC3B,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IAEtE,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,QAAQ,CAAC,CAAC;IAC3B,OAAO,IAAI,CAAC,EAAE,CAAC;AACjB,CAAC;AAED,SAAS,aAAa,CAAC,MAA+B,EAAE,IAAuB,EAAE,EAAM;IACrF,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;IACpD,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;IAC5C,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IAC3D,OAAO,IAAI,CAAC,EAAE,CAAC;AACjB,CAAC;AAED;;;;;;GAMG;AACH,SAAS,QAAQ,CAAC,MAA+B,EAAE,IAAuB,EAAE,EAAM;IAChF,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC;IACtD,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;IAE9D,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACxF,CAAC;IACD,MAAM,UAAU,GAAG,CAAC,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAE/F,EAAE,CAAC,GAAG,CACJ;QACE,GAAG,IAAI,WAAW,MAAM,SAAS;QACjC,KAAK,QAAQ,CAAC,MAAM,WAAW,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,UAAU,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,UAAU,EAAE,EAAE;QAC/G,KAAK,QAAQ,CAAC,MAAM,CAAC,MAAM,SAAS,QAAQ,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,uBAAuB;QAClG,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;IACF,OAAO,IAAI,CAAC,EAAE,CAAC;AACjB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@secureport/cli",
3
+ "version": "0.0.0",
4
+ "description": "Turn any scanner's output into a client-ready security report. Works entirely offline, with no account.",
5
+ "keywords": [
6
+ "security",
7
+ "pentest",
8
+ "penetration-testing",
9
+ "vulnerability",
10
+ "vulnerability-management",
11
+ "appsec",
12
+ "devsecops",
13
+ "dast",
14
+ "nuclei",
15
+ "zap",
16
+ "burp",
17
+ "nessus",
18
+ "security-reporting",
19
+ "cli"
20
+ ],
21
+ "homepage": "https://github.com/agbjordan/secureport/tree/main/packages/cli#readme",
22
+ "bugs": {
23
+ "url": "https://github.com/agbjordan/secureport/issues"
24
+ },
25
+ "type": "module",
26
+ "bin": {
27
+ "secureport": "./dist/cli.js"
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "src",
32
+ "LICENSE"
33
+ ],
34
+ "dependencies": {
35
+ "@secureport/core": "^0.3.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^22.20.1",
39
+ "typescript": "^5.7.2"
40
+ },
41
+ "license": "MIT",
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "git+https://github.com/agbjordan/secureport.git",
45
+ "directory": "packages/cli"
46
+ },
47
+ "publishConfig": {
48
+ "access": "public"
49
+ },
50
+ "scripts": {
51
+ "build": "rm -rf dist && tsc -p tsconfig.json",
52
+ "typecheck": "tsc --noEmit"
53
+ }
54
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync, writeFileSync } from 'node:fs';
3
+ import { createRequire } from 'node:module';
4
+ import { run, type Io } from './run.js';
5
+
6
+ /**
7
+ * The executable.
8
+ *
9
+ * **The only file in this package that touches the outside world**, and the
10
+ * only one permitted to import `node:fs`. Everything the CLI actually does is
11
+ * in `run.ts`, behind an {@link Io} it is handed — so the whole command
12
+ * surface is testable in-process without a temporary directory or a spawned
13
+ * process.
14
+ */
15
+ const io: Io = {
16
+ readFile: (path) => readFileSync(path, 'utf8'),
17
+ writeFile: (path, content) => {
18
+ writeFileSync(path, content, 'utf8');
19
+ },
20
+ out: (text) => {
21
+ process.stdout.write(text);
22
+ },
23
+ err: (text) => {
24
+ process.stderr.write(text);
25
+ },
26
+ now: () => new Date(),
27
+ };
28
+
29
+ const { version } = createRequire(import.meta.url)('../package.json') as { version: string };
30
+
31
+ process.exitCode = run(process.argv.slice(2), io, version);
package/src/detect.ts ADDED
@@ -0,0 +1,64 @@
1
+ /** The scanner formats the CLI can read. */
2
+ export type Engine = 'nuclei' | 'zap' | 'burp' | 'nessus' | 'generic';
3
+
4
+ /** Every engine name, for error messages and `--engine` validation. */
5
+ export const ENGINES: readonly Engine[] = ['nuclei', 'zap', 'burp', 'nessus', 'generic'];
6
+
7
+ /**
8
+ * Works out which scanner produced a file, from its content.
9
+ *
10
+ * **Detection exists so the five-minute path has one fewer thing to know.**
11
+ * Somebody with a Nuclei file should not have to discover a `--engine` flag
12
+ * before they can render anything; `--engine` is there to override this when
13
+ * it guesses wrong, not to be mandatory.
14
+ *
15
+ * Every branch keys on something structural — a root element name, a
16
+ * required top-level field — rather than on a filename or an extension, both
17
+ * of which the user controls and neither of which the scanner guarantees.
18
+ *
19
+ * @param text - The file's content.
20
+ * @returns The engine, or `undefined` when nothing matches confidently.
21
+ */
22
+ export function detectEngine(text: string): Engine | undefined {
23
+ const trimmed = text.trimStart();
24
+ if (trimmed === '') return undefined;
25
+
26
+ if (trimmed.startsWith('<')) {
27
+ // Nessus names itself in its root element. Burp's root is `<issues>`, and
28
+ // the `burpVersion` attribute is what separates it from anything else
29
+ // that happens to call its root that.
30
+ if (/<NessusClientData_v2[\s>]/u.test(trimmed)) return 'nessus';
31
+ if (/<issues[\s>]/u.test(trimmed)) return 'burp';
32
+ return undefined;
33
+ }
34
+
35
+ // A generic document declares its own version, which is the whole reason
36
+ // that field exists — so check it before anything that guesses.
37
+ const whole = parse(trimmed);
38
+ if (whole !== undefined && typeof whole === 'object' && !Array.isArray(whole)) {
39
+ const record = whole as Record<string, unknown>;
40
+ if ('version' in record && Array.isArray(record['findings'])) return 'generic';
41
+ // ZAP writes one object with a `site` array; the alerts hang off it.
42
+ if (Array.isArray(record['site'])) return 'zap';
43
+ return undefined;
44
+ }
45
+
46
+ // Nuclei is JSONL: the whole file is not one document, but each line is.
47
+ const first = trimmed.split('\n').find((line) => line.trim() !== '');
48
+ if (first === undefined) return undefined;
49
+ const line = parse(first);
50
+ if (line !== undefined && typeof line === 'object' && !Array.isArray(line)) {
51
+ const record = line as Record<string, unknown>;
52
+ if ('template-id' in record || 'matched-at' in record) return 'nuclei';
53
+ }
54
+ return undefined;
55
+ }
56
+
57
+ /** `JSON.parse` that answers "no" instead of throwing. */
58
+ function parse(text: string): unknown {
59
+ try {
60
+ return JSON.parse(text);
61
+ } catch {
62
+ return undefined;
63
+ }
64
+ }
package/src/run.ts ADDED
@@ -0,0 +1,503 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { parseArgs } from 'node:util';
3
+ import {
4
+ buildSnapshot,
5
+ importBurp,
6
+ importGeneric,
7
+ importNessus,
8
+ importNuclei,
9
+ importZap,
10
+ parseSnapshot,
11
+ renderHtml,
12
+ renderJson,
13
+ renderMarkdown,
14
+ type Finding,
15
+ type ImportOptions,
16
+ type ReportKind,
17
+ type RunKind,
18
+ type Snapshot,
19
+ } from '@secureport/core';
20
+ import { detectEngine, ENGINES, type Engine } from './detect.js';
21
+
22
+ /**
23
+ * Everything the CLI touches outside itself.
24
+ *
25
+ * Injected rather than reached for, so the whole command surface can be
26
+ * exercised in-process by a test that hands it strings and reads strings
27
+ * back. `cli.ts` is the only file that builds a real one, and it is the only
28
+ * file in this package permitted to import `node:fs`.
29
+ */
30
+ export interface Io {
31
+ /** Reads a file as UTF-8. Throws if it cannot. */
32
+ readFile(path: string): string;
33
+
34
+ /** Writes a file as UTF-8. */
35
+ writeFile(path: string, content: string): void;
36
+
37
+ /** Writes to standard output. The report goes here. */
38
+ out(text: string): void;
39
+
40
+ /** Writes to standard error. Diagnostics go here, never the report. */
41
+ err(text: string): void;
42
+
43
+ /** The wall clock. An argument so output can be made reproducible. */
44
+ now(): Date;
45
+ }
46
+
47
+ /**
48
+ * Exit codes, which are a contract for anything scripting this.
49
+ *
50
+ * - `0` — it worked.
51
+ * - `1` — the command line was wrong: unknown flag, missing argument, a
52
+ * `--format` that does not exist. Nothing was read.
53
+ * - `2` — the input was wrong: a file that does not parse, an engine that
54
+ * could not be detected, a baseline against a different target.
55
+ */
56
+ export const EXIT = Object.freeze({ ok: 0, usage: 1, input: 2 });
57
+
58
+ const REPORT_KINDS: readonly ReportKind[] = ['pen', 'vap', 'exec', 'attest', 'retest'];
59
+ const FORMATS = ['md', 'html', 'json'] as const;
60
+ const RUN_KINDS: readonly RunKind[] = ['scan', 'upload', 'manual'];
61
+
62
+ const HELP = `secureport — turn a scanner's output into a client-ready report.
63
+
64
+ Everything here runs offline against local files. There is no account, nothing
65
+ is uploaded, and nothing leaves the machine.
66
+
67
+ Usage
68
+ secureport report <kind> <file> Render a report to stdout
69
+ secureport import <file> Reconcile findings into a snapshot
70
+ secureport validate <file> Check a file parses, and say what is in it
71
+
72
+ Report kinds
73
+ pen Penetration Test — every issue in full, with its evidence
74
+ vap Vulnerability Assessment — the same issues, tabulated
75
+ exec Executive Summary — where things stand, no technical detail
76
+ attest Attestation Letter — a formal statement, with its limits stated
77
+ retest Retest — what a second run fixed, and what came back
78
+
79
+ Options
80
+ --engine <name> nuclei, zap, burp, nessus or generic. Detected by default
81
+ --previous <file> A snapshot from an earlier run, to compare against
82
+ --format <fmt> md, html or json. Default: md
83
+ --out <file> Write here instead of stdout
84
+ --snapshot-out <file> Also write the snapshot, for the next run's --previous
85
+ --url <url> What was tested. Part of every fingerprint
86
+ --target <name> Its name on the report cover
87
+ --scope <glob> What the run covered. Repeatable. Default: everything
88
+ --run-kind <kind> scan, upload or manual. Default: upload
89
+ --title <text> Override the report title
90
+ --prepared-by <text> Who prepared it
91
+ --prepared-for <text> Who it is for
92
+ --now <iso8601> Freeze the clock, for byte-identical output
93
+ -h, --help This
94
+ -v, --version Print the version
95
+
96
+ Two runs, no account
97
+ secureport import run1.jsonl --url https://app.example.com --out run1.json
98
+ secureport report retest run2.jsonl --previous run1.json
99
+
100
+ The second command needs no --url: the baseline carries the target, and a
101
+ baseline for a different target is refused rather than compared.
102
+ `;
103
+
104
+ /** A command that failed in a way the user can act on. */
105
+ class CliError extends Error {
106
+ constructor(
107
+ message: string,
108
+ readonly code: number,
109
+ ) {
110
+ super(message);
111
+ this.name = 'CliError';
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Runs one command.
117
+ *
118
+ * @param argv - Arguments after the executable and script, as `process.argv.slice(2)`.
119
+ * @param io - Where to read, write and read the clock.
120
+ * @param version - The package version, for `--version`.
121
+ * @returns The process exit code.
122
+ */
123
+ export function run(argv: readonly string[], io: Io, version: string): number {
124
+ try {
125
+ return dispatch(argv, io, version);
126
+ } catch (error) {
127
+ if (error instanceof CliError) {
128
+ io.err(`secureport: ${error.message}\n`);
129
+ return error.code;
130
+ }
131
+ // An unexpected failure is still the user's problem to see, and a stack
132
+ // trace is not a message. The message is, and importers write good ones.
133
+ io.err(`secureport: ${error instanceof Error ? error.message : String(error)}\n`);
134
+ return EXIT.input;
135
+ }
136
+ }
137
+
138
+ function dispatch(argv: readonly string[], io: Io, version: string): number {
139
+ let values: Record<string, string | boolean | string[] | undefined>;
140
+ let positionals: string[];
141
+ try {
142
+ const parsed = parseArgs({
143
+ args: [...argv],
144
+ allowPositionals: true,
145
+ options: {
146
+ engine: { type: 'string' },
147
+ previous: { type: 'string' },
148
+ format: { type: 'string' },
149
+ out: { type: 'string' },
150
+ 'snapshot-out': { type: 'string' },
151
+ url: { type: 'string' },
152
+ target: { type: 'string' },
153
+ scope: { type: 'string', multiple: true },
154
+ 'run-kind': { type: 'string' },
155
+ title: { type: 'string' },
156
+ 'prepared-by': { type: 'string' },
157
+ 'prepared-for': { type: 'string' },
158
+ now: { type: 'string' },
159
+ help: { type: 'boolean', short: 'h' },
160
+ version: { type: 'boolean', short: 'v' },
161
+ },
162
+ });
163
+ values = parsed.values;
164
+ positionals = parsed.positionals;
165
+ } catch (error) {
166
+ throw new CliError(
167
+ `${error instanceof Error ? error.message : String(error)}\nRun \`secureport --help\`.`,
168
+ EXIT.usage,
169
+ );
170
+ }
171
+
172
+ if (values['version'] === true) {
173
+ io.out(`${version}\n`);
174
+ return EXIT.ok;
175
+ }
176
+ if (values['help'] === true || positionals.length === 0) {
177
+ io.out(HELP);
178
+ return EXIT.ok;
179
+ }
180
+
181
+ const command = positionals[0];
182
+ switch (command) {
183
+ case 'report':
184
+ return report(values, positionals.slice(1), io);
185
+ case 'import':
186
+ return importCommand(values, positionals.slice(1), io);
187
+ case 'validate':
188
+ return validate(values, positionals.slice(1), io);
189
+ default:
190
+ throw new CliError(
191
+ `unknown command \`${command}\`. Expected report, import or validate.`,
192
+ EXIT.usage,
193
+ );
194
+ }
195
+ }
196
+
197
+ /** A string option, or undefined. `parseArgs` gives us `string | boolean`. */
198
+ function str(values: Record<string, unknown>, name: string): string | undefined {
199
+ const value = values[name];
200
+ return typeof value === 'string' ? value : undefined;
201
+ }
202
+
203
+ /** One `--option value` that must be one of a known set. */
204
+ function oneOf<T extends string>(
205
+ values: Record<string, unknown>,
206
+ name: string,
207
+ allowed: readonly T[],
208
+ ): T | undefined {
209
+ const value = str(values, name);
210
+ if (value === undefined) return undefined;
211
+ if (!(allowed as readonly string[]).includes(value)) {
212
+ throw new CliError(
213
+ `--${name} must be one of ${allowed.join(', ')} (got \`${value}\`)`,
214
+ EXIT.usage,
215
+ );
216
+ }
217
+ return value as T;
218
+ }
219
+
220
+ /** The one positional a command needs, or a usage error naming it. */
221
+ function onlyPositional(rest: readonly string[], what: string): string {
222
+ const [first, ...extra] = rest;
223
+ if (first === undefined)
224
+ throw new CliError(`expected a ${what}. Run \`secureport --help\`.`, EXIT.usage);
225
+ if (extra.length > 0) throw new CliError(`unexpected argument \`${extra[0]}\``, EXIT.usage);
226
+ return first;
227
+ }
228
+
229
+ /** Reads a file, turning a missing one into a message rather than a stack. */
230
+ function read(io: Io, path: string): string {
231
+ try {
232
+ return io.readFile(path);
233
+ } catch {
234
+ throw new CliError(`cannot read \`${path}\``, EXIT.input);
235
+ }
236
+ }
237
+
238
+ /**
239
+ * A stable id derived from content.
240
+ *
241
+ * **Ids have to be deterministic here, and they have to not collide across
242
+ * runs.** Deterministic, because a report re-rendered from the same input must
243
+ * come out byte-identical or it cannot be compared with the copy somebody was
244
+ * sent. Non-colliding, because a second run's new issues are appended to a
245
+ * baseline's existing ones, and a counter restarting at 1 would reuse ids the
246
+ * baseline already holds.
247
+ *
248
+ * Hashing the thing being identified gives both at once.
249
+ */
250
+ function idFrom(prefix: string, material: string): string {
251
+ return `${prefix}_${createHash('sha256').update(material).digest('hex').slice(0, 12)}`;
252
+ }
253
+
254
+ /** Everything the three commands work out the same way. */
255
+ interface Loaded {
256
+ readonly snapshot: Snapshot;
257
+ readonly engine: Engine;
258
+ readonly findings: readonly Finding[];
259
+ }
260
+
261
+ /** Parses a scanner file and reconciles it into a snapshot. */
262
+ function load(values: Record<string, unknown>, file: string, io: Io): Loaded {
263
+ const text = read(io, file);
264
+
265
+ const engine = oneOf(values, 'engine', ENGINES) ?? detectEngine(text);
266
+ if (engine === undefined) {
267
+ throw new CliError(
268
+ `cannot tell which scanner produced \`${file}\`. Pass --engine (${ENGINES.join(', ')}).`,
269
+ EXIT.input,
270
+ );
271
+ }
272
+
273
+ const previousPath = str(values, 'previous');
274
+ const previous = previousPath === undefined ? undefined : loadSnapshot(io, previousPath);
275
+
276
+ // The baseline carries the target, so a retest needs no --url. An explicit
277
+ // one that disagrees is refused rather than honoured: the target id is in
278
+ // every fingerprint, so comparing across two of them would report every
279
+ // issue as resolved and every issue as new, which is worse than an error.
280
+ const url = str(values, 'url') ?? previous?.target.url;
281
+ const targetId = url === undefined ? 'tgt_local' : idFrom('tgt', url);
282
+ if (previous !== undefined && targetId !== previous.target.id) {
283
+ throw new CliError(
284
+ `--previous is a snapshot of ${previous.target.url}, and --url says ${url}. ` +
285
+ 'A target id is part of every fingerprint, so comparing across two of them would ' +
286
+ 'report everything as both resolved and new. Drop --url to use the baseline, or ' +
287
+ 'drop --previous to start fresh.',
288
+ EXIT.input,
289
+ );
290
+ }
291
+
292
+ const now = parseNow(values, io);
293
+ const runId = idFrom('run', text);
294
+ const paths = Array.isArray(values['scope']) ? (values['scope'] as string[]) : [];
295
+ if (paths.length === 0 && previous !== undefined) {
296
+ io.err(
297
+ 'secureport: no --scope given, so this run is assumed to have covered everything, and ' +
298
+ 'anything the baseline had that this file does not will resolve. Pass --scope to narrow it.\n',
299
+ );
300
+ }
301
+
302
+ const options: ImportOptions = {
303
+ orgId: 'org_local',
304
+ runId,
305
+ targetId,
306
+ now,
307
+ newId: counter(`${runId}_f`),
308
+ };
309
+ const findings = parseFindings(engine, text, options, file);
310
+
311
+ const snapshot = buildSnapshot({
312
+ target: {
313
+ id: targetId,
314
+ orgId: 'org_local',
315
+ name: str(values, 'target') ?? previous?.target.name ?? hostOf(url) ?? 'Target',
316
+ url: url ?? 'unknown',
317
+ },
318
+ run: {
319
+ id: runId,
320
+ orgId: 'org_local',
321
+ targetId,
322
+ kind: oneOf(values, 'run-kind', RUN_KINDS) ?? 'upload',
323
+ trigger: 'cli',
324
+ coverage: { paths: paths.length > 0 ? paths : ['**'], engines: [engine] },
325
+ startedAt: now,
326
+ },
327
+ findings,
328
+ ...(previous === undefined ? {} : { previous }),
329
+ now,
330
+ newId: counter(`${runId}_i`),
331
+ });
332
+
333
+ return { snapshot, engine, findings };
334
+ }
335
+
336
+ /** Ids within one run, numbered in order. The run id keeps them unique. */
337
+ function counter(prefix: string): () => string {
338
+ let n = 0;
339
+ return () => `${prefix}${++n}`;
340
+ }
341
+
342
+ function parseFindings(
343
+ engine: Engine,
344
+ text: string,
345
+ options: ImportOptions,
346
+ file: string,
347
+ ): readonly Finding[] {
348
+ try {
349
+ switch (engine) {
350
+ case 'nuclei':
351
+ return importNuclei(text, options);
352
+ case 'zap':
353
+ return importZap(text, options);
354
+ case 'burp':
355
+ return importBurp(text, options);
356
+ case 'nessus':
357
+ return importNessus(text, options);
358
+ case 'generic':
359
+ return importGeneric(text, options);
360
+ }
361
+ } catch (error) {
362
+ throw new CliError(
363
+ `\`${file}\` is not valid ${engine} output: ${error instanceof Error ? error.message : String(error)}`,
364
+ EXIT.input,
365
+ );
366
+ }
367
+ }
368
+
369
+ function loadSnapshot(io: Io, path: string): Snapshot {
370
+ const text = read(io, path);
371
+ try {
372
+ return parseSnapshot(text);
373
+ } catch (error) {
374
+ throw new CliError(
375
+ `\`${path}\` is not a snapshot: ${error instanceof Error ? error.message : String(error)}`,
376
+ EXIT.input,
377
+ );
378
+ }
379
+ }
380
+
381
+ function parseNow(values: Record<string, unknown>, io: Io): Date {
382
+ const raw = str(values, 'now');
383
+ if (raw === undefined) return io.now();
384
+ const now = new Date(raw);
385
+ if (Number.isNaN(now.getTime())) {
386
+ throw new CliError(`--now is not a date: \`${raw}\``, EXIT.usage);
387
+ }
388
+ return now;
389
+ }
390
+
391
+ /** The host of a URL, for a report cover, when nothing better was given. */
392
+ function hostOf(url: string | undefined): string | undefined {
393
+ if (url === undefined) return undefined;
394
+ try {
395
+ return new URL(url).host;
396
+ } catch {
397
+ return undefined;
398
+ }
399
+ }
400
+
401
+ /** Writes to `--out` or to stdout. */
402
+ function emit(values: Record<string, unknown>, io: Io, content: string): void {
403
+ const out = str(values, 'out');
404
+ if (out === undefined) io.out(content.endsWith('\n') ? content : `${content}\n`);
405
+ else io.writeFile(out, content);
406
+ }
407
+
408
+ function report(values: Record<string, unknown>, rest: readonly string[], io: Io): number {
409
+ const [kindArg, ...files] = rest;
410
+ if (kindArg === undefined) {
411
+ throw new CliError(`expected a report kind: ${REPORT_KINDS.join(', ')}.`, EXIT.usage);
412
+ }
413
+ if (!(REPORT_KINDS as readonly string[]).includes(kindArg)) {
414
+ throw new CliError(
415
+ `unknown report kind \`${kindArg}\`. Expected ${REPORT_KINDS.join(', ')}.`,
416
+ EXIT.usage,
417
+ );
418
+ }
419
+ const kind = kindArg as ReportKind;
420
+ const file = onlyPositional(files, 'file to report on');
421
+
422
+ // Checked here rather than left to the model, which refuses the same thing
423
+ // a few steps later in the vocabulary of a library caller. Failing before a
424
+ // file is read also means the message arrives immediately.
425
+ if (kind === 'retest' && str(values, 'previous') === undefined) {
426
+ throw new CliError(
427
+ 'a retest compares two runs, so it needs --previous <snapshot.json> — a snapshot ' +
428
+ 'written by an earlier `secureport import`. There is nothing to compare one run to.',
429
+ EXIT.usage,
430
+ );
431
+ }
432
+
433
+ const format = oneOf(values, 'format', FORMATS) ?? 'md';
434
+ const { snapshot } = load(values, file, io);
435
+
436
+ const options = {
437
+ kind,
438
+ now: parseNow(values, io),
439
+ ...(str(values, 'title') === undefined ? {} : { title: str(values, 'title') as string }),
440
+ ...(str(values, 'prepared-by') === undefined
441
+ ? {}
442
+ : { preparedBy: str(values, 'prepared-by') as string }),
443
+ ...(str(values, 'prepared-for') === undefined
444
+ ? {}
445
+ : { preparedFor: str(values, 'prepared-for') as string }),
446
+ };
447
+
448
+ let rendered: string;
449
+ try {
450
+ rendered =
451
+ format === 'md'
452
+ ? renderMarkdown(snapshot, options)
453
+ : format === 'html'
454
+ ? renderHtml(snapshot, options)
455
+ : renderJson(snapshot, options);
456
+ } catch (error) {
457
+ // The one case that lands here is a retest with no baseline, which the
458
+ // model refuses rather than rendering a comparison against nothing.
459
+ throw new CliError(error instanceof Error ? error.message : String(error), EXIT.input);
460
+ }
461
+
462
+ const snapshotOut = str(values, 'snapshot-out');
463
+ if (snapshotOut !== undefined)
464
+ io.writeFile(snapshotOut, `${JSON.stringify(snapshot, null, 2)}\n`);
465
+
466
+ emit(values, io, rendered);
467
+ return EXIT.ok;
468
+ }
469
+
470
+ function importCommand(values: Record<string, unknown>, rest: readonly string[], io: Io): number {
471
+ const file = onlyPositional(rest, 'file to import');
472
+ const { snapshot } = load(values, file, io);
473
+ emit(values, io, `${JSON.stringify(snapshot, null, 2)}\n`);
474
+ return EXIT.ok;
475
+ }
476
+
477
+ /**
478
+ * Says what is in a file without producing anything from it.
479
+ *
480
+ * The command somebody runs when an import failed and they want to know
481
+ * whether the file or the flag was wrong, so it reports what it detected and
482
+ * what it read rather than only whether it parsed.
483
+ */
484
+ function validate(values: Record<string, unknown>, rest: readonly string[], io: Io): number {
485
+ const file = onlyPositional(rest, 'file to validate');
486
+ const { engine, findings, snapshot } = load(values, file, io);
487
+
488
+ const counts = new Map<string, number>();
489
+ for (const finding of findings) {
490
+ counts.set(finding.detectedSeverity, (counts.get(finding.detectedSeverity) ?? 0) + 1);
491
+ }
492
+ const bySeverity = [...counts.entries()].map(([severity, n]) => `${n} ${severity}`).join(', ');
493
+
494
+ io.out(
495
+ [
496
+ `${file}: valid ${engine} output`,
497
+ ` ${findings.length} finding${findings.length === 1 ? '' : 's'}${bySeverity === '' ? '' : ` — ${bySeverity}`}`,
498
+ ` ${snapshot.issues.length} issue${snapshot.issues.length === 1 ? '' : 's'} after reconciliation`,
499
+ '',
500
+ ].join('\n'),
501
+ );
502
+ return EXIT.ok;
503
+ }