@transcend-io/cli 12.1.5 → 13.0.1
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 +64 -14
- package/dist/{app-BMLQABJX.mjs → app-Bms-wwFP.mjs} +19 -19
- package/dist/app-Bms-wwFP.mjs.map +1 -0
- package/dist/{approvePrivacyRequests-CY6rQT4J.mjs → approvePrivacyRequests-DyLb38gV.mjs} +2 -2
- package/dist/{approvePrivacyRequests-CY6rQT4J.mjs.map → approvePrivacyRequests-DyLb38gV.mjs.map} +1 -1
- package/dist/bin/bash-complete.mjs +1 -1
- package/dist/bin/cli.mjs +1 -1
- package/dist/bin/deprecated-command.mjs +1 -1
- package/dist/{buildExamples-BNGtj-DW.mjs → buildExamples-Ck7gzWSG.mjs} +2 -2
- package/dist/{buildExamples-BNGtj-DW.mjs.map → buildExamples-Ck7gzWSG.mjs.map} +1 -1
- package/dist/{buildXdiSyncEndpoint-BNCfOc-g.mjs → buildXdiSyncEndpoint-etqb_c38.mjs} +2 -2
- package/dist/{buildXdiSyncEndpoint-BNCfOc-g.mjs.map → buildXdiSyncEndpoint-etqb_c38.mjs.map} +1 -1
- package/dist/{bulkRestartRequests-CGvODu3r.mjs → bulkRestartRequests-DWJ8N0fa.mjs} +2 -2
- package/dist/{bulkRestartRequests-CGvODu3r.mjs.map → bulkRestartRequests-DWJ8N0fa.mjs.map} +1 -1
- package/dist/{bulkRetryEnrichers-BgDd4GWH.mjs → bulkRetryEnrichers-J8CHMy_1.mjs} +2 -2
- package/dist/{bulkRetryEnrichers-BgDd4GWH.mjs.map → bulkRetryEnrichers-J8CHMy_1.mjs.map} +1 -1
- package/dist/{cancelPrivacyRequests-Ccsiwrnu.mjs → cancelPrivacyRequests-C9nFdfgW.mjs} +2 -2
- package/dist/{cancelPrivacyRequests-Ccsiwrnu.mjs.map → cancelPrivacyRequests-C9nFdfgW.mjs.map} +1 -1
- package/dist/codecs-8T8Jhxsr.mjs +2 -0
- package/dist/codecs-8T8Jhxsr.mjs.map +1 -0
- package/dist/{command-BjzdoKgJ.mjs → command-C-wEPTO5.mjs} +2 -2
- package/dist/{command-BjzdoKgJ.mjs.map → command-C-wEPTO5.mjs.map} +1 -1
- package/dist/constants-D6y_AX3P.mjs +2 -0
- package/dist/constants-D6y_AX3P.mjs.map +1 -0
- package/dist/{constants-FDjw7bPS.mjs → constants-I694qbRg.mjs} +2 -2
- package/dist/{constants-FDjw7bPS.mjs.map → constants-I694qbRg.mjs.map} +1 -1
- package/dist/{createPoolingCommandUi-BdtnLBBM.mjs → createPoolingCommandUi-BZr2HSiZ.mjs} +2 -2
- package/dist/{createPoolingCommandUi-BdtnLBBM.mjs.map → createPoolingCommandUi-BZr2HSiZ.mjs.map} +1 -1
- package/dist/{downloadPrivacyRequestFiles-C9-xSCpt.mjs → downloadPrivacyRequestFiles-CF_QjkR9.mjs} +2 -2
- package/dist/{downloadPrivacyRequestFiles-C9-xSCpt.mjs.map → downloadPrivacyRequestFiles-CF_QjkR9.mjs.map} +1 -1
- package/dist/fetchAllRequests-BV9K0wXv.mjs +2 -0
- package/dist/fetchAllRequests-BV9K0wXv.mjs.map +1 -0
- package/dist/{fuzzyMatchColumns-D18n9Beu.mjs → fuzzyMatchColumns-Djl3a7lV.mjs} +2 -2
- package/dist/{fuzzyMatchColumns-D18n9Beu.mjs.map → fuzzyMatchColumns-Djl3a7lV.mjs.map} +1 -1
- package/dist/{generateCrossAccountApiKeys-FtWwlf_L.mjs → generateCrossAccountApiKeys-BxW6xU40.mjs} +2 -2
- package/dist/{generateCrossAccountApiKeys-FtWwlf_L.mjs.map → generateCrossAccountApiKeys-BxW6xU40.mjs.map} +1 -1
- package/dist/{impl-SOebshza.mjs → impl-3VziEzJI.mjs} +2 -2
- package/dist/{impl-SOebshza.mjs.map → impl-3VziEzJI.mjs.map} +1 -1
- package/dist/{impl-DnDM9zcu.mjs → impl-7NFxUzSa.mjs} +2 -2
- package/dist/{impl-DnDM9zcu.mjs.map → impl-7NFxUzSa.mjs.map} +1 -1
- package/dist/{impl-Dg7XSQdO2.mjs → impl-7qTKg-YP2.mjs} +2 -2
- package/dist/{impl-Dg7XSQdO2.mjs.map → impl-7qTKg-YP2.mjs.map} +1 -1
- package/dist/{impl-zZd6fuXS.mjs → impl-AWYAfZ2C.mjs} +2 -2
- package/dist/{impl-zZd6fuXS.mjs.map → impl-AWYAfZ2C.mjs.map} +1 -1
- package/dist/{impl-B94dMG4x2.mjs → impl-B-BkGfqq2.mjs} +2 -2
- package/dist/{impl-B94dMG4x2.mjs.map → impl-B-BkGfqq2.mjs.map} +1 -1
- package/dist/{impl-Dd39KsYO.mjs → impl-B0NqrNcJ2.mjs} +2 -2
- package/dist/impl-B0NqrNcJ2.mjs.map +1 -0
- package/dist/{impl-C3vTm9zQ.mjs → impl-B5fkF8m2.mjs} +2 -2
- package/dist/{impl-C3vTm9zQ.mjs.map → impl-B5fkF8m2.mjs.map} +1 -1
- package/dist/{impl-xVmMN-Vt.mjs → impl-B9vbcV8J.mjs} +2 -2
- package/dist/{impl-xVmMN-Vt.mjs.map → impl-B9vbcV8J.mjs.map} +1 -1
- package/dist/{impl-CSnBFv5P.mjs → impl-BCTVuvlD.mjs} +2 -2
- package/dist/{impl-CSnBFv5P.mjs.map → impl-BCTVuvlD.mjs.map} +1 -1
- package/dist/impl-BfS6LtNo2.mjs +2 -0
- package/dist/impl-BfS6LtNo2.mjs.map +1 -0
- package/dist/{impl-qNF5eqX-.mjs → impl-BfU0GFmW.mjs} +1 -1
- package/dist/{impl-qNF5eqX-.mjs.map → impl-BfU0GFmW.mjs.map} +1 -1
- package/dist/{impl-BXGpJz21.mjs → impl-Bhmoagby.mjs} +2 -2
- package/dist/{impl-BXGpJz21.mjs.map → impl-Bhmoagby.mjs.map} +1 -1
- package/dist/{impl-COqlYiiw.mjs → impl-Bpiggf8H.mjs} +2 -2
- package/dist/{impl-COqlYiiw.mjs.map → impl-Bpiggf8H.mjs.map} +1 -1
- package/dist/{impl-D6frX-PK.mjs → impl-BsxD7toO.mjs} +2 -2
- package/dist/{impl-D6frX-PK.mjs.map → impl-BsxD7toO.mjs.map} +1 -1
- package/dist/{impl-Bs2pNgMK.mjs → impl-BvL9wElZ.mjs} +2 -2
- package/dist/{impl-Bs2pNgMK.mjs.map → impl-BvL9wElZ.mjs.map} +1 -1
- package/dist/{impl-DUYRVzMo.mjs → impl-BzzkiK_b.mjs} +1 -1
- package/dist/{impl-DUYRVzMo.mjs.map → impl-BzzkiK_b.mjs.map} +1 -1
- package/dist/{impl-DZoTWLfw2.mjs → impl-C-hBO0Wa2.mjs} +2 -2
- package/dist/{impl-DZoTWLfw2.mjs.map → impl-C-hBO0Wa2.mjs.map} +1 -1
- package/dist/{impl-bl6NF3Lr.mjs → impl-C6tbQV8G.mjs} +2 -2
- package/dist/{impl-bl6NF3Lr.mjs.map → impl-C6tbQV8G.mjs.map} +1 -1
- package/dist/{impl-BEiCEzl_2.mjs → impl-CHoglMch.mjs} +1 -1
- package/dist/impl-CHoglMch.mjs.map +1 -0
- package/dist/{impl-ClT3G845.mjs → impl-CQ3NctWn.mjs} +2 -2
- package/dist/{impl-ClT3G845.mjs.map → impl-CQ3NctWn.mjs.map} +1 -1
- package/dist/{impl-DhXzxQDN2.mjs → impl-CRif3TpB2.mjs} +2 -2
- package/dist/{impl-DhXzxQDN2.mjs.map → impl-CRif3TpB2.mjs.map} +1 -1
- package/dist/{impl-Cvyc-iTI2.mjs → impl-CWINXCFq2.mjs} +2 -2
- package/dist/{impl-Cvyc-iTI2.mjs.map → impl-CWINXCFq2.mjs.map} +1 -1
- package/dist/{impl-BLtOfbtk.mjs → impl-CaErz960.mjs} +2 -2
- package/dist/{impl-BLtOfbtk.mjs.map → impl-CaErz960.mjs.map} +1 -1
- package/dist/{impl-D6rMLiNB2.mjs → impl-CjOkPJJx.mjs} +2 -2
- package/dist/impl-CjOkPJJx.mjs.map +1 -0
- package/dist/{impl-yIVxAhCG2.mjs → impl-CpJIikeg2.mjs} +2 -2
- package/dist/{impl-yIVxAhCG2.mjs.map → impl-CpJIikeg2.mjs.map} +1 -1
- package/dist/{impl-XxYQHKO5.mjs → impl-CujcaHKR.mjs} +2 -2
- package/dist/{impl-XxYQHKO5.mjs.map → impl-CujcaHKR.mjs.map} +1 -1
- package/dist/{impl-BLOhoP-b2.mjs → impl-CzFfjx8n2.mjs} +2 -2
- package/dist/{impl-BLOhoP-b2.mjs.map → impl-CzFfjx8n2.mjs.map} +1 -1
- package/dist/{impl-QD3_9HTk.mjs → impl-CzgNvB64.mjs} +2 -2
- package/dist/{impl-QD3_9HTk.mjs.map → impl-CzgNvB64.mjs.map} +1 -1
- package/dist/{impl-B_N_fOq0.mjs → impl-D5BItJzQ.mjs} +2 -2
- package/dist/{impl-B_N_fOq0.mjs.map → impl-D5BItJzQ.mjs.map} +1 -1
- package/dist/{impl-DeCRXfAh2.mjs → impl-DEF-meSg2.mjs} +2 -2
- package/dist/{impl-DeCRXfAh2.mjs.map → impl-DEF-meSg2.mjs.map} +1 -1
- package/dist/{impl-CTOgxZFi.mjs → impl-DIz7yqDc.mjs} +2 -2
- package/dist/{impl-CTOgxZFi.mjs.map → impl-DIz7yqDc.mjs.map} +1 -1
- package/dist/{impl-DDVhG8Gf.mjs → impl-DLnVDCjy.mjs} +2 -2
- package/dist/{impl-DDVhG8Gf.mjs.map → impl-DLnVDCjy.mjs.map} +1 -1
- package/dist/{impl-DGtCNANL.mjs → impl-DMoMMAMW.mjs} +2 -2
- package/dist/{impl-DGtCNANL.mjs.map → impl-DMoMMAMW.mjs.map} +1 -1
- package/dist/{impl-ChQAmyBN.mjs → impl-DMwjar-u.mjs} +2 -2
- package/dist/{impl-ChQAmyBN.mjs.map → impl-DMwjar-u.mjs.map} +1 -1
- package/dist/{impl-Bz1wZxa-.mjs → impl-DUWlKcy6.mjs} +2 -2
- package/dist/{impl-Bz1wZxa-.mjs.map → impl-DUWlKcy6.mjs.map} +1 -1
- package/dist/{impl-D58JUOdA.mjs → impl-DY_TNWaO.mjs} +2 -2
- package/dist/{impl-D58JUOdA.mjs.map → impl-DY_TNWaO.mjs.map} +1 -1
- package/dist/{impl-Cp5o0Q2g.mjs → impl-D_8KoR4w.mjs} +2 -2
- package/dist/{impl-Cp5o0Q2g.mjs.map → impl-D_8KoR4w.mjs.map} +1 -1
- package/dist/{impl-VSo8bO-J.mjs → impl-DdL7paBD.mjs} +2 -2
- package/dist/{impl-VSo8bO-J.mjs.map → impl-DdL7paBD.mjs.map} +1 -1
- package/dist/{impl-0-_oxvck.mjs → impl-DioQW8jR.mjs} +2 -2
- package/dist/{impl-0-_oxvck.mjs.map → impl-DioQW8jR.mjs.map} +1 -1
- package/dist/{impl-EDwngLc7.mjs → impl-Dj4IYEXg.mjs} +2 -2
- package/dist/{impl-EDwngLc7.mjs.map → impl-Dj4IYEXg.mjs.map} +1 -1
- package/dist/{impl-BvwRiPSG.mjs → impl-DpW_rDzv.mjs} +2 -2
- package/dist/{impl-BvwRiPSG.mjs.map → impl-DpW_rDzv.mjs.map} +1 -1
- package/dist/{impl-Cb-7C1Ml2.mjs → impl-Dr1qLfh1.mjs} +12 -105
- package/dist/impl-Dr1qLfh1.mjs.map +1 -0
- package/dist/{impl-agm4lpyq.mjs → impl-HsRwcfRu.mjs} +2 -2
- package/dist/{impl-agm4lpyq.mjs.map → impl-HsRwcfRu.mjs.map} +1 -1
- package/dist/{impl-BHEAKFX02.mjs → impl-LT1FBKID2.mjs} +2 -2
- package/dist/{impl-BHEAKFX02.mjs.map → impl-LT1FBKID2.mjs.map} +1 -1
- package/dist/impl-Q2ZDQO_y2.mjs +2 -0
- package/dist/impl-Q2ZDQO_y2.mjs.map +1 -0
- package/dist/{impl-DFnWzZyY.mjs → impl-UtanPzK1.mjs} +2 -2
- package/dist/{impl-DFnWzZyY.mjs.map → impl-UtanPzK1.mjs.map} +1 -1
- package/dist/{impl-Ar7VAh0Z.mjs → impl-ZFp_SwYr.mjs} +2 -2
- package/dist/{impl-Ar7VAh0Z.mjs.map → impl-ZFp_SwYr.mjs.map} +1 -1
- package/dist/{impl-Hj5CgbWd.mjs → impl-eWsR0UvV.mjs} +2 -2
- package/dist/{impl-Hj5CgbWd.mjs.map → impl-eWsR0UvV.mjs.map} +1 -1
- package/dist/{impl-Ay6JeH-7.mjs → impl-kuuX5KaC.mjs} +2 -2
- package/dist/{impl-Ay6JeH-7.mjs.map → impl-kuuX5KaC.mjs.map} +1 -1
- package/dist/{impl-xY_5o78i.mjs → impl-mBJWy3lg.mjs} +2 -2
- package/dist/{impl-xY_5o78i.mjs.map → impl-mBJWy3lg.mjs.map} +1 -1
- package/dist/{impl-ojAnhhbi.mjs → impl-nWslO7Lu.mjs} +2 -2
- package/dist/{impl-ojAnhhbi.mjs.map → impl-nWslO7Lu.mjs.map} +1 -1
- package/dist/{impl-ApZGIx6l.mjs → impl-o66EB2o7.mjs} +2 -2
- package/dist/{impl-ApZGIx6l.mjs.map → impl-o66EB2o7.mjs.map} +1 -1
- package/dist/{impl-BCKUj-ko.mjs → impl-pqCx3OlD.mjs} +2 -2
- package/dist/{impl-BCKUj-ko.mjs.map → impl-pqCx3OlD.mjs.map} +1 -1
- package/dist/{impl-BpVe6fMe.mjs → impl-yxR3rwGm.mjs} +2 -2
- package/dist/{impl-BpVe6fMe.mjs.map → impl-yxR3rwGm.mjs.map} +1 -1
- package/dist/index.d.mts +36 -26
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{inquirer-jzIQWAvq.mjs → inquirer-DK1JbPA0.mjs} +2 -2
- package/dist/{inquirer-jzIQWAvq.mjs.map → inquirer-DK1JbPA0.mjs.map} +1 -1
- package/dist/{markRequestDataSiloIdsCompleted-JGJf33yp.mjs → markRequestDataSiloIdsCompleted-CRLlmf-g.mjs} +2 -2
- package/dist/{markRequestDataSiloIdsCompleted-JGJf33yp.mjs.map → markRequestDataSiloIdsCompleted-CRLlmf-g.mjs.map} +1 -1
- package/dist/{markSilentPrivacyRequests-BTI3rH-V.mjs → markSilentPrivacyRequests-CZIAGpw2.mjs} +2 -2
- package/dist/{markSilentPrivacyRequests-BTI3rH-V.mjs.map → markSilentPrivacyRequests-CZIAGpw2.mjs.map} +1 -1
- package/dist/{mergeTranscendInputs-B7WjxYMN.mjs → mergeTranscendInputs-9kQ3t5VY.mjs} +2 -2
- package/dist/{mergeTranscendInputs-B7WjxYMN.mjs.map → mergeTranscendInputs-9kQ3t5VY.mjs.map} +1 -1
- package/dist/{notifyPrivacyRequestsAdditionalTime-n5fvOJaA.mjs → notifyPrivacyRequestsAdditionalTime-KiF9dkuk.mjs} +2 -2
- package/dist/{notifyPrivacyRequestsAdditionalTime-n5fvOJaA.mjs.map → notifyPrivacyRequestsAdditionalTime-KiF9dkuk.mjs.map} +1 -1
- package/dist/{policy-bundle-manifest-DbbTXe0u.mjs → policy-bundle-manifest-DW1kUjMf.mjs} +2 -2
- package/dist/{policy-bundle-manifest-DbbTXe0u.mjs.map → policy-bundle-manifest-DW1kUjMf.mjs.map} +1 -1
- package/dist/{policy-project-discovery-DTSl0Gfl.mjs → policy-project-discovery-BSQ2Cow0.mjs} +4 -4
- package/dist/policy-project-discovery-BSQ2Cow0.mjs.map +1 -0
- package/dist/{policy-scaffold-config-RpxwwZ2H.mjs → policy-scaffold-config-B_Xbfv7q.mjs} +8 -8
- package/dist/policy-scaffold-config-B_Xbfv7q.mjs.map +1 -0
- package/dist/{project-discovery-BkHpTXDx.mjs → project-discovery-g29vd44a.mjs} +2 -2
- package/dist/{project-discovery-BkHpTXDx.mjs.map → project-discovery-g29vd44a.mjs.map} +1 -1
- package/dist/{pullChunkedCustomSiloOutstandingIdentifiers-DbybCU2T.mjs → pullChunkedCustomSiloOutstandingIdentifiers-ekuEZlzq.mjs} +2 -2
- package/dist/{pullChunkedCustomSiloOutstandingIdentifiers-DbybCU2T.mjs.map → pullChunkedCustomSiloOutstandingIdentifiers-ekuEZlzq.mjs.map} +1 -1
- package/dist/{pullManualEnrichmentIdentifiersToCsv-CxTPUfpV.mjs → pullManualEnrichmentIdentifiersToCsv-D710M8kt.mjs} +2 -2
- package/dist/{pullManualEnrichmentIdentifiersToCsv-CxTPUfpV.mjs.map → pullManualEnrichmentIdentifiersToCsv-D710M8kt.mjs.map} +1 -1
- package/dist/{pushCronIdentifiersFromCsv-CeRVlbFE.mjs → pushCronIdentifiersFromCsv-f8FSSFhM.mjs} +2 -2
- package/dist/{pushCronIdentifiersFromCsv-CeRVlbFE.mjs.map → pushCronIdentifiersFromCsv-f8FSSFhM.mjs.map} +1 -1
- package/dist/{pushManualEnrichmentIdentifiersFromCsv--4dYCvyv.mjs → pushManualEnrichmentIdentifiersFromCsv-6xOUhOpJ.mjs} +2 -2
- package/dist/{pushManualEnrichmentIdentifiersFromCsv--4dYCvyv.mjs.map → pushManualEnrichmentIdentifiersFromCsv-6xOUhOpJ.mjs.map} +1 -1
- package/dist/{readTranscendYaml-BfqQbPiT.mjs → readTranscendYaml-D576bvzR.mjs} +2 -2
- package/dist/{readTranscendYaml-BfqQbPiT.mjs.map → readTranscendYaml-D576bvzR.mjs.map} +1 -1
- package/dist/{removeUnverifiedRequestIdentifiers-CV6JGgt_.mjs → removeUnverifiedRequestIdentifiers-B8iIreP4.mjs} +2 -2
- package/dist/{removeUnverifiedRequestIdentifiers-CV6JGgt_.mjs.map → removeUnverifiedRequestIdentifiers-B8iIreP4.mjs.map} +1 -1
- package/dist/{retryRequestDataSilos-_OThvVwU.mjs → retryRequestDataSilos-BQuxZd4C.mjs} +2 -2
- package/dist/{retryRequestDataSilos-_OThvVwU.mjs.map → retryRequestDataSilos-BQuxZd4C.mjs.map} +1 -1
- package/dist/runRegalLintStep-DPaMm7Gg.mjs +3 -0
- package/dist/runRegalLintStep-DPaMm7Gg.mjs.map +1 -0
- package/dist/{scaffold-planning-BrX-pcz6.mjs → scaffold-planning-Bp_yqZWx.mjs} +2 -2
- package/dist/{scaffold-planning-BrX-pcz6.mjs.map → scaffold-planning-Bp_yqZWx.mjs.map} +1 -1
- package/dist/{skipPreflightJobs-D8og5pKe.mjs → skipPreflightJobs-BkTH4QOf.mjs} +2 -2
- package/dist/{skipPreflightJobs-D8og5pKe.mjs.map → skipPreflightJobs-BkTH4QOf.mjs.map} +1 -1
- package/dist/{skipRequestDataSilos-DUQ6AD_O.mjs → skipRequestDataSilos-sdfJ0gPn.mjs} +2 -2
- package/dist/{skipRequestDataSilos-DUQ6AD_O.mjs.map → skipRequestDataSilos-sdfJ0gPn.mjs.map} +1 -1
- package/dist/{streamPrivacyRequestsToCsv-BApu4dvI.mjs → streamPrivacyRequestsToCsv-D0PK8mjN.mjs} +2 -2
- package/dist/{streamPrivacyRequestsToCsv-BApu4dvI.mjs.map → streamPrivacyRequestsToCsv-D0PK8mjN.mjs.map} +1 -1
- package/dist/{updateConsentManagerVersionToLatest-BrgPZ5hX.mjs → updateConsentManagerVersionToLatest-EJaexjTY.mjs} +2 -2
- package/dist/{updateConsentManagerVersionToLatest-BrgPZ5hX.mjs.map → updateConsentManagerVersionToLatest-EJaexjTY.mjs.map} +1 -1
- package/dist/{uploadConsents-DRlDHQak.mjs → uploadConsents-DjvQVsIl.mjs} +2 -2
- package/dist/{uploadConsents-DRlDHQak.mjs.map → uploadConsents-DjvQVsIl.mjs.map} +1 -1
- package/dist/uploadPrivacyRequestsFromCsv-FvSx9zis.mjs +2 -0
- package/dist/uploadPrivacyRequestsFromCsv-FvSx9zis.mjs.map +1 -0
- package/dist/{validateTranscendAuth-D-dDINof.mjs → validateTranscendAuth-BrVGfq6X.mjs} +2 -2
- package/dist/{validateTranscendAuth-D-dDINof.mjs.map → validateTranscendAuth-BrVGfq6X.mjs.map} +1 -1
- package/package.json +6 -6
- package/dist/app-BMLQABJX.mjs.map +0 -1
- package/dist/codecs-CfcaFoZR.mjs +0 -2
- package/dist/codecs-CfcaFoZR.mjs.map +0 -1
- package/dist/constants-DlH2vWm_.mjs +0 -2
- package/dist/constants-DlH2vWm_.mjs.map +0 -1
- package/dist/fetchAllRequests-BI0tFl_a.mjs +0 -2
- package/dist/fetchAllRequests-BI0tFl_a.mjs.map +0 -1
- package/dist/impl-BEiCEzl_2.mjs.map +0 -1
- package/dist/impl-Cb-7C1Ml2.mjs.map +0 -1
- package/dist/impl-D6rMLiNB2.mjs.map +0 -1
- package/dist/impl-DNOgBcsu.mjs +0 -3
- package/dist/impl-DNOgBcsu.mjs.map +0 -1
- package/dist/impl-Dd39KsYO.mjs.map +0 -1
- package/dist/policy-project-discovery-DTSl0Gfl.mjs.map +0 -1
- package/dist/policy-scaffold-config-RpxwwZ2H.mjs.map +0 -1
- package/dist/uploadPrivacyRequestsFromCsv-FAGllBdd.mjs +0 -2
- package/dist/uploadPrivacyRequestsFromCsv-FAGllBdd.mjs.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{i as e}from"./enums-OiLcZ26O.mjs";import{a as t,o as n,s as r}from"./constants-
|
|
1
|
+
import{i as e}from"./enums-OiLcZ26O.mjs";import{a as t,o as n,s as r}from"./constants-I694qbRg.mjs";import{buildCommand as i,numberParser as a}from"@stricli/core";import{ConsentTrackerStatus as o,ScopeName as s,TRANSCEND_SCOPES as c}from"@transcend-io/privacy-types";import l from"ms";function u(e){if(!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(e))throw Error(`Invalid UUID format: ${e}`);return e}function d(e){try{return new URL(e).toString().replace(/\/$/,``)}catch{throw Error(`Invalid URL format: ${e}`)}}function f(e){return e.split(`,`).map(e=>e.trim()).filter(e=>e.length>0)}function p(e){let t=new Date(e);if(Number.isNaN(t.getTime()))throw TypeError(`Invalid date: ${e}. Try using the ISO 8601 format (YYYY-MM-DDTHH:MM:SS.SSSZ)`);return t}function m(e){if(typeof e==`number`&&Number.isFinite(e))return Math.round(e*1e3);if(typeof e==`string`){let t=e.trim();if(t===``)throw Error(`Invalid duration. Examples: "45", "2d", "1h", "90 minutes", "10s".`);let n=Number(t);if(t!==``&&Number.isFinite(n))return Math.round(n*1e3);let r;try{r=l(t)}catch{throw Error(`Invalid duration. Examples: "45", "2d", "1h", "90 minutes", "10s".`)}if(typeof r==`number`&&Number.isFinite(r))return r}throw Error(`Invalid duration. Examples: "45", "2d", "1h", "90 minutes", "10s".`)}const h=({scopes:e,requiresSiloScope:t=!1})=>{let r={kind:`parsed`,parse:String,brief:`The Transcend API key. Defaults to the TRANSCEND_API_KEY environment variable when set, so --auth may be omitted if it is exported.`,...n?{default:n}:{}};return t&&(r.brief+=` This key must be associated with the data silo(s) being operated on.`),e===`Varies`?{...r,brief:`${r.brief} The scopes required will vary depending on the operation performed. If in doubt, the ${c[s.FullAdmin].title} scope will always work.`}:e.length===0?{...r,brief:`${r.brief} No scopes are required for this command.`}:{...r,brief:`${r.brief} Requires scopes: ${e.map(e=>`"${c[e].title}"`).join(`, `)}`}},g=(e=t,n=`transcendUrl`)=>({kind:`parsed`,parse:d,brief:`URL of the Transcend backend. Use https://api.us.transcend.io for US hosting. Defaults to the TRANSCEND_API_URL environment variable when set, so --${n} may be omitted if it is exported.`,default:e}),_=(e=r)=>({kind:`parsed`,parse:d,brief:`URL of the Transcend consent backend. Use https://consent.us.transcend.io for US hosting. Defaults to the TRANSCEND_CONSENT_API_URL environment variable when set, so --consentUrl may be omitted if it is exported.`,default:e}),v=()=>({kind:`parsed`,parse:String,brief:`The Sombra internal key, use for additional authentication when self-hosting Sombra`,optional:!0}),y=[`dataSilos`,`enrichers`,`templates`,`apiKeys`],b=Object.values(o),x=i({loader:async()=>{let{pull:e}=await import(`./impl-DdL7paBD.mjs`);return e},parameters:{flags:{auth:h({scopes:`Varies`}),resources:{kind:`enum`,values:[`all`,...Object.values(e)],brief:`The different resource types to pull in. Defaults to ${y.join(`,`)}.`,variadic:`,`,optional:!0},file:{kind:`parsed`,parse:String,brief:`Path to the YAML file to pull into`,default:`./transcend.yml`},transcendUrl:g(),dataSiloIds:{kind:`parsed`,parse:String,variadic:`,`,brief:`The UUIDs of the data silos that should be pulled into the YAML file`,optional:!0},integrationNames:{kind:`parsed`,parse:String,variadic:`,`,brief:`The types of integrations to pull down`,optional:!0},trackerStatuses:{kind:`enum`,values:Object.values(o),variadic:`,`,brief:`The statuses of consent manager trackers to pull down. Defaults to all statuses.`,optional:!0},pageSize:{kind:`parsed`,parse:a,brief:`The page size to use when paginating over the API`,default:`50`},skipDatapoints:{kind:`boolean`,brief:`When true, skip pulling in datapoints alongside data silo resource`,default:!1},skipSubDatapoints:{kind:`boolean`,brief:`When true, skip pulling in subDatapoints alongside data silo resource`,default:!1},includeGuessedCategories:{kind:`boolean`,brief:`When true, included guessed data categories that came from the content classifier`,default:!1},debug:{kind:`boolean`,brief:`Set to true to include debug logs while pulling the configuration`,default:!1}}},docs:{brief:`Pull metadata from Transcend into transcend.yml`,fullDescription:`Generates a transcend.yml by pulling the configuration from your Transcend instance.
|
|
2
2
|
|
|
3
3
|
The API key needs various scopes depending on the resources being pulled (see the CLI's README for more details).
|
|
4
4
|
|
|
@@ -6,4 +6,4 @@ This command can be helpful if you are looking to:
|
|
|
6
6
|
|
|
7
7
|
- Copy your data into another instance
|
|
8
8
|
- Generate a transcend.yml file as a starting point to maintain parts of your data inventory in code.`}});export{_ as a,f as c,u as d,h as i,p as l,y as n,v as o,x as r,g as s,b as t,m as u};
|
|
9
|
-
//# sourceMappingURL=command-
|
|
9
|
+
//# sourceMappingURL=command-C-wEPTO5.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-BjzdoKgJ.mjs","names":[],"sources":["../src/lib/cli/parsers.ts","../src/lib/cli/common-parameters.ts","../src/commands/inventory/pull/command.ts"],"sourcesContent":["import ms, { type StringValue as MsStringValue } from 'ms';\n\n/**\n * Validates and returns a UUID string.\n *\n * @param input - The input string to validate as UUID\n * @returns The validated UUID string\n * @throws Error if input is not a valid UUID\n */\nexport function uuidParser(input: string): string {\n const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n if (!uuidRegex.test(input)) {\n throw new Error(`Invalid UUID format: ${input}`);\n }\n return input;\n}\n\n/**\n * Validates and returns a URL string.\n *\n * @param input - The input string to validate as URL\n * @returns The validated URL string\n * @throws Error if input is not a valid URL\n */\nexport function urlParser(input: string): string {\n try {\n const url = new URL(input);\n return url.toString().replace(/\\/$/, '');\n } catch {\n throw new Error(`Invalid URL format: ${input}`);\n }\n}\n\n/**\n * Parse a comma-separated string to array.\n * NOTE: Prefer using `variadic` for list arguments instead of this function. This should only be used for arguments which have a default value.\n *\n * @param input - The comma-separated string to parse\n * @returns Array of trimmed, non-empty strings\n */\nexport function arrayParser(input: string): string[] {\n return input\n .split(',')\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n}\n\n/**\n * Parse a date string to a Date object.\n *\n * @param input - The date string to parse\n * @returns The parsed Date object\n * @throws TypeError if input is not a valid date\n */\nexport function dateParser(input: string): Date {\n const date = new Date(input);\n if (Number.isNaN(date.getTime())) {\n throw new TypeError(\n `Invalid date: ${input}. Try using the ISO 8601 format (YYYY-MM-DDTHH:MM:SS.SSSZ)`,\n );\n }\n return date;\n}\n\n/**\n * Parse a duration string to milliseconds.\n * Accepts concise/natural-ish strings (powered by `ms`) and returns milliseconds.\n * Examples: \"3600\", \"2d\", \"1h\", \"90 minutes\", \"10s\".\n *\n * @param input - The duration to parse\n * @returns The parsed duration in milliseconds\n * @throws Error if input is not a valid duration\n */\nexport function parseDurationToMs(input: unknown): number {\n if (typeof input === 'number' && Number.isFinite(input)) {\n // backward-compat: numbers => seconds\n return Math.round(input * 1000);\n }\n\n if (typeof input === 'string') {\n const trimmed = input.trim();\n // empty string → our standardized error (avoid ms throwing its own)\n if (trimmed === '') {\n throw new Error('Invalid duration. Examples: \"45\", \"2d\", \"1h\", \"90 minutes\", \"10s\".');\n }\n\n // bare numeric string => seconds (backward-compat)\n const asNumber = Number(trimmed);\n if (trimmed !== '' && Number.isFinite(asNumber)) {\n return Math.round(asNumber * 1000);\n }\n\n // let ms parse human strings\n let parsed: number | undefined;\n try {\n parsed = ms(trimmed as MsStringValue);\n } catch {\n // normalize ms' error to ours\n throw new Error('Invalid duration. Examples: \"45\", \"2d\", \"1h\", \"90 minutes\", \"10s\".');\n }\n if (typeof parsed === 'number' && Number.isFinite(parsed)) {\n return parsed;\n }\n }\n\n throw new Error('Invalid duration. Examples: \"45\", \"2d\", \"1h\", \"90 minutes\", \"10s\".');\n}\n","import type { TypedFlagParameter } from '@stricli/core';\nimport { ScopeName, TRANSCEND_SCOPES } from '@transcend-io/privacy-types';\n\nimport {\n DEFAULT_TRANSCEND_API,\n DEFAULT_TRANSCEND_API_KEY,\n DEFAULT_TRANSCEND_CONSENT_API,\n} from '../../constants.js';\nimport type { LocalContext } from '../../context.js';\nimport { urlParser } from './parsers.js';\n\n/**\n * Common parameter builders for CLI commands\n * These reduce duplication and ensure consistency across commands\n */\n\n/**\n * Creates a standard authentication parameter\n *\n * @param root0 - The scopes and whether it requires a silo scope\n * @returns The parameter object\n */\nexport const createAuthParameter = ({\n scopes,\n requiresSiloScope = false,\n}: {\n /** The scopes required for the command */\n scopes: ScopeName[] | 'Varies';\n /** Whether the command requires a silo scope */\n requiresSiloScope?: boolean;\n}): TypedFlagParameter<string, LocalContext> => {\n const parameter = {\n kind: 'parsed' as const,\n parse: String,\n brief:\n 'The Transcend API key. Defaults to the TRANSCEND_API_KEY environment variable when set, so --auth may be omitted if it is exported.',\n // When TRANSCEND_API_KEY is exported, make the flag optional by seeding a\n // default. When it is unset, omit `default` so the flag stays required.\n ...(DEFAULT_TRANSCEND_API_KEY ? { default: DEFAULT_TRANSCEND_API_KEY } : {}),\n };\n\n if (requiresSiloScope) {\n parameter.brief += ' This key must be associated with the data silo(s) being operated on.';\n }\n\n if (scopes === 'Varies') {\n return {\n ...parameter,\n brief: `${\n parameter.brief\n } The scopes required will vary depending on the operation performed. If in doubt, the ${\n TRANSCEND_SCOPES[ScopeName.FullAdmin].title\n } scope will always work.`,\n };\n }\n\n if (scopes.length === 0) {\n return {\n ...parameter,\n brief: `${parameter.brief} No scopes are required for this command.`,\n };\n }\n\n return {\n ...parameter,\n brief: `${parameter.brief} Requires scopes: ${scopes\n .map((s) => `\"${TRANSCEND_SCOPES[s].title}\"`)\n .join(', ')}`,\n };\n};\n\n/**\n * Creates a standard Transcend URL parameter\n *\n * @param defaultUrl - The default URL to use if not provided\n * @param flagName - Public flag name used by the command\n * @returns The parameter object\n */\nexport const createTranscendUrlParameter = (\n defaultUrl = DEFAULT_TRANSCEND_API,\n flagName = 'transcendUrl',\n): TypedFlagParameter<string, LocalContext> => ({\n kind: 'parsed',\n parse: urlParser,\n brief:\n `URL of the Transcend backend. Use https://api.us.transcend.io for US hosting. ` +\n `Defaults to the TRANSCEND_API_URL environment variable when set, so --${flagName} may be omitted if it is exported.`,\n default: defaultUrl,\n});\n\n/**\n * Creates a standard Consent URL parameter\n *\n * @param defaultUrl - The default URL to use if not provided\n * @returns The parameter object\n */\nexport const createConsentUrlParameter = (\n defaultUrl = DEFAULT_TRANSCEND_CONSENT_API,\n): TypedFlagParameter<string, LocalContext> => ({\n kind: 'parsed',\n parse: urlParser,\n brief:\n 'URL of the Transcend consent backend. Use https://consent.us.transcend.io for US hosting. Defaults to the TRANSCEND_CONSENT_API_URL environment variable when set, so --consentUrl may be omitted if it is exported.',\n default: defaultUrl,\n});\n\n/**\n * Creates a standard Sombra authentication parameter\n *\n * @returns The parameter object\n */\nexport const createSombraAuthParameter = (): TypedFlagParameter<\n string | undefined,\n LocalContext\n> => ({\n kind: 'parsed',\n parse: String,\n brief: 'The Sombra internal key, use for additional authentication when self-hosting Sombra',\n optional: true,\n});\n","import { buildCommand, numberParser } from '@stricli/core';\nimport { ConsentTrackerStatus } from '@transcend-io/privacy-types';\n\nimport { TranscendPullResource } from '../../../enums.js';\nimport {\n createAuthParameter,\n createTranscendUrlParameter,\n} from '../../../lib/cli/common-parameters.js';\n\nexport const DEFAULT_TRANSCEND_PULL_RESOURCES = [\n TranscendPullResource.DataSilos,\n TranscendPullResource.Enrichers,\n TranscendPullResource.Templates,\n TranscendPullResource.ApiKeys,\n];\n\nexport const DEFAULT_CONSENT_TRACKER_STATUSES = Object.values(ConsentTrackerStatus);\n\nexport const pullCommand = buildCommand({\n loader: async () => {\n const { pull } = await import('./impl.js');\n return pull;\n },\n parameters: {\n flags: {\n auth: createAuthParameter({\n scopes: 'Varies',\n }),\n resources: {\n kind: 'enum',\n values: ['all', ...Object.values(TranscendPullResource)],\n brief: `The different resource types to pull in. Defaults to ${DEFAULT_TRANSCEND_PULL_RESOURCES.join(\n ',',\n )}.`,\n variadic: ',',\n optional: true,\n },\n file: {\n kind: 'parsed',\n parse: String,\n brief: 'Path to the YAML file to pull into',\n default: './transcend.yml',\n },\n transcendUrl: createTranscendUrlParameter(),\n dataSiloIds: {\n kind: 'parsed',\n parse: String,\n variadic: ',',\n brief: 'The UUIDs of the data silos that should be pulled into the YAML file',\n optional: true,\n },\n integrationNames: {\n kind: 'parsed',\n parse: String,\n variadic: ',',\n brief: 'The types of integrations to pull down',\n optional: true,\n },\n trackerStatuses: {\n kind: 'enum',\n values: Object.values(ConsentTrackerStatus),\n variadic: ',',\n brief: 'The statuses of consent manager trackers to pull down. Defaults to all statuses.',\n optional: true,\n },\n pageSize: {\n kind: 'parsed',\n parse: numberParser,\n brief: 'The page size to use when paginating over the API',\n default: '50',\n },\n skipDatapoints: {\n kind: 'boolean',\n brief: 'When true, skip pulling in datapoints alongside data silo resource',\n default: false,\n },\n skipSubDatapoints: {\n kind: 'boolean',\n brief: 'When true, skip pulling in subDatapoints alongside data silo resource',\n default: false,\n },\n includeGuessedCategories: {\n kind: 'boolean',\n brief: 'When true, included guessed data categories that came from the content classifier',\n default: false,\n },\n debug: {\n kind: 'boolean',\n brief: 'Set to true to include debug logs while pulling the configuration',\n default: false,\n },\n },\n },\n docs: {\n brief: 'Pull metadata from Transcend into transcend.yml',\n fullDescription: `Generates a transcend.yml by pulling the configuration from your Transcend instance.\n\nThe API key needs various scopes depending on the resources being pulled (see the CLI's README for more details).\n\nThis command can be helpful if you are looking to:\n\n- Copy your data into another instance\n- Generate a transcend.yml file as a starting point to maintain parts of your data inventory in code.`,\n },\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"command-C-wEPTO5.mjs","names":[],"sources":["../src/lib/cli/parsers.ts","../src/lib/cli/common-parameters.ts","../src/commands/inventory/pull/command.ts"],"sourcesContent":["import ms, { type StringValue as MsStringValue } from 'ms';\n\n/**\n * Validates and returns a UUID string.\n *\n * @param input - The input string to validate as UUID\n * @returns The validated UUID string\n * @throws Error if input is not a valid UUID\n */\nexport function uuidParser(input: string): string {\n const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n if (!uuidRegex.test(input)) {\n throw new Error(`Invalid UUID format: ${input}`);\n }\n return input;\n}\n\n/**\n * Validates and returns a URL string.\n *\n * @param input - The input string to validate as URL\n * @returns The validated URL string\n * @throws Error if input is not a valid URL\n */\nexport function urlParser(input: string): string {\n try {\n const url = new URL(input);\n return url.toString().replace(/\\/$/, '');\n } catch {\n throw new Error(`Invalid URL format: ${input}`);\n }\n}\n\n/**\n * Parse a comma-separated string to array.\n * NOTE: Prefer using `variadic` for list arguments instead of this function. This should only be used for arguments which have a default value.\n *\n * @param input - The comma-separated string to parse\n * @returns Array of trimmed, non-empty strings\n */\nexport function arrayParser(input: string): string[] {\n return input\n .split(',')\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n}\n\n/**\n * Parse a date string to a Date object.\n *\n * @param input - The date string to parse\n * @returns The parsed Date object\n * @throws TypeError if input is not a valid date\n */\nexport function dateParser(input: string): Date {\n const date = new Date(input);\n if (Number.isNaN(date.getTime())) {\n throw new TypeError(\n `Invalid date: ${input}. Try using the ISO 8601 format (YYYY-MM-DDTHH:MM:SS.SSSZ)`,\n );\n }\n return date;\n}\n\n/**\n * Parse a duration string to milliseconds.\n * Accepts concise/natural-ish strings (powered by `ms`) and returns milliseconds.\n * Examples: \"3600\", \"2d\", \"1h\", \"90 minutes\", \"10s\".\n *\n * @param input - The duration to parse\n * @returns The parsed duration in milliseconds\n * @throws Error if input is not a valid duration\n */\nexport function parseDurationToMs(input: unknown): number {\n if (typeof input === 'number' && Number.isFinite(input)) {\n // backward-compat: numbers => seconds\n return Math.round(input * 1000);\n }\n\n if (typeof input === 'string') {\n const trimmed = input.trim();\n // empty string → our standardized error (avoid ms throwing its own)\n if (trimmed === '') {\n throw new Error('Invalid duration. Examples: \"45\", \"2d\", \"1h\", \"90 minutes\", \"10s\".');\n }\n\n // bare numeric string => seconds (backward-compat)\n const asNumber = Number(trimmed);\n if (trimmed !== '' && Number.isFinite(asNumber)) {\n return Math.round(asNumber * 1000);\n }\n\n // let ms parse human strings\n let parsed: number | undefined;\n try {\n parsed = ms(trimmed as MsStringValue);\n } catch {\n // normalize ms' error to ours\n throw new Error('Invalid duration. Examples: \"45\", \"2d\", \"1h\", \"90 minutes\", \"10s\".');\n }\n if (typeof parsed === 'number' && Number.isFinite(parsed)) {\n return parsed;\n }\n }\n\n throw new Error('Invalid duration. Examples: \"45\", \"2d\", \"1h\", \"90 minutes\", \"10s\".');\n}\n","import type { TypedFlagParameter } from '@stricli/core';\nimport { ScopeName, TRANSCEND_SCOPES } from '@transcend-io/privacy-types';\n\nimport {\n DEFAULT_TRANSCEND_API,\n DEFAULT_TRANSCEND_API_KEY,\n DEFAULT_TRANSCEND_CONSENT_API,\n} from '../../constants.js';\nimport type { LocalContext } from '../../context.js';\nimport { urlParser } from './parsers.js';\n\n/**\n * Common parameter builders for CLI commands\n * These reduce duplication and ensure consistency across commands\n */\n\n/**\n * Creates a standard authentication parameter\n *\n * @param root0 - The scopes and whether it requires a silo scope\n * @returns The parameter object\n */\nexport const createAuthParameter = ({\n scopes,\n requiresSiloScope = false,\n}: {\n /** The scopes required for the command */\n scopes: ScopeName[] | 'Varies';\n /** Whether the command requires a silo scope */\n requiresSiloScope?: boolean;\n}): TypedFlagParameter<string, LocalContext> => {\n const parameter = {\n kind: 'parsed' as const,\n parse: String,\n brief:\n 'The Transcend API key. Defaults to the TRANSCEND_API_KEY environment variable when set, so --auth may be omitted if it is exported.',\n // When TRANSCEND_API_KEY is exported, make the flag optional by seeding a\n // default. When it is unset, omit `default` so the flag stays required.\n ...(DEFAULT_TRANSCEND_API_KEY ? { default: DEFAULT_TRANSCEND_API_KEY } : {}),\n };\n\n if (requiresSiloScope) {\n parameter.brief += ' This key must be associated with the data silo(s) being operated on.';\n }\n\n if (scopes === 'Varies') {\n return {\n ...parameter,\n brief: `${\n parameter.brief\n } The scopes required will vary depending on the operation performed. If in doubt, the ${\n TRANSCEND_SCOPES[ScopeName.FullAdmin].title\n } scope will always work.`,\n };\n }\n\n if (scopes.length === 0) {\n return {\n ...parameter,\n brief: `${parameter.brief} No scopes are required for this command.`,\n };\n }\n\n return {\n ...parameter,\n brief: `${parameter.brief} Requires scopes: ${scopes\n .map((s) => `\"${TRANSCEND_SCOPES[s].title}\"`)\n .join(', ')}`,\n };\n};\n\n/**\n * Creates a standard Transcend URL parameter\n *\n * @param defaultUrl - The default URL to use if not provided\n * @param flagName - Public flag name used by the command\n * @returns The parameter object\n */\nexport const createTranscendUrlParameter = (\n defaultUrl = DEFAULT_TRANSCEND_API,\n flagName = 'transcendUrl',\n): TypedFlagParameter<string, LocalContext> => ({\n kind: 'parsed',\n parse: urlParser,\n brief:\n `URL of the Transcend backend. Use https://api.us.transcend.io for US hosting. ` +\n `Defaults to the TRANSCEND_API_URL environment variable when set, so --${flagName} may be omitted if it is exported.`,\n default: defaultUrl,\n});\n\n/**\n * Creates a standard Consent URL parameter\n *\n * @param defaultUrl - The default URL to use if not provided\n * @returns The parameter object\n */\nexport const createConsentUrlParameter = (\n defaultUrl = DEFAULT_TRANSCEND_CONSENT_API,\n): TypedFlagParameter<string, LocalContext> => ({\n kind: 'parsed',\n parse: urlParser,\n brief:\n 'URL of the Transcend consent backend. Use https://consent.us.transcend.io for US hosting. Defaults to the TRANSCEND_CONSENT_API_URL environment variable when set, so --consentUrl may be omitted if it is exported.',\n default: defaultUrl,\n});\n\n/**\n * Creates a standard Sombra authentication parameter\n *\n * @returns The parameter object\n */\nexport const createSombraAuthParameter = (): TypedFlagParameter<\n string | undefined,\n LocalContext\n> => ({\n kind: 'parsed',\n parse: String,\n brief: 'The Sombra internal key, use for additional authentication when self-hosting Sombra',\n optional: true,\n});\n","import { buildCommand, numberParser } from '@stricli/core';\nimport { ConsentTrackerStatus } from '@transcend-io/privacy-types';\n\nimport { TranscendPullResource } from '../../../enums.js';\nimport {\n createAuthParameter,\n createTranscendUrlParameter,\n} from '../../../lib/cli/common-parameters.js';\n\nexport const DEFAULT_TRANSCEND_PULL_RESOURCES = [\n TranscendPullResource.DataSilos,\n TranscendPullResource.Enrichers,\n TranscendPullResource.Templates,\n TranscendPullResource.ApiKeys,\n];\n\nexport const DEFAULT_CONSENT_TRACKER_STATUSES = Object.values(ConsentTrackerStatus);\n\nexport const pullCommand = buildCommand({\n loader: async () => {\n const { pull } = await import('./impl.js');\n return pull;\n },\n parameters: {\n flags: {\n auth: createAuthParameter({\n scopes: 'Varies',\n }),\n resources: {\n kind: 'enum',\n values: ['all', ...Object.values(TranscendPullResource)],\n brief: `The different resource types to pull in. Defaults to ${DEFAULT_TRANSCEND_PULL_RESOURCES.join(\n ',',\n )}.`,\n variadic: ',',\n optional: true,\n },\n file: {\n kind: 'parsed',\n parse: String,\n brief: 'Path to the YAML file to pull into',\n default: './transcend.yml',\n },\n transcendUrl: createTranscendUrlParameter(),\n dataSiloIds: {\n kind: 'parsed',\n parse: String,\n variadic: ',',\n brief: 'The UUIDs of the data silos that should be pulled into the YAML file',\n optional: true,\n },\n integrationNames: {\n kind: 'parsed',\n parse: String,\n variadic: ',',\n brief: 'The types of integrations to pull down',\n optional: true,\n },\n trackerStatuses: {\n kind: 'enum',\n values: Object.values(ConsentTrackerStatus),\n variadic: ',',\n brief: 'The statuses of consent manager trackers to pull down. Defaults to all statuses.',\n optional: true,\n },\n pageSize: {\n kind: 'parsed',\n parse: numberParser,\n brief: 'The page size to use when paginating over the API',\n default: '50',\n },\n skipDatapoints: {\n kind: 'boolean',\n brief: 'When true, skip pulling in datapoints alongside data silo resource',\n default: false,\n },\n skipSubDatapoints: {\n kind: 'boolean',\n brief: 'When true, skip pulling in subDatapoints alongside data silo resource',\n default: false,\n },\n includeGuessedCategories: {\n kind: 'boolean',\n brief: 'When true, included guessed data categories that came from the content classifier',\n default: false,\n },\n debug: {\n kind: 'boolean',\n brief: 'Set to true to include debug logs while pulling the configuration',\n default: false,\n },\n },\n },\n docs: {\n brief: 'Pull metadata from Transcend into transcend.yml',\n fullDescription: `Generates a transcend.yml by pulling the configuration from your Transcend instance.\n\nThe API key needs various scopes depending on the resources being pulled (see the CLI's README for more details).\n\nThis command can be helpful if you are looking to:\n\n- Copy your data into another instance\n- Generate a transcend.yml file as a starting point to maintain parts of your data inventory in code.`,\n },\n});\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{CompletedRequestStatus as e,IsoCountryCode as t,IsoCountrySubdivisionCode as n,LocaleCodec as r,RequestAction as i}from"@transcend-io/privacy-types";import{applyEnum as a,valuesOf as o}from"@transcend-io/type-utils";import*as s from"io-ts";const c=`[NONE]`,l=`[APPLY VALUE TO ALL ROWS]`,u=`<blank>`,d=[`email`,`coreIdentifier`];let f=function(e){return e.Email=`email`,e.CoreIdentifier=`coreIdentifier`,e.RequestType=`requestType`,e.SubjectType=`subjectType`,e.Locale=`locale`,e.Country=`country`,e.CountrySubDivision=`countrySubDivision`,e.RequestStatus=`requestStatus`,e.CreatedAt=`createdAt`,e.DataSiloIds=`dataSiloIds`,e.WorkflowConfigId=`workflowConfigId`,e}({});const p={email:!1,coreIdentifier:!0,requestType:!0,subjectType:!0,requestStatus:!1,createdAt:!1,dataSiloIds:!1,locale:!1,country:!1,countrySubDivision:!1,workflowConfigId:!1},m={requestType:!0,subjectType:!0},h=s.type({columnNames:s.partial(a(f,()=>s.string)),identifierNames:s.record(s.string,s.string),attributeNames:s.record(s.string,s.string),requestTypeToRequestAction:s.record(s.string,o(i)),subjectTypeToSubjectName:s.record(s.string,s.string),languageToLocale:s.record(s.string,r),regionToCountry:s.record(s.string,o({...t,[c]:c})),regionToCountrySubDivision:s.record(s.string,o({...n,[c]:c})),statusToRequestStatus:s.record(s.string,o({...e,[c]:c}))}),g=s.type({id:s.string,link:s.string,rowIndex:s.number,coreIdentifier:s.string,attemptedAt:s.string}),_=s.type({failingRequests:s.array(s.record(s.string,s.any)),successfulRequests:s.array(g),duplicateRequests:s.array(s.type({rowIndex:s.number,coreIdentifier:s.string,attemptedAt:s.string}))});export{_ as a,p as c,h as i,c as l,l as n,f as o,m as r,d as s,u as t,g as u};
|
|
2
|
+
//# sourceMappingURL=constants-D6y_AX3P.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants-D6y_AX3P.mjs","names":[],"sources":["../src/lib/requests/constants.ts"],"sourcesContent":["import {\n CompletedRequestStatus,\n RequestAction,\n IsoCountryCode,\n IsoCountrySubdivisionCode,\n LocaleCodec,\n} from '@transcend-io/privacy-types';\nimport { applyEnum, valuesOf } from '@transcend-io/type-utils';\nimport * as t from 'io-ts';\n\nexport const NONE = '[NONE]' as const;\nexport const BULK_APPLY = '[APPLY VALUE TO ALL ROWS]' as const;\nexport const BLANK = '<blank>' as const;\n\n/** These are uploaded at the top level of the request */\nexport const IDENTIFIER_BLOCK_LIST = ['email', 'coreIdentifier'];\n\n/**\n * Column names to map\n */\nexport enum ColumnName {\n /** The title of the email column */\n Email = 'email',\n /** The title of the core identifier column */\n CoreIdentifier = 'coreIdentifier',\n /** The title of the requestType column */\n RequestType = 'requestType',\n /** The title of the subjectType column */\n SubjectType = 'subjectType',\n /** The title of the locale column */\n Locale = 'locale',\n /** The country */\n Country = 'country',\n /** The country sub division */\n CountrySubDivision = 'countrySubDivision',\n /** The title of the requestStatus column */\n RequestStatus = 'requestStatus',\n /** The title of the createdAt column */\n CreatedAt = 'createdAt',\n /** The title of the dataSiloIds column */\n DataSiloIds = 'dataSiloIds',\n /** The title of the workflowConfigId column */\n WorkflowConfigId = 'workflowConfigId',\n}\n\n/** These parameters are required in the Transcend DSR API */\nexport const IS_REQUIRED: { [k in ColumnName]: boolean } = {\n [ColumnName.Email]: false,\n [ColumnName.CoreIdentifier]: true,\n [ColumnName.RequestType]: true,\n [ColumnName.SubjectType]: true,\n [ColumnName.RequestStatus]: false,\n [ColumnName.CreatedAt]: false,\n [ColumnName.DataSiloIds]: false,\n [ColumnName.Locale]: false,\n [ColumnName.Country]: false,\n [ColumnName.CountrySubDivision]: false,\n [ColumnName.WorkflowConfigId]: false,\n};\n\n/** These parameters can be specified for the entire CSV set if needed */\nexport const CAN_APPLY_IN_BULK: { [k in ColumnName]?: boolean } = {\n [ColumnName.RequestType]: true,\n [ColumnName.SubjectType]: true,\n};\n\n// Cache state\nexport const CachedFileState = t.type({\n /** Mapping between the default request input column names and the CSV column name for that input */\n columnNames: t.partial(applyEnum(ColumnName, () => t.string)),\n /** Mapping between the identifier names and the CSV column name for that input */\n identifierNames: t.record(t.string, t.string),\n /** Mapping between the request attribute inputs and the CSV column name for that input */\n attributeNames: t.record(t.string, t.string),\n /** Mapping between CSV request type and Transcend Request Action */\n requestTypeToRequestAction: t.record(t.string, valuesOf(RequestAction)),\n /** Mapping between CSV data subject type and the name of the data subject in Transcend */\n subjectTypeToSubjectName: t.record(t.string, t.string),\n /** Mapping between language imported and Transcend locale code */\n languageToLocale: t.record(t.string, LocaleCodec),\n /** Mapping between region and country code */\n regionToCountry: t.record(t.string, valuesOf({ ...IsoCountryCode, [NONE]: NONE })),\n /** Mapping between region and country sub division code */\n regionToCountrySubDivision: t.record(\n t.string,\n valuesOf({ ...IsoCountrySubdivisionCode, [NONE]: NONE }),\n ),\n /** Mapping between request status in import to Transcend request status */\n statusToRequestStatus: t.record(t.string, valuesOf({ ...CompletedRequestStatus, [NONE]: NONE })),\n});\n\n/** Type override */\nexport type CachedFileState = t.TypeOf<typeof CachedFileState>;\n\n/**\n * Successfully processed request\n */\nexport const SuccessfulRequest = t.type({\n id: t.string,\n link: t.string,\n rowIndex: t.number,\n coreIdentifier: t.string,\n attemptedAt: t.string,\n});\n\n/** Type override */\nexport type SuccessfulRequest = t.TypeOf<typeof SuccessfulRequest>;\n\n// Cache state\nexport const CachedRequestState = t.type({\n /** Set of privacy requests that failed to upload */\n failingRequests: t.array(t.record(t.string, t.any)),\n /** Successfully uploaded requests */\n successfulRequests: t.array(SuccessfulRequest),\n /** Duplicate requests */\n duplicateRequests: t.array(\n t.type({\n rowIndex: t.number,\n coreIdentifier: t.string,\n attemptedAt: t.string,\n }),\n ),\n});\n\n/** Type override */\nexport type CachedRequestState = t.TypeOf<typeof CachedRequestState>;\n"],"mappings":"uPAUA,MAAa,EAAO,SACP,EAAa,4BACb,EAAQ,UAGR,EAAwB,CAAC,QAAS,iBAAiB,CAKhE,IAAY,EAAL,SAAA,EAAA,OAEL,GAAA,MAAA,QAEA,EAAA,eAAA,iBAEA,EAAA,YAAA,cAEA,EAAA,YAAA,cAEA,EAAA,OAAA,SAEA,EAAA,QAAA,UAEA,EAAA,mBAAA,qBAEA,EAAA,cAAA,gBAEA,EAAA,UAAA,YAEA,EAAA,YAAA,cAEA,EAAA,iBAAA,yBACD,CAGD,MAAa,EAA8C,OACrC,kBACS,eACH,eACA,iBACE,aACJ,eACE,UACL,WACC,sBACW,oBACF,GAChC,CAGY,EAAqD,aACtC,eACA,GAC3B,CAGY,EAAkB,EAAE,KAAK,CAEpC,YAAa,EAAE,QAAQ,EAAU,MAAkB,EAAE,OAAO,CAAC,CAE7D,gBAAiB,EAAE,OAAO,EAAE,OAAQ,EAAE,OAAO,CAE7C,eAAgB,EAAE,OAAO,EAAE,OAAQ,EAAE,OAAO,CAE5C,2BAA4B,EAAE,OAAO,EAAE,OAAQ,EAAS,EAAc,CAAC,CAEvE,yBAA0B,EAAE,OAAO,EAAE,OAAQ,EAAE,OAAO,CAEtD,iBAAkB,EAAE,OAAO,EAAE,OAAQ,EAAY,CAEjD,gBAAiB,EAAE,OAAO,EAAE,OAAQ,EAAS,CAAE,GAAG,GAAiB,GAAO,EAAM,CAAC,CAAC,CAElF,2BAA4B,EAAE,OAC5B,EAAE,OACF,EAAS,CAAE,GAAG,GAA4B,GAAO,EAAM,CAAC,CACzD,CAED,sBAAuB,EAAE,OAAO,EAAE,OAAQ,EAAS,CAAE,GAAG,GAAyB,GAAO,EAAM,CAAC,CAAC,CACjG,CAAC,CAQW,EAAoB,EAAE,KAAK,CACtC,GAAI,EAAE,OACN,KAAM,EAAE,OACR,SAAU,EAAE,OACZ,eAAgB,EAAE,OAClB,YAAa,EAAE,OAChB,CAAC,CAMW,EAAqB,EAAE,KAAK,CAEvC,gBAAiB,EAAE,MAAM,EAAE,OAAO,EAAE,OAAQ,EAAE,IAAI,CAAC,CAEnD,mBAAoB,EAAE,MAAM,EAAkB,CAE9C,kBAAmB,EAAE,MACnB,EAAE,KAAK,CACL,SAAU,EAAE,OACZ,eAAgB,EAAE,OAClB,YAAa,EAAE,OAChB,CAAC,CACH,CACF,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import"./enums-OiLcZ26O.mjs";import{ScopeName as e,TRANSCEND_SCOPES as t}from"@transcend-io/privacy-types";import{keyBy as n}from"lodash-es";var r=`
|
|
2
|
-
//# sourceMappingURL=constants-
|
|
1
|
+
import"./enums-OiLcZ26O.mjs";import{ScopeName as e,TRANSCEND_SCOPES as t}from"@transcend-io/privacy-types";import{keyBy as n}from"lodash-es";var r=`13.0.1`,i=`A command line interface for programmatic operations across Transcend.`;const a=`transcend`,o=`https://app.transcend.io`,s=`${o}/infrastructure/integrations`,c=`${o}/data-map/data-inventory/data-points`,l=process.env.TRANSCEND_API_URL||`https://api.transcend.io`,u=process.env.TRANSCEND_API_KEY||void 0,d=process.env.TRANSCEND_CONSENT_API_URL||`https://consent.transcend.io`,f={apiKeys:[e.ViewApiKeys],templates:[e.ManageEmailTemplates],dataSilos:[e.ManageDataMap,e.ConnectDataSilos,e.ViewApiKeys],enrichers:[e.ManageRequestIdentities],businessEntities:[e.ManageDataInventory],processingActivities:[e.ManageDataMap],identifiers:[e.ManageRequestIdentities],customFields:[e.ManageGlobalAttributes],dataFlows:[e.ManageDataFlow],cookies:[e.ManageDataFlow],consentManager:[e.ManageConsentManagerDeveloperSettings],partitions:[e.ManageConsentManagerDeveloperSettings],actions:[e.ManageDataSubjectRequestSettings],dataSubjects:[e.ManageDataSubjectRequestSettings],vendors:[e.ManageDataInventory],dataCategories:[e.ManageDataInventory],processingPurposes:[e.ManageDataInventory],actionItems:[e.ManageAllActionItems,e.ViewGlobalAttributes],actionItemCollections:[e.ManageActionItemCollections],teams:[e.ManageAccessControl],messages:[e.ManageIntlMessages],privacyCenters:[e.ManagePrivacyCenter],policies:[e.ManagePolicies],assessments:[e.ManageAssessments],assessmentTemplates:[e.ManageAssessments],purposes:[e.ManageConsentManager,e.ManagePreferenceStoreSettings],preferenceOptions:[e.ManagePreferenceStoreSettings],systemDiscovery:[e.ManageDataMap],preferenceWorkflowConfigs:[e.ManagePreferenceStoreSettings,e.ViewDataSubjectRequestSettings,e.ViewConsentManager,e.ViewDataMap,e.ManageWorkflows],workflowConfigs:[e.ManageWorkflows]},p={apiKeys:[e.ViewApiKeys],templates:[e.ViewEmailTemplates],dataSilos:[e.ViewDataMap,e.ViewDataSubjectRequestSettings,e.ViewApiKeys],enrichers:[e.ViewRequestIdentitySettings],businessEntities:[e.ViewDataInventory],processingActivities:[e.ViewDataInventory],identifiers:[e.ViewRequestIdentitySettings],customFields:[e.ViewGlobalAttributes],dataFlows:[e.ViewDataFlow],cookies:[e.ViewDataFlow],consentManager:[e.ViewConsentManager],partitions:[e.ViewConsentManager],actions:[e.ViewDataSubjectRequestSettings],dataSubjects:[e.ViewDataSubjectRequestSettings],vendors:[e.ViewDataInventory],dataCategories:[e.ViewDataInventory],processingPurposes:[e.ViewDataInventory],actionItemCollections:[e.ViewAllActionItems],actionItems:[e.ViewAllActionItems],teams:[e.ViewScopes],messages:[e.ViewIntlMessages],privacyCenters:[e.ViewPrivacyCenter],policies:[e.ViewPolicies],assessments:[e.ViewAssessments],assessmentTemplates:[e.ViewAssessments],purposes:[e.ViewConsentManager,e.ViewPreferenceStoreSettings],preferenceOptions:[e.ViewPreferenceStoreSettings],systemDiscovery:[e.ViewDataMap],preferenceWorkflowConfigs:[e.ViewPreferenceStoreSettings,e.ManageWorkflows],workflowConfigs:[e.ViewWorkflows]},m={apiKeys:`api-keys`,customFields:`attributes`,dataFlows:`data-flows`,cookies:`cookies`,consentManager:`consent-manager`,partitions:`partitions`,actions:`actions`,dataSubjects:`data-subjects`,businessEntities:`business-entities`,processingActivities:`processing-activities`,identifiers:`identifiers`,enrichers:`preflights`,dataSilos:`data-silos`,templates:`templates`,vendors:`vendors`,dataCategories:`data-categories`,processingPurposes:`processing-purposes`,actionItems:`action-items`,actionItemCollections:`action-item-collections`,teams:`teams`,messages:`messages`,privacyCenters:`privacy-center`,policies:`policies`,assessments:`assessments`,assessmentTemplates:`assessment-templates`,purposes:`purposes`,preferenceOptions:`preference-options`,systemDiscovery:`system-discovery`,preferenceWorkflowConfigs:`preference-workflow-configs`,workflowConfigs:`workflow-configs`},h=n(Object.entries(t).map(([e,t])=>({...t,name:e})),`title`),g=Object.keys(h),_=new Set([429,502,500,504,329]),v=process.env.DEBUG===`1`;export{l as a,_ as c,p as d,f,r as g,i as h,v as i,h as l,a as m,c as n,u as o,m as p,s as r,d as s,o as t,g as u};
|
|
2
|
+
//# sourceMappingURL=constants-I694qbRg.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants-FDjw7bPS.mjs","names":[],"sources":["../package.json","../src/constants.ts"],"sourcesContent":["","import { ScopeName, TRANSCEND_SCOPES, type ScopeDefinition } from '@transcend-io/privacy-types';\nimport { keyBy } from 'lodash-es';\n\nimport { TranscendInput } from './codecs.js';\nimport { TranscendPullResource } from './enums.js';\n\nexport { description, version } from '../package.json';\n/**\n * The name of the main binary for the CLI\n */\nexport const name = 'transcend';\n\nexport const ADMIN_DASH = 'https://app.transcend.io';\n\nexport const ADMIN_DASH_INTEGRATIONS = `${ADMIN_DASH}/infrastructure/integrations`;\nexport const ADMIN_DASH_DATAPOINTS = `${ADMIN_DASH}/data-map/data-inventory/data-points`;\n\n/**\n * Override default transcend API url using\n * TRANSCEND_API_URL=https://api.us.transcend.io transcend ...\n */\nexport const DEFAULT_TRANSCEND_API = process.env.TRANSCEND_API_URL || 'https://api.transcend.io';\n\n/**\n * Default the `--auth` flag to the `TRANSCEND_API_KEY` environment variable\n * when it is set, so callers do not need to pass `--auth` on every invocation:\n * TRANSCEND_API_KEY=... transcend policy bundles\n * When unset, `--auth` remains required.\n */\nexport const DEFAULT_TRANSCEND_API_KEY = process.env.TRANSCEND_API_KEY || undefined;\n\n/**\n * Override default transcend API url using\n * TRANSCEND_CONSENT_API_URL=https://consent.us.transcend.io transcend ...\n */\nexport const DEFAULT_TRANSCEND_CONSENT_API =\n process.env.TRANSCEND_CONSENT_API_URL || 'https://consent.transcend.io';\n\n/**\n * Mapping between resource type and scopes required for cli\n */\nexport const TR_PUSH_RESOURCE_SCOPE_MAP: {\n [k in TranscendPullResource]: ScopeName[];\n} = {\n [TranscendPullResource.ApiKeys]: [ScopeName.ViewApiKeys],\n [TranscendPullResource.Templates]: [ScopeName.ManageEmailTemplates],\n // ViewApiKeys — resolve api-key-title → apiKeyId when present on data silos\n [TranscendPullResource.DataSilos]: [\n ScopeName.ManageDataMap,\n ScopeName.ConnectDataSilos,\n ScopeName.ViewApiKeys,\n ],\n [TranscendPullResource.Enrichers]: [ScopeName.ManageRequestIdentities],\n [TranscendPullResource.BusinessEntities]: [ScopeName.ManageDataInventory],\n [TranscendPullResource.ProcessingActivities]: [ScopeName.ManageDataMap],\n [TranscendPullResource.Identifiers]: [ScopeName.ManageRequestIdentities],\n [TranscendPullResource.Attributes]: [ScopeName.ManageGlobalAttributes],\n [TranscendPullResource.DataFlows]: [ScopeName.ManageDataFlow],\n [TranscendPullResource.Cookies]: [ScopeName.ManageDataFlow],\n [TranscendPullResource.ConsentManager]: [ScopeName.ManageConsentManagerDeveloperSettings],\n [TranscendPullResource.Partitions]: [ScopeName.ManageConsentManagerDeveloperSettings],\n [TranscendPullResource.Actions]: [ScopeName.ManageDataSubjectRequestSettings],\n [TranscendPullResource.DataSubjects]: [ScopeName.ManageDataSubjectRequestSettings],\n [TranscendPullResource.Vendors]: [ScopeName.ManageDataInventory],\n [TranscendPullResource.DataCategories]: [ScopeName.ManageDataInventory],\n [TranscendPullResource.ProcessingPurposes]: [ScopeName.ManageDataInventory],\n [TranscendPullResource.ActionItems]: [\n ScopeName.ManageAllActionItems,\n ScopeName.ViewGlobalAttributes,\n ],\n [TranscendPullResource.ActionItemCollections]: [ScopeName.ManageActionItemCollections],\n [TranscendPullResource.Teams]: [ScopeName.ManageAccessControl],\n [TranscendPullResource.Messages]: [ScopeName.ManageIntlMessages],\n [TranscendPullResource.PrivacyCenters]: [ScopeName.ManagePrivacyCenter],\n [TranscendPullResource.Policies]: [ScopeName.ManagePolicies],\n [TranscendPullResource.Assessments]: [ScopeName.ManageAssessments],\n [TranscendPullResource.AssessmentTemplates]: [ScopeName.ManageAssessments],\n [TranscendPullResource.Purposes]: [\n ScopeName.ManageConsentManager,\n ScopeName.ManagePreferenceStoreSettings,\n ],\n [TranscendPullResource.PreferenceOptions]: [ScopeName.ManagePreferenceStoreSettings],\n [TranscendPullResource.SystemDiscovery]: [ScopeName.ManageDataMap],\n // Primary: ManagePreferenceStoreSettings (createOrUpdateConsentWorkflowTrigger).\n // Related fetches used during sync:\n // - ViewDataSubjectRequestSettings — resolve action-type / data-subject-type (legacy)\n // - ViewConsentManager — resolve purposes[].tracking-type → purposeId\n // - ViewDataMap — resolve data-silo-titles (legacy, only when present)\n // - ManageWorkflows — list DSR workflows when workflow-title is set (V2)\n [TranscendPullResource.PreferenceWorkflowConfigs]: [\n ScopeName.ManagePreferenceStoreSettings,\n ScopeName.ViewDataSubjectRequestSettings,\n ScopeName.ViewConsentManager,\n ScopeName.ViewDataMap,\n ScopeName.ManageWorkflows,\n ],\n // ManageWorkflows inherits ViewDataSubjectRequestSettings and ViewGlobalAttributes\n // (needed to resolve action/data-subject/attribute-key names to IDs on push)\n [TranscendPullResource.WorkflowConfigs]: [ScopeName.ManageWorkflows],\n};\n\n/**\n * Mapping between resource type and scopes required for cli\n */\nexport const TR_PULL_RESOURCE_SCOPE_MAP: {\n [k in TranscendPullResource]: ScopeName[];\n} = {\n [TranscendPullResource.ApiKeys]: [ScopeName.ViewApiKeys],\n [TranscendPullResource.Templates]: [ScopeName.ViewEmailTemplates],\n // ViewApiKeys — dataSilos.apiKeys on the enriched pull query\n [TranscendPullResource.DataSilos]: [\n ScopeName.ViewDataMap,\n ScopeName.ViewDataSubjectRequestSettings,\n ScopeName.ViewApiKeys,\n ],\n [TranscendPullResource.Enrichers]: [ScopeName.ViewRequestIdentitySettings],\n [TranscendPullResource.BusinessEntities]: [ScopeName.ViewDataInventory],\n [TranscendPullResource.ProcessingActivities]: [ScopeName.ViewDataInventory],\n [TranscendPullResource.Identifiers]: [ScopeName.ViewRequestIdentitySettings],\n [TranscendPullResource.Attributes]: [ScopeName.ViewGlobalAttributes],\n [TranscendPullResource.DataFlows]: [ScopeName.ViewDataFlow],\n [TranscendPullResource.Cookies]: [ScopeName.ViewDataFlow],\n [TranscendPullResource.ConsentManager]: [ScopeName.ViewConsentManager],\n [TranscendPullResource.Partitions]: [ScopeName.ViewConsentManager],\n [TranscendPullResource.Actions]: [ScopeName.ViewDataSubjectRequestSettings],\n [TranscendPullResource.DataSubjects]: [ScopeName.ViewDataSubjectRequestSettings],\n [TranscendPullResource.Vendors]: [ScopeName.ViewDataInventory],\n [TranscendPullResource.DataCategories]: [ScopeName.ViewDataInventory],\n [TranscendPullResource.ProcessingPurposes]: [ScopeName.ViewDataInventory],\n [TranscendPullResource.ActionItemCollections]: [ScopeName.ViewAllActionItems],\n [TranscendPullResource.ActionItems]: [ScopeName.ViewAllActionItems],\n [TranscendPullResource.Teams]: [ScopeName.ViewScopes],\n [TranscendPullResource.Messages]: [ScopeName.ViewIntlMessages],\n [TranscendPullResource.PrivacyCenters]: [ScopeName.ViewPrivacyCenter],\n [TranscendPullResource.Policies]: [ScopeName.ViewPolicies],\n [TranscendPullResource.Assessments]: [ScopeName.ViewAssessments],\n [TranscendPullResource.AssessmentTemplates]: [ScopeName.ViewAssessments],\n [TranscendPullResource.Purposes]: [\n ScopeName.ViewConsentManager,\n ScopeName.ViewPreferenceStoreSettings,\n ],\n [TranscendPullResource.PreferenceOptions]: [ScopeName.ViewPreferenceStoreSettings],\n [TranscendPullResource.SystemDiscovery]: [ScopeName.ViewDataMap],\n // ViewPreferenceStoreSettings — consentWorkflowTriggers GraphQL query\n // ManageWorkflows — resolve workflowConfigId → workflow-title for V2 configs\n [TranscendPullResource.PreferenceWorkflowConfigs]: [\n ScopeName.ViewPreferenceStoreSettings,\n ScopeName.ManageWorkflows,\n ],\n [TranscendPullResource.WorkflowConfigs]: [ScopeName.ViewWorkflows],\n};\n\nexport const TR_YML_RESOURCE_TO_FIELD_NAME: Record<TranscendPullResource, keyof TranscendInput> = {\n [TranscendPullResource.ApiKeys]: 'api-keys',\n [TranscendPullResource.Attributes]: 'attributes',\n [TranscendPullResource.DataFlows]: 'data-flows',\n [TranscendPullResource.Cookies]: 'cookies',\n [TranscendPullResource.ConsentManager]: 'consent-manager',\n [TranscendPullResource.Partitions]: 'partitions',\n [TranscendPullResource.Actions]: 'actions',\n [TranscendPullResource.DataSubjects]: 'data-subjects',\n [TranscendPullResource.BusinessEntities]: 'business-entities',\n [TranscendPullResource.ProcessingActivities]: 'processing-activities',\n [TranscendPullResource.Identifiers]: 'identifiers',\n [TranscendPullResource.Enrichers]: 'preflights',\n [TranscendPullResource.DataSilos]: 'data-silos',\n [TranscendPullResource.Templates]: 'templates',\n [TranscendPullResource.Vendors]: 'vendors',\n [TranscendPullResource.DataCategories]: 'data-categories',\n [TranscendPullResource.ProcessingPurposes]: 'processing-purposes',\n [TranscendPullResource.ActionItems]: 'action-items',\n [TranscendPullResource.ActionItemCollections]: 'action-item-collections',\n [TranscendPullResource.Teams]: 'teams',\n [TranscendPullResource.Messages]: 'messages',\n [TranscendPullResource.PrivacyCenters]: 'privacy-center',\n [TranscendPullResource.Policies]: 'policies',\n [TranscendPullResource.Assessments]: 'assessments',\n [TranscendPullResource.AssessmentTemplates]: 'assessment-templates',\n [TranscendPullResource.Purposes]: 'purposes',\n [TranscendPullResource.PreferenceOptions]: 'preference-options',\n [TranscendPullResource.SystemDiscovery]: 'system-discovery',\n [TranscendPullResource.PreferenceWorkflowConfigs]: 'preference-workflow-configs',\n [TranscendPullResource.WorkflowConfigs]: 'workflow-configs',\n};\n\nexport const SCOPES_BY_TITLE = keyBy(\n Object.entries(TRANSCEND_SCOPES).map(([name, value]) => ({\n ...value,\n name,\n })),\n 'title',\n) as Record<\n string,\n ScopeDefinition & {\n /** The camelCased name which identifies the scope */\n name: ScopeName;\n }\n>;\n\nexport const SCOPE_TITLES = Object.keys(SCOPES_BY_TITLE);\n\n/**\n * HTTP statuses that should be retried *in place* without splitting.\n * 429: Rate-limited\n * 502: Upstream/edge gateway error\n * 329: Reserved for custom infra (kept defensively)\n */\nexport const RETRYABLE_BATCH_STATUSES = new Set([429, 502, 500, 504, 329] as const);\n\n/**\n * Debugging\n */\nexport const DEBUG = process.env.DEBUG === '1';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"constants-I694qbRg.mjs","names":[],"sources":["../package.json","../src/constants.ts"],"sourcesContent":["","import { ScopeName, TRANSCEND_SCOPES, type ScopeDefinition } from '@transcend-io/privacy-types';\nimport { keyBy } from 'lodash-es';\n\nimport { TranscendInput } from './codecs.js';\nimport { TranscendPullResource } from './enums.js';\n\nexport { description, version } from '../package.json';\n/**\n * The name of the main binary for the CLI\n */\nexport const name = 'transcend';\n\nexport const ADMIN_DASH = 'https://app.transcend.io';\n\nexport const ADMIN_DASH_INTEGRATIONS = `${ADMIN_DASH}/infrastructure/integrations`;\nexport const ADMIN_DASH_DATAPOINTS = `${ADMIN_DASH}/data-map/data-inventory/data-points`;\n\n/**\n * Override default transcend API url using\n * TRANSCEND_API_URL=https://api.us.transcend.io transcend ...\n */\nexport const DEFAULT_TRANSCEND_API = process.env.TRANSCEND_API_URL || 'https://api.transcend.io';\n\n/**\n * Default the `--auth` flag to the `TRANSCEND_API_KEY` environment variable\n * when it is set, so callers do not need to pass `--auth` on every invocation:\n * TRANSCEND_API_KEY=... transcend policy bundles\n * When unset, `--auth` remains required.\n */\nexport const DEFAULT_TRANSCEND_API_KEY = process.env.TRANSCEND_API_KEY || undefined;\n\n/**\n * Override default transcend API url using\n * TRANSCEND_CONSENT_API_URL=https://consent.us.transcend.io transcend ...\n */\nexport const DEFAULT_TRANSCEND_CONSENT_API =\n process.env.TRANSCEND_CONSENT_API_URL || 'https://consent.transcend.io';\n\n/**\n * Mapping between resource type and scopes required for cli\n */\nexport const TR_PUSH_RESOURCE_SCOPE_MAP: {\n [k in TranscendPullResource]: ScopeName[];\n} = {\n [TranscendPullResource.ApiKeys]: [ScopeName.ViewApiKeys],\n [TranscendPullResource.Templates]: [ScopeName.ManageEmailTemplates],\n // ViewApiKeys — resolve api-key-title → apiKeyId when present on data silos\n [TranscendPullResource.DataSilos]: [\n ScopeName.ManageDataMap,\n ScopeName.ConnectDataSilos,\n ScopeName.ViewApiKeys,\n ],\n [TranscendPullResource.Enrichers]: [ScopeName.ManageRequestIdentities],\n [TranscendPullResource.BusinessEntities]: [ScopeName.ManageDataInventory],\n [TranscendPullResource.ProcessingActivities]: [ScopeName.ManageDataMap],\n [TranscendPullResource.Identifiers]: [ScopeName.ManageRequestIdentities],\n [TranscendPullResource.Attributes]: [ScopeName.ManageGlobalAttributes],\n [TranscendPullResource.DataFlows]: [ScopeName.ManageDataFlow],\n [TranscendPullResource.Cookies]: [ScopeName.ManageDataFlow],\n [TranscendPullResource.ConsentManager]: [ScopeName.ManageConsentManagerDeveloperSettings],\n [TranscendPullResource.Partitions]: [ScopeName.ManageConsentManagerDeveloperSettings],\n [TranscendPullResource.Actions]: [ScopeName.ManageDataSubjectRequestSettings],\n [TranscendPullResource.DataSubjects]: [ScopeName.ManageDataSubjectRequestSettings],\n [TranscendPullResource.Vendors]: [ScopeName.ManageDataInventory],\n [TranscendPullResource.DataCategories]: [ScopeName.ManageDataInventory],\n [TranscendPullResource.ProcessingPurposes]: [ScopeName.ManageDataInventory],\n [TranscendPullResource.ActionItems]: [\n ScopeName.ManageAllActionItems,\n ScopeName.ViewGlobalAttributes,\n ],\n [TranscendPullResource.ActionItemCollections]: [ScopeName.ManageActionItemCollections],\n [TranscendPullResource.Teams]: [ScopeName.ManageAccessControl],\n [TranscendPullResource.Messages]: [ScopeName.ManageIntlMessages],\n [TranscendPullResource.PrivacyCenters]: [ScopeName.ManagePrivacyCenter],\n [TranscendPullResource.Policies]: [ScopeName.ManagePolicies],\n [TranscendPullResource.Assessments]: [ScopeName.ManageAssessments],\n [TranscendPullResource.AssessmentTemplates]: [ScopeName.ManageAssessments],\n [TranscendPullResource.Purposes]: [\n ScopeName.ManageConsentManager,\n ScopeName.ManagePreferenceStoreSettings,\n ],\n [TranscendPullResource.PreferenceOptions]: [ScopeName.ManagePreferenceStoreSettings],\n [TranscendPullResource.SystemDiscovery]: [ScopeName.ManageDataMap],\n // Primary: ManagePreferenceStoreSettings (createOrUpdateConsentWorkflowTrigger).\n // Related fetches used during sync:\n // - ViewDataSubjectRequestSettings — resolve action-type / data-subject-type (legacy)\n // - ViewConsentManager — resolve purposes[].tracking-type → purposeId\n // - ViewDataMap — resolve data-silo-titles (legacy, only when present)\n // - ManageWorkflows — list DSR workflows when workflow-title is set (V2)\n [TranscendPullResource.PreferenceWorkflowConfigs]: [\n ScopeName.ManagePreferenceStoreSettings,\n ScopeName.ViewDataSubjectRequestSettings,\n ScopeName.ViewConsentManager,\n ScopeName.ViewDataMap,\n ScopeName.ManageWorkflows,\n ],\n // ManageWorkflows inherits ViewDataSubjectRequestSettings and ViewGlobalAttributes\n // (needed to resolve action/data-subject/attribute-key names to IDs on push)\n [TranscendPullResource.WorkflowConfigs]: [ScopeName.ManageWorkflows],\n};\n\n/**\n * Mapping between resource type and scopes required for cli\n */\nexport const TR_PULL_RESOURCE_SCOPE_MAP: {\n [k in TranscendPullResource]: ScopeName[];\n} = {\n [TranscendPullResource.ApiKeys]: [ScopeName.ViewApiKeys],\n [TranscendPullResource.Templates]: [ScopeName.ViewEmailTemplates],\n // ViewApiKeys — dataSilos.apiKeys on the enriched pull query\n [TranscendPullResource.DataSilos]: [\n ScopeName.ViewDataMap,\n ScopeName.ViewDataSubjectRequestSettings,\n ScopeName.ViewApiKeys,\n ],\n [TranscendPullResource.Enrichers]: [ScopeName.ViewRequestIdentitySettings],\n [TranscendPullResource.BusinessEntities]: [ScopeName.ViewDataInventory],\n [TranscendPullResource.ProcessingActivities]: [ScopeName.ViewDataInventory],\n [TranscendPullResource.Identifiers]: [ScopeName.ViewRequestIdentitySettings],\n [TranscendPullResource.Attributes]: [ScopeName.ViewGlobalAttributes],\n [TranscendPullResource.DataFlows]: [ScopeName.ViewDataFlow],\n [TranscendPullResource.Cookies]: [ScopeName.ViewDataFlow],\n [TranscendPullResource.ConsentManager]: [ScopeName.ViewConsentManager],\n [TranscendPullResource.Partitions]: [ScopeName.ViewConsentManager],\n [TranscendPullResource.Actions]: [ScopeName.ViewDataSubjectRequestSettings],\n [TranscendPullResource.DataSubjects]: [ScopeName.ViewDataSubjectRequestSettings],\n [TranscendPullResource.Vendors]: [ScopeName.ViewDataInventory],\n [TranscendPullResource.DataCategories]: [ScopeName.ViewDataInventory],\n [TranscendPullResource.ProcessingPurposes]: [ScopeName.ViewDataInventory],\n [TranscendPullResource.ActionItemCollections]: [ScopeName.ViewAllActionItems],\n [TranscendPullResource.ActionItems]: [ScopeName.ViewAllActionItems],\n [TranscendPullResource.Teams]: [ScopeName.ViewScopes],\n [TranscendPullResource.Messages]: [ScopeName.ViewIntlMessages],\n [TranscendPullResource.PrivacyCenters]: [ScopeName.ViewPrivacyCenter],\n [TranscendPullResource.Policies]: [ScopeName.ViewPolicies],\n [TranscendPullResource.Assessments]: [ScopeName.ViewAssessments],\n [TranscendPullResource.AssessmentTemplates]: [ScopeName.ViewAssessments],\n [TranscendPullResource.Purposes]: [\n ScopeName.ViewConsentManager,\n ScopeName.ViewPreferenceStoreSettings,\n ],\n [TranscendPullResource.PreferenceOptions]: [ScopeName.ViewPreferenceStoreSettings],\n [TranscendPullResource.SystemDiscovery]: [ScopeName.ViewDataMap],\n // ViewPreferenceStoreSettings — consentWorkflowTriggers GraphQL query\n // ManageWorkflows — resolve workflowConfigId → workflow-title for V2 configs\n [TranscendPullResource.PreferenceWorkflowConfigs]: [\n ScopeName.ViewPreferenceStoreSettings,\n ScopeName.ManageWorkflows,\n ],\n [TranscendPullResource.WorkflowConfigs]: [ScopeName.ViewWorkflows],\n};\n\nexport const TR_YML_RESOURCE_TO_FIELD_NAME: Record<TranscendPullResource, keyof TranscendInput> = {\n [TranscendPullResource.ApiKeys]: 'api-keys',\n [TranscendPullResource.Attributes]: 'attributes',\n [TranscendPullResource.DataFlows]: 'data-flows',\n [TranscendPullResource.Cookies]: 'cookies',\n [TranscendPullResource.ConsentManager]: 'consent-manager',\n [TranscendPullResource.Partitions]: 'partitions',\n [TranscendPullResource.Actions]: 'actions',\n [TranscendPullResource.DataSubjects]: 'data-subjects',\n [TranscendPullResource.BusinessEntities]: 'business-entities',\n [TranscendPullResource.ProcessingActivities]: 'processing-activities',\n [TranscendPullResource.Identifiers]: 'identifiers',\n [TranscendPullResource.Enrichers]: 'preflights',\n [TranscendPullResource.DataSilos]: 'data-silos',\n [TranscendPullResource.Templates]: 'templates',\n [TranscendPullResource.Vendors]: 'vendors',\n [TranscendPullResource.DataCategories]: 'data-categories',\n [TranscendPullResource.ProcessingPurposes]: 'processing-purposes',\n [TranscendPullResource.ActionItems]: 'action-items',\n [TranscendPullResource.ActionItemCollections]: 'action-item-collections',\n [TranscendPullResource.Teams]: 'teams',\n [TranscendPullResource.Messages]: 'messages',\n [TranscendPullResource.PrivacyCenters]: 'privacy-center',\n [TranscendPullResource.Policies]: 'policies',\n [TranscendPullResource.Assessments]: 'assessments',\n [TranscendPullResource.AssessmentTemplates]: 'assessment-templates',\n [TranscendPullResource.Purposes]: 'purposes',\n [TranscendPullResource.PreferenceOptions]: 'preference-options',\n [TranscendPullResource.SystemDiscovery]: 'system-discovery',\n [TranscendPullResource.PreferenceWorkflowConfigs]: 'preference-workflow-configs',\n [TranscendPullResource.WorkflowConfigs]: 'workflow-configs',\n};\n\nexport const SCOPES_BY_TITLE = keyBy(\n Object.entries(TRANSCEND_SCOPES).map(([name, value]) => ({\n ...value,\n name,\n })),\n 'title',\n) as Record<\n string,\n ScopeDefinition & {\n /** The camelCased name which identifies the scope */\n name: ScopeName;\n }\n>;\n\nexport const SCOPE_TITLES = Object.keys(SCOPES_BY_TITLE);\n\n/**\n * HTTP statuses that should be retried *in place* without splitting.\n * 429: Rate-limited\n * 502: Upstream/edge gateway error\n * 329: Reserved for custom infra (kept defensively)\n */\nexport const RETRYABLE_BATCH_STATUSES = new Set([429, 502, 500, 504, 329] as const);\n\n/**\n * Debugging\n */\nexport const DEBUG = process.env.DEBUG === '1';\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{i as e}from"./constants-
|
|
1
|
+
import{i as e}from"./constants-I694qbRg.mjs";import{basename as t}from"node:path";import n from"colors";import{createReadStream as r,readFileSync as i,statSync as a}from"node:fs";import*as o from"node:readline";const s={readFile:e=>i(e,`utf8`),stdout:process.stdout};function c(e,t,n,r={}){let i=r.readFile??s.readFile,a=r.stdout??s.stdout;a.write(`\x1B[2J\x1B[H`);let o=e=>/\b(ERROR|uncaughtException|unhandledRejection)\b/i.test(e),c=e=>/\b(WARN|WARNING)\b/i.test(e),l=[];for(let[,r]of e){if(!r)continue;let e=[];for(let n of t)n===`out`&&r.outPath&&e.push({path:r.outPath,src:`out`}),n===`err`&&r.errPath&&e.push({path:r.errPath,src:`err`}),n===`structured`&&r.structuredPath&&e.push({path:r.structuredPath,src:`structured`}),r.warnPath&&n===`warn`&&e.push({path:r.warnPath,src:`warn`}),r.infoPath&&n===`info`&&e.push({path:r.infoPath,src:`info`});for(let{path:t,src:r}of e){let e=``;try{e=i(t)}catch{continue}for(let t of e.split(`
|
|
2
2
|
`)){if(!t)continue;let e=t.replace(/\x1B\[[0-9;]*m/g,``);if(n===`all`){l.push(t);continue}if(n===`error`){o(e)&&l.push(t);continue}if(c(e)||r===`err`&&!o(e)){l.push(t);continue}}}}l.sort((e,t)=>{let n=e.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)?.[0]??``,r=t.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)?.[0]??``;return n.localeCompare(r)}),a.write(`${l.join(`
|
|
3
3
|
`)}\n`),a.write(`
|
|
4
4
|
Press Esc/Ctrl+] to return to dashboard.
|
|
@@ -11,4 +11,4 @@ Press Esc/Ctrl+] to return to dashboard.
|
|
|
11
11
|
`);!e.final&&i===g.get(r.stdout)||(g.set(r.stdout,i),e.final?r.stdout.write(`\x1B[?25h`):(r.stdout.write(`\x1B[?25l`),r.cursorTo(r.stdout,0,0),r.clearScreenDown(r.stdout)),r.stdout.write(`${i}\n`))}function y(e){return typeof e==`number`?e.toLocaleString():`0`}function b(e,t=40){let n=Math.floor(Math.max(0,Math.min(100,Math.floor(e)))/100*t);return`█`.repeat(n)+`░`.repeat(t-n)}function x(e){let t=[...e.workerState.values()].filter(e=>e.busy).length,n=e.filesCompleted+e.filesFailed;return{done:n,inProgress:t,pct:e.filesTotal===0?100:Math.floor(n/Math.max(1,e.filesTotal)*100)}}function S(e,t=[]){let{title:r,poolSize:i,cpuCount:a,filesTotal:o,filesCompleted:s,filesFailed:c,throughput:l}=e,{inProgress:u,pct:d}=x(e),f=[`${n.bold(r)} — ${i} workers ${n.dim(`(CPU avail: ${a})`)}`,`${n.dim(`Files`)} ${y(o)} ${n.dim(`Completed`)} ${y(s)} ${n.dim(`Failed`)} ${c?n.red(y(c)):y(c)} ${n.dim(`In-flight`)} ${y(u)}`,`[${b(d)}] ${d}%`];if(l){let t=l.jobsR10s>0||l.jobsR60s>0,r=Math.round((t?l.jobsR10s:l.r10s)*3600).toLocaleString(),i=Math.round((t?l.jobsR60s:l.r60s)*3600).toLocaleString(),a=t?`rec`:`files`,o=e.throughput?.successSoFar==null?``:` Newly uploaded: ${y(e.throughput.successSoFar)}`;f.push(n.cyan(`Throughput: ${r} ${a}/hr (1h: ${i} ${a}/hr)${o}`))}return t.length?f.concat(t):f}function C(e,r=e=>e?t(e):`-`){return[...e.workerState.entries()].map(([e,t])=>{let i=t.lastLevel===`error`?n.red(`ERROR `):t.lastLevel===`warn`?n.yellow(`WARN `):t.busy?n.green(`WORKING`):n.dim(`IDLE `),a=r(t.file),o=t.startedAt?`${Math.floor((Date.now()-t.startedAt)/1e3)}s`:`-`,s=t.progress?.processed??0,c=t.progress?.total??0,l=c>0?Math.floor(s/c*100):0;return` [w${e}] ${i} | ${a} | ${o} | [${c>0?b(l,18):` `.repeat(18)}] ${c>0?`${s.toLocaleString()}/${c.toLocaleString()} (${l}%)`:n.dim(`—`)}`})}function w(e){let{logsBySlot:t,repaint:n,setPaused:r,exportMgr:i,exportStatus:a,stdout:o=process.stdout,now:s=Date.now,custom:l}=e,u=e=>{o.write(`${e}\n`)},d=(e,t)=>{let r=s(),i=a?.[e]??{path:t};a&&(a[e]={path:t||i.path,savedAt:r,exported:!0},n())},f=!1,p=(e,i)=>{f||(f=!0,r(!0),o.write(`\x1B[2J\x1B[H`),o.write(`Combined logs viewer (press Esc or Ctrl+] to return)
|
|
12
12
|
|
|
13
13
|
`),(async()=>{try{await c(t,e,i,{stdout:o})}catch{f=!1,r(!1),n()}})())},m=(e,n)=>{if(i)try{let r=i.exportCombinedLogs(t,e);u(`\nWrote combined ${n} logs to: ${r}`),d(e,r)}catch{u(`\nFailed to write combined ${n} logs`)}};return e=>{let t=e.toString(`utf8`);if(t===`e`){p([`err`],`error`);return}if(t===`w`){p([`warn`,`err`],`warn`);return}if(t===`i`){p([`info`],`all`);return}if(t===`l`){p([`out`,`err`,`structured`],`all`);return}if(t===`E`){m(`error`,`error`);return}if(t===`W`){m(`warn`,`warn`);return}if(t===`I`){m(`info`,`info`);return}if(t===`A`){m(`all`,`ALL`);return}let i=l?.[t];if(i){i({noteExport:d,say:u});return}(t===`\x1B`||t===``)&&(f=!1,r(!1),n())}}function T(e,t,n){let{process:r}=e;return{render:e=>v(e,t,n,{stdout:r.stdout,cursorTo:o.cursorTo,clearScreenDown:o.clearScreenDown}),installInteractiveSwitcher:n?void 0:({workers:e,onCtrlC:t,getLogPaths:n,replayBytes:i,replayWhich:a,setPaused:s,repaint:c})=>m({workers:e,onCtrlC:t,getLogPaths:n,replayBytes:i,replayWhich:a,ports:{stdin:r.stdin,stdout:r.stdout,stderr:r.stderr,emitKeypressEvents:o.emitKeypressEvents},onAttach:()=>s(!0),onDetach:()=>{s(!1),c()},onEnterAttachScreen:e=>{s(!0),r.stdout.write(`\x1B[2J\x1B[H`),r.stdout.write(`Attached to worker ${e}. (Esc/Ctrl+] detach • Ctrl+D EOF • Ctrl+C SIGINT)\n`)}}),extraKeyHandler:({logsBySlot:e,repaint:t,setPaused:n})=>w({logsBySlot:e,repaint:t,setPaused:n,stdout:r.stdout})}}export{S as n,C as r,T as t};
|
|
14
|
-
//# sourceMappingURL=createPoolingCommandUi-
|
|
14
|
+
//# sourceMappingURL=createPoolingCommandUi-BZr2HSiZ.mjs.map
|
package/dist/{createPoolingCommandUi-BdtnLBBM.mjs.map → createPoolingCommandUi-BZr2HSiZ.mjs.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createPoolingCommandUi-BdtnLBBM.mjs","names":["defaultPorts"],"sources":["../src/lib/pooling/showCombinedLogs.ts","../src/lib/pooling/keymap.ts","../src/lib/pooling/replayFileTailToStdout.ts","../src/lib/pooling/workerIds.ts","../src/lib/pooling/installInteractiveSwitcher.ts","../src/lib/pooling/dashboardPlugin.ts","../src/lib/pooling/uiPlugins.ts","../src/lib/pooling/createExtraKeyHandler.ts","../src/lib/pooling/createPoolingCommandUi.ts"],"sourcesContent":["/* eslint-disable no-continue, no-control-regex */\nimport { readFileSync } from 'node:fs';\n\nimport type { WorkerLogPaths } from '@transcend-io/utils';\n\n/**\n * Log locations\n */\nexport type LogLocation = 'out' | 'err' | 'structured' | 'warn' | 'info';\n\n/**\n * Which logs to show in the combined output.\n * Can include 'out' (stdout), 'err' (stderr), 'structured' (\n */\nexport type WhichLogs = Array<LogLocation>;\n\n/**\n * External operations used while rendering combined logs.\n */\nexport interface ShowCombinedLogsPorts {\n /** Read a UTF-8 log file from disk. */\n readFile: (path: string) => string;\n /** Write rendered output to the terminal. */\n stdout: Pick<NodeJS.WriteStream, 'write'>;\n}\n\nconst defaultPorts: ShowCombinedLogsPorts = {\n readFile: (path) => readFileSync(path, 'utf8'),\n stdout: process.stdout,\n};\n\n/**\n * Show combined logs from all worker processes.\n *\n * @param slotLogPaths - Map of worker IDs to their log file paths.\n * @param whichList - one or more sources to include (e.g., ['err','out'])\n * @param filterLevel - 'error', 'warn', or 'all' to filter log levels.\n * @param ports - Optional file and terminal operations.\n */\nexport function showCombinedLogs(\n slotLogPaths: Map<number, WorkerLogPaths | undefined>,\n whichList: WhichLogs,\n filterLevel: 'error' | 'warn' | 'all',\n ports: Partial<ShowCombinedLogsPorts> = {},\n): void {\n const readFile = ports.readFile ?? defaultPorts.readFile;\n const stdout = ports.stdout ?? defaultPorts.stdout;\n\n stdout.write('\\x1b[2J\\x1b[H');\n\n const isError = (t: string): boolean =>\n /\\b(ERROR|uncaughtException|unhandledRejection)\\b/i.test(t);\n const isWarnTag = (t: string): boolean => /\\b(WARN|WARNING)\\b/i.test(t);\n\n const lines: string[] = [];\n\n for (const [, paths] of slotLogPaths) {\n if (!paths) continue;\n\n const files: Array<{\n /** Absolute file path to read from */\n path: string;\n /** Source type for this file, used for classification */\n src: LogLocation;\n }> = [];\n for (const which of whichList) {\n if (which === 'out' && paths.outPath) {\n files.push({ path: paths.outPath, src: 'out' });\n }\n if (which === 'err' && paths.errPath) {\n files.push({ path: paths.errPath, src: 'err' });\n }\n if (which === 'structured' && paths.structuredPath) {\n files.push({ path: paths.structuredPath, src: 'structured' });\n }\n if (paths.warnPath && which === 'warn') {\n files.push({ path: paths.warnPath, src: 'warn' });\n }\n if (paths.infoPath && which === 'info') {\n files.push({ path: paths.infoPath, src: 'info' });\n }\n }\n\n for (const { path, src } of files) {\n let text = '';\n try {\n text = readFile(path);\n } catch {\n continue;\n }\n\n for (const ln of text.split('\\n')) {\n if (!ln) continue;\n\n const clean = ln.replace(/\\x1B\\[[0-9;]*m/g, '');\n\n if (filterLevel === 'all') {\n lines.push(ln);\n continue;\n }\n\n if (filterLevel === 'error') {\n if (isError(clean)) lines.push(ln);\n continue;\n }\n\n // filterLevel === 'warn'\n // Accept:\n // - explicit WARN tag anywhere\n // - OR lines from stderr that are NOT explicit errors (many warn libs print to stderr)\n // - OR lines containing the word \"warning\" (common in some libs)\n if (isWarnTag(clean) || (src === 'err' && !isError(clean))) {\n lines.push(ln);\n continue;\n }\n }\n }\n }\n\n // simple time-sort; each worker often prefixes ISO timestamps\n lines.sort((a, b) => {\n const ta = a.match(/\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}/)?.[0] ?? '';\n const tb = b.match(/\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}/)?.[0] ?? '';\n return ta.localeCompare(tb);\n });\n\n stdout.write(`${lines.join('\\n')}\\n`);\n stdout.write('\\nPress Esc/Ctrl+] to return to dashboard.\\n');\n}\n/* eslint-enable no-continue, no-control-regex */\n","import type * as readline from 'node:readline';\n\n/**\n * Map a key press to an action in the interactive dashboard.\n */\nexport type Action =\n | {\n /** Indicates attaching to a session by id. */\n type: 'ATTACH';\n /** The id of the session to attach to. */\n id: number;\n }\n | {\n /** Indicates cycling through sessions. */\n type: 'CYCLE';\n /** The direction to cycle: +1 for next, -1 for previous. */\n delta: number;\n }\n | {\n /** Indicates detaching from the current session. */\n type: 'DETACH';\n }\n | {\n /** Indicates the Ctrl+C key combination was pressed. */\n type: 'CTRL_C';\n }\n | {\n /** Indicates the Ctrl+D key combination was pressed. */\n type: 'CTRL_D';\n }\n | {\n /** Indicates quitting the dashboard. */\n type: 'QUIT';\n }\n | {\n /** Forwards an unhandled key sequence. */\n type: 'FORWARD';\n /** The key sequence to forward. */\n sequence: string;\n };\n\n/**\n * Map a key press to an action in the interactive dashboard.\n *\n * @param str - The string representation of the key press.\n * @param key - The key object containing details about the key press.\n * @param mode - The current mode of the dashboard, either 'dashboard' or 'attached'.\n * @returns An Action object representing the mapped action, or null if no action is mapped.\n */\nexport function keymap(\n str: string,\n key: readline.Key,\n mode: 'dashboard' | 'attached',\n): Action | null {\n if (key.ctrl && key.name === 'c') return { type: 'CTRL_C' };\n\n if (mode === 'dashboard') {\n if (key.name && /^[0-9]$/.test(key.name)) {\n return { type: 'ATTACH', id: Number(key.name) };\n }\n if (key.name === 'tab' && !key.shift) return { type: 'CYCLE', delta: +1 };\n if (key.name === 'tab' && key.shift) return { type: 'CYCLE', delta: -1 };\n if (key.name === 'q') return { type: 'QUIT' };\n return null;\n }\n\n // attached\n if (key.name === 'escape' || (key.ctrl && key.name === ']')) {\n return { type: 'DETACH' };\n }\n if (key.ctrl && key.name === 'd') return { type: 'CTRL_D' };\n\n const sequence = key.sequence ?? str ?? '';\n return sequence ? { type: 'FORWARD', sequence } : null;\n}\n","import { createReadStream, statSync } from 'node:fs';\n\n/** File metadata needed when replaying a tail. */\nexport interface ReplayFileStat {\n /** File size in bytes */\n readonly size: number;\n}\n\n/** Dependencies used to replay a file tail. */\nexport interface ReplayFileTailToStdoutDependencies {\n /** Create a stream for the requested file range. */\n createReadStream: typeof createReadStream;\n /** Read file metadata synchronously. */\n statSync: (path: string) => ReplayFileStat;\n}\n\nconst defaultDependencies: ReplayFileTailToStdoutDependencies = {\n createReadStream,\n statSync,\n};\n\n/**\n * Replay the tail of a file to stdout.\n *\n * @param path - The absolute path to the file to read.\n * @param maxBytes - The maximum number of bytes to read from the end of the file.\n * @param write - A function to write the output to stdout.\n * @param dependencies - File-system operations used by the replay.\n */\nexport async function replayFileTailToStdout(\n path: string,\n maxBytes: number,\n write: (s: string) => void,\n dependencies: ReplayFileTailToStdoutDependencies = defaultDependencies,\n): Promise<void> {\n await new Promise<void>((resolve) => {\n try {\n const st = dependencies.statSync(path);\n const start = Math.max(0, st.size - maxBytes);\n const stream = dependencies.createReadStream(path, { start, encoding: 'utf8' });\n stream.on('data', (chunk) => write(chunk as string));\n stream.on('end', () => resolve());\n stream.on('error', () => resolve());\n } catch {\n resolve();\n }\n });\n}\n","import type { ChildProcess } from 'node:child_process';\n\n/**\n * Get the sorted list of worker IDs from a map of ChildProcess instances.\n *\n * @param m - Map of worker IDs to ChildProcess instances.\n * @returns Sorted array of worker IDs.\n */\nexport function getWorkerIds(m: Map<number, ChildProcess>): number[] {\n return [...m.keys()].sort((a, b) => a - b);\n}\n\n/**\n * Cycles through an array of numeric IDs, returning the next ID based on a delta.\n *\n * If the `current` ID is not provided or not found in the array, the first ID is used as the starting point.\n * The function then moves forward or backward in the array by `delta` positions, wrapping around if necessary.\n *\n * @param ids - Array of numeric IDs to cycle through.\n * @param current - The current ID to start cycling from. If `null` or not found, starts from the first ID.\n * @param delta - The number of positions to move forward (positive) or backward (negative) in the array.\n * @returns The next ID in the array after cycling, or `null` if the array is empty.\n */\nexport function cycleWorkers(ids: number[], current: number | null, delta: number): number | null {\n if (!ids.length) return null;\n const cur = current == null ? ids[0] : current;\n let i = ids.indexOf(cur);\n if (i === -1) i = 0;\n i = (i + delta + ids.length) % ids.length;\n return ids[i]!;\n}\n","import type { ChildProcess } from 'node:child_process';\nimport * as readline from 'node:readline';\n\nimport type { WorkerLogPaths } from '@transcend-io/utils';\n\nimport { DEBUG } from '../../constants.js';\nimport { keymap } from './keymap.js';\nimport { replayFileTailToStdout } from './replayFileTailToStdout.js';\nimport type { WhichLogs } from './showCombinedLogs.js';\nimport { cycleWorkers, getWorkerIds } from './workerIds.js';\n\n/**\n * Key action types for the interactive switcher\n */\nexport type InteractiveDashboardMode = 'dashboard' | 'attached';\n\nexport interface SwitcherPorts {\n /** Standard input stream */\n stdin: NodeJS.ReadStream;\n /** Standard output stream */\n stdout: NodeJS.WriteStream;\n /** Standard error stream */\n stderr: NodeJS.WriteStream;\n /** Enable keypress events on the input stream */\n emitKeypressEvents?: typeof readline.emitKeypressEvents;\n}\n\n/**\n * Install an interactive switcher for managing worker processes.\n *\n * @param opts - Options for the switcher\n * @returns A cleanup function to remove the switcher\n */\nexport function installInteractiveSwitcher(opts: {\n /** Registry of live workers by id */\n workers: Map<number, ChildProcess>;\n /** Hooks */\n onAttach?: (id: number) => void;\n /** Optional detach handler */\n onDetach?: () => void;\n /** Optional Ctrl+C handler for parent graceful shutdown in dashboard */\n onCtrlC?: () => void; // parent graceful shutdown in dashboard\n /** Provide log paths so we can replay the tail on attach */\n getLogPaths?: (id: number) => WorkerLogPaths | undefined;\n /** How many bytes to replay from the end of each file (default 200 KB) */\n replayBytes?: number;\n /** Which logs to replay first (default ['out','err']) */\n replayWhich?: WhichLogs;\n /** Print a small banner/clear screen before replaying (optional) */\n onEnterAttachScreen?: (id: number) => void;\n /** Optional stdio ports for testing; defaults to process stdio */\n ports?: SwitcherPorts;\n}): () => void {\n const {\n workers,\n onAttach,\n onDetach,\n onCtrlC,\n getLogPaths,\n replayBytes = 200 * 1024,\n replayWhich = ['out', 'err'],\n onEnterAttachScreen,\n ports,\n } = opts;\n\n const stdin = ports?.stdin ?? process.stdin;\n const stdout = ports?.stdout ?? process.stdout;\n const stderr = ports?.stderr ?? process.stderr;\n\n const d = (...a: unknown[]): void => {\n if (DEBUG) {\n try {\n (ports?.stderr ?? process.stderr).write(`[keys] ${a.map(String).join(' ')}\\n`);\n } catch {\n // noop\n }\n }\n };\n\n if (!stdin.isTTY) {\n // Not a TTY; return a no-op cleanup\n return () => {\n // noop\n };\n }\n\n (ports?.emitKeypressEvents ?? readline.emitKeypressEvents)(stdin);\n stdin.setRawMode?.(true);\n\n let mode: InteractiveDashboardMode = 'dashboard';\n let focus: number | null = null;\n\n // live mirroring handlers while attached\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let outHandler: ((chunk: any) => void) | null = null;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let errHandler: ((chunk: any) => void) | null = null;\n\n /**\n * Cycle through worker IDs, wrapping around.\n *\n * @param id - The current worker ID to start cycling from.\n * @returns The next worker ID after cycling, or null if no workers are available.\n */\n async function replayLogs(id: number): Promise<void> {\n if (!getLogPaths) return;\n const paths = getLogPaths(id);\n if (!paths) return;\n\n const toReplay: string[] = [];\n for (const which of replayWhich) {\n if (which === 'out') toReplay.push(paths.outPath);\n if (which === 'err') toReplay.push(paths.errPath);\n if (which === 'structured') toReplay.push(paths.structuredPath);\n }\n\n if (toReplay.length) {\n stdout.write('\\n------------ replay ------------\\n');\n for (const p of toReplay) {\n stdout.write(`\\n--- ${p} (last ~${Math.floor(replayBytes / 1024)}KB) ---\\n`);\n await replayFileTailToStdout(p, replayBytes, (s) => stdout.write(s));\n }\n stdout.write('\\n--------------------------------\\n\\n');\n }\n }\n\n const attach = async (id: number): Promise<void> => {\n d('attach()', `id=${id}`); // at function entry\n\n const w = workers.get(id);\n if (!w) return;\n\n // Detach any previous focus\n if (mode === 'attached') detach();\n\n mode = 'attached';\n focus = id;\n\n // UX: clear + banner\n onEnterAttachScreen?.(id);\n\n onAttach?.(id); // prints “Attached to worker …” and clears\n await replayLogs(id); // now the tail stays visible\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n outHandler = (chunk: any) => stdout.write(chunk);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n errHandler = (chunk: any) => stderr.write(chunk);\n w.stdout?.on('data', outHandler);\n w.stderr?.on('data', errHandler);\n\n // auto-detach if child exits\n const onExit = (): void => {\n if (focus === id) detach();\n };\n w.once('exit', onExit);\n };\n\n const detach = (): void => {\n d('detach()', `id=${focus}`); // at function entry\n\n if (focus == null) return;\n const id = focus;\n const w = workers.get(id);\n if (w) {\n if (outHandler) w.stdout?.off('data', outHandler);\n if (errHandler) w.stderr?.off('data', errHandler);\n }\n outHandler = null;\n errHandler = null;\n focus = null;\n mode = 'dashboard';\n onDetach?.();\n };\n\n const onKey = (str: string, key: readline.Key): void => {\n d(\n 'keypress',\n JSON.stringify({\n str,\n name: key.name,\n seq: key.sequence,\n ctrl: key.ctrl,\n meta: key.meta,\n shift: key.shift,\n mode,\n }),\n );\n const act = keymap(str, key, mode);\n d('mapped', JSON.stringify(act));\n\n if (!act) return;\n\n // eslint-disable-next-line default-case\n switch (act.type) {\n case 'CTRL_C': {\n d('CTRL_C');\n if (mode === 'attached' && focus != null) {\n const w = workers.get(focus);\n try {\n w?.kill('SIGINT');\n } catch {\n // noop\n }\n // optional: auto-detach so second Ctrl+C exits parent\n detach();\n return;\n }\n onCtrlC?.();\n return;\n }\n\n case 'ATTACH': {\n d('ATTACH', `id=${act.id}`, `has=${workers.has(act.id)}`);\n\n if (mode !== 'dashboard') return;\n // eslint-disable-next-line no-void\n if (workers.has(act.id)) void attach(act.id);\n return;\n }\n\n case 'CYCLE': {\n d('CYCLE', `delta=${act.delta}`);\n if (mode !== 'dashboard') return;\n const next = cycleWorkers(getWorkerIds(workers), focus, act.delta);\n // eslint-disable-next-line no-void\n if (next != null) void attach(next);\n return;\n }\n\n case 'QUIT': {\n if (mode !== 'dashboard') return;\n onCtrlC?.();\n return;\n }\n\n case 'DETACH': {\n d('DETACH');\n if (mode === 'attached') detach();\n return;\n }\n\n case 'CTRL_D': {\n if (mode === 'attached' && focus != null) {\n const w = workers.get(focus);\n try {\n w?.stdin?.end();\n } catch {\n // noop\n }\n }\n return;\n }\n\n case 'FORWARD': {\n if (mode === 'attached' && focus != null) {\n const w = workers.get(focus);\n try {\n w?.stdin?.write(act.sequence);\n } catch {\n // noop\n }\n }\n }\n }\n };\n\n // Raw bytes fallback (usually not hit because keypress handles it)\n const onData = (chunk: Buffer): void => {\n if (mode === 'attached' && focus != null) {\n const w = workers.get(focus);\n try {\n w?.stdin?.write(chunk);\n } catch {\n // noop\n }\n }\n };\n\n const cleanup = (): void => {\n stdin.off('keypress', onKey);\n stdin.off('data', onData);\n stdin.setRawMode?.(false);\n stdout.write('\\x1b[?25h');\n };\n\n stdin.on('keypress', onKey);\n stdin.on('data', onData);\n\n return cleanup;\n}\n","// lib/pooling/dashboardPlugin.ts\nimport * as readline from 'node:readline';\n\nimport type { ObjByString } from '@transcend-io/type-utils';\nimport type { SlotState } from '@transcend-io/utils';\nimport colors from 'colors';\n\n/**\n * A dashboard plugin defines how to render the worker pool UI.\n * Commands can supply a plugin to customize:\n * - The header block (summary stats, title, etc.)\n * - Per-worker rows (one line per worker slot)\n * - Optional extras (artifact exports, breakdowns, footers)\n *\n * @template TTotals - The shape of the aggregate totals object maintained by the command.\n */\nexport interface DashboardPlugin<TTotals, TSlotState extends ObjByString> {\n /**\n * Render the header block of the dashboard.\n *\n * @param ctx - Context with pool/worker state, totals, and metadata.\n * @returns An array of strings, each representing one line in the header.\n */\n renderHeader: (ctx: CommonCtx<TTotals, TSlotState>) => string[];\n\n /**\n * Render per-worker rows, usually one line per worker slot.\n *\n * @param ctx - Context with pool/worker state, totals, and metadata.\n * @returns An array of strings, each representing one row in the workers section.\n */\n renderWorkers: (ctx: CommonCtx<TTotals, TSlotState>) => string[];\n\n /**\n * Render any optional extra blocks that appear after the worker rows.\n * Useful for printing export paths, aggregated metrics, breakdowns, etc.\n *\n * @param ctx - Context with pool/worker state, totals, and metadata.\n * @returns An array of strings, each representing one additional line.\n */\n renderExtras?: (ctx: CommonCtx<TTotals, TSlotState>) => string[];\n}\n\n/**\n * Shared context object passed into all render methods of a {@link DashboardPlugin}.\n *\n * @template TTotals - The shape of the aggregate totals object maintained by the command.\n */\nexport type CommonCtx<TTotals, TSlotState extends ObjByString> = {\n /** Human-readable title for the dashboard (e.g., \"Parallel uploader\"). */\n title: string;\n\n /** Number of worker processes spawned in the pool. */\n poolSize: number;\n\n /** Logical CPU count, included for informational display. */\n cpuCount: number;\n\n /** Total number of \"files\" or logical units the command expects to process. */\n filesTotal: number;\n\n /** Count of successfully completed files/tasks. */\n filesCompleted: number;\n\n /** Count of failed files/tasks. */\n filesFailed: number;\n\n /**\n * State of each worker slot, keyed by worker id.\n * Includes busy flag, file label, start time, last log badge, and progress.\n */\n workerState: Map<number, SlotState<TSlotState>>;\n\n /**\n * Aggregate totals maintained by the command’s hook logic.\n * Domain-specific metrics (e.g., rows uploaded, bytes processed) can be surfaced here.\n */\n totals: TTotals;\n\n /**\n * Throughput metrics tracked by the runner:\n * - successSoFar: convenience alias for completed count\n * - r10s: completions/sec averaged over last 10s\n * - r60s: completions/sec averaged over last 60s\n */\n throughput: {\n /** Cumulative count of successful completions so far. */\n successSoFar: number;\n /** Recent file-level throughput rate over the last 10 seconds. */\n r10s: number;\n /** Recent file-level throughput rate over the last 60 seconds. */\n r60s: number;\n /** Recent job/record-level throughput rate over the last 10 seconds. */\n jobsR10s: number;\n /** Recent job/record-level throughput rate over the last 60 seconds. */\n jobsR60s: number;\n };\n\n /** True when the pool has fully drained and all workers have exited. */\n final: boolean;\n\n /**\n * Optional export status payload provided by the command.\n * Useful for rendering artifact paths or \"latest export\" summaries.\n */\n exportStatus?: Record<string, unknown>;\n};\n\n/**\n * Terminal operations used to paint the dashboard.\n */\nexport interface DashboardPorts {\n /** Standard output stream used for dashboard rendering. */\n stdout: NodeJS.WriteStream;\n /** Move the output cursor before repainting. */\n cursorTo: typeof readline.cursorTo;\n /** Clear terminal content below the cursor. */\n clearScreenDown: typeof readline.clearScreenDown;\n}\n\nconst defaultPorts: DashboardPorts = {\n stdout: process.stdout,\n cursorTo: readline.cursorTo,\n clearScreenDown: readline.clearScreenDown,\n};\n\n/** Most recently rendered frame per output stream, cached to suppress duplicate renders. */\nconst lastFrames = new WeakMap<NodeJS.WriteStream, string>();\n\n/**\n * Generate the hotkeys hint string that appears at the bottom of the dashboard.\n *\n * @param poolSize - The number of worker slots in the pool.\n * @param final - Whether the run has completed.\n * @returns A dimmed string listing the supported hotkeys for attach/detach/quit.\n */\nexport const hotkeysHint = (poolSize: number, final: boolean): string => {\n const maxDigit = Math.min(poolSize - 1, 9);\n const digitRange = poolSize <= 1 ? '0' : `0-${maxDigit}`;\n const extra = poolSize > 10 ? ' (Tab/Shift+Tab for ≥10)' : '';\n return final\n ? colors.dim(\n 'Run complete — digits to view logs • Tab/Shift+Tab cycle • Esc/Ctrl+] detach • q to quit',\n )\n : colors.dim(\n `Hotkeys: [${digitRange}] attach${extra} • e=errors • w=warnings • i=info • l=logs • ` +\n 'Tab/Shift+Tab • Esc/Ctrl+] detach • Ctrl+C exit',\n );\n};\n\n/**\n * Render the dashboard using a supplied {@link DashboardPlugin}.\n *\n * The frame is composed of:\n * - Header lines\n * - A blank separator\n * - Worker rows\n * - A blank separator\n * - Hotkeys hint\n * - Optional extras (if plugin supplies them)\n *\n * Optimizations:\n * - Suppresses re-renders if the frame is identical to the previous frame (flicker-free).\n * - Hides the terminal cursor during live updates, restoring it when final.\n *\n * @param ctx - Shared context containing pool state, worker state, totals, throughput, etc.\n * @param plugin - The plugin that defines how to render the header, workers, and optional extras.\n * @param viewerMode - If true, renders in viewer mode (no ability to switch between files).\n * @param ports - Optional terminal operations used to paint the dashboard.\n */\nexport function dashboardPlugin<TTotals, TSlotState extends ObjByString>(\n ctx: CommonCtx<TTotals, TSlotState>,\n plugin: DashboardPlugin<TTotals, TSlotState>,\n viewerMode = false,\n ports: DashboardPorts = defaultPorts,\n): void {\n const frame = [\n ...plugin.renderHeader(ctx),\n '',\n ...plugin.renderWorkers(ctx),\n ...(viewerMode ? [] : ['', hotkeysHint(ctx.poolSize, ctx.final)]),\n ...(plugin.renderExtras ? [''].concat(plugin.renderExtras(ctx)) : []),\n ].join('\\n');\n\n // Skip duplicate renders during live runs to avoid flicker.\n if (!ctx.final && frame === lastFrames.get(ports.stdout)) return;\n lastFrames.set(ports.stdout, frame);\n\n if (!ctx.final) {\n // Hide cursor and repaint in place\n ports.stdout.write('\\x1b[?25l');\n ports.cursorTo(ports.stdout, 0, 0);\n ports.clearScreenDown(ports.stdout);\n } else {\n // Restore cursor on final render\n ports.stdout.write('\\x1b[?25h');\n }\n ports.stdout.write(`${frame}\\n`);\n}\n","import { basename } from 'node:path';\n\nimport type { ObjByString } from '@transcend-io/type-utils';\nimport colors from 'colors';\n\nimport type { CommonCtx } from './dashboardPlugin.js';\n\n/**\n * Progress snapshot for a worker slot in the chunk-csv command.\n */\nexport type ChunkSlotProgress = {\n /** Absolute path of the file being processed by this worker. */\n filePath?: string;\n /** Number of rows processed so far in this file. */\n processed?: number;\n /** Optional total number of rows in the file (if known). */\n total?: number;\n};\n\n/**\n * Format a number safely for display.\n *\n * @param n - The number to format (or `undefined`).\n * @returns A localized string representation, or \"0\".\n */\nexport function fmtNum(n: number | undefined): string {\n return typeof n === 'number' ? n.toLocaleString() : '0';\n}\n\n/**\n * Draw a horizontal bar of length `width` filled to `pct` percent.\n *\n * @param pct - Percentage 0..100.\n * @param width - Number of characters in the bar.\n * @returns A string like \"████░░░░\".\n */\nexport function pctBar(pct: number, width = 40): string {\n const clamped = Math.max(0, Math.min(100, Math.floor(pct)));\n const filled = Math.floor((clamped / 100) * width);\n return '█'.repeat(filled) + '░'.repeat(width - filled);\n}\n\n/**\n * Compute pool-wide progress values needed by headers.\n *\n * @param ctx - Dashboard context containing pool state, worker state, totals, etc.\n * @returns An object with `done`, `inProgress`, and `pct` properties.\n */\nexport function poolProgress<TTotals, TSlot extends ObjByString>(\n ctx: CommonCtx<TTotals, TSlot>,\n): {\n /** Count of successfully completed files/tasks. */\n done: number;\n /** Count of currently in-progress files/tasks. */\n inProgress: number;\n /** Percentage of completion (0-100). */\n pct: number;\n} {\n const inProgress = [...ctx.workerState.values()].filter((s) => s.busy).length;\n const done = ctx.filesCompleted + ctx.filesFailed;\n const pct = ctx.filesTotal === 0 ? 100 : Math.floor((done / Math.max(1, ctx.filesTotal)) * 100);\n return { done, inProgress, pct };\n}\n\n/**\n * Compose the common header lines (title, pool stats, progress bar, throughput).\n *\n * @param ctx - Dashboard context.\n * @param extraLines - Optional extra lines (e.g., totals block).\n * @returns Header lines.\n */\nexport function makeHeader<TTotals, TSlot extends ObjByString>(\n ctx: CommonCtx<TTotals, TSlot>,\n extraLines: string[] = [],\n): string[] {\n const { title, poolSize, cpuCount, filesTotal, filesCompleted, filesFailed, throughput } = ctx;\n const { inProgress, pct } = poolProgress(ctx);\n\n const lines: string[] = [\n `${colors.bold(title)} — ${poolSize} workers ${colors.dim(`(CPU avail: ${cpuCount})`)}`,\n `${colors.dim('Files')} ${fmtNum(filesTotal)} ${colors.dim(\n 'Completed',\n )} ${fmtNum(filesCompleted)} ${colors.dim('Failed')} ${\n filesFailed ? colors.red(fmtNum(filesFailed)) : fmtNum(filesFailed)\n } ${colors.dim('In-flight')} ${fmtNum(inProgress)}`,\n `[${pctBar(pct)}] ${pct}%`,\n ];\n\n if (throughput) {\n const jobsActive = throughput.jobsR10s > 0 || throughput.jobsR60s > 0;\n const perHour10 = Math.round(\n (jobsActive ? throughput.jobsR10s : throughput.r10s) * 3600,\n ).toLocaleString();\n const perHour60 = Math.round(\n (jobsActive ? throughput.jobsR60s : throughput.r60s) * 3600,\n ).toLocaleString();\n const unit = jobsActive ? 'rec' : 'files';\n const suffix =\n ctx.throughput?.successSoFar != null\n ? ` Newly uploaded: ${fmtNum(ctx.throughput.successSoFar)}`\n : '';\n lines.push(\n colors.cyan(`Throughput: ${perHour10} ${unit}/hr (1h: ${perHour60} ${unit}/hr)${suffix}`),\n );\n }\n\n return extraLines.length ? lines.concat(extraLines) : lines;\n}\n\n/**\n * Render per-worker rows with a compact progress bar and status badge.\n *\n * @param ctx - Dashboard context (slot progress type must have processed/total?).\n * @param getFileLabel - Optional: override how the filename is shown.\n * @returns Array of strings, each representing one worker row.\n */\nexport function makeWorkerRows<TTotals, TSlot extends Omit<ChunkSlotProgress, 'filePath'>>(\n ctx: CommonCtx<TTotals, TSlot>,\n getFileLabel: (file: string | null | undefined) => string = (file) =>\n file ? basename(file) : '-',\n): string[] {\n const miniWidth = 18;\n\n return [...ctx.workerState.entries()].map(([id, s]) => {\n const badge =\n s.lastLevel === 'error'\n ? colors.red('ERROR ')\n : s.lastLevel === 'warn'\n ? colors.yellow('WARN ')\n : s.busy\n ? colors.green('WORKING')\n : colors.dim('IDLE ');\n\n const fname = getFileLabel(s.file);\n const elapsed = s.startedAt ? `${Math.floor((Date.now() - s.startedAt) / 1000)}s` : '-';\n\n const processed = s.progress?.processed ?? 0;\n const total = s.progress?.total ?? 0;\n const pctw = total > 0 ? Math.floor((processed / total) * 100) : 0;\n const mini = total > 0 ? pctBar(pctw, miniWidth) : ' '.repeat(miniWidth);\n const miniTxt =\n total > 0\n ? `${processed.toLocaleString()}/${total.toLocaleString()} (${pctw}%)`\n : colors.dim('—');\n\n return ` [w${id}] ${badge} | ${fname} | ${elapsed} | [${mini}] ${miniTxt}`;\n });\n}\n","import type { ExportStatusMap, SlotPaths } from '@transcend-io/utils';\n\nimport {\n showCombinedLogs,\n type LogLocation,\n type ShowCombinedLogsPorts,\n} from './showCombinedLogs.js';\n\n/** Severity filter applied by the viewer. */\ntype ViewLevel = 'error' | 'warn' | 'all';\n\n/**\n * Options for {@link createExtraKeyHandler}.\n */\nexport type CreateExtraKeyHandlerOpts = {\n /**\n * Per-slot log file paths maintained by the runner; used to stream or export logs.\n */\n logsBySlot: SlotPaths;\n\n /**\n * Request an immediate dashboard repaint (e.g., after updating export status).\n */\n repaint: () => void;\n\n /**\n * Pause/unpause dashboard repainting. The handler pauses while a viewer is open\n * to prevent the dashboard from overwriting the viewer output, then resumes on exit.\n */\n setPaused: (p: boolean) => void;\n\n /**\n * Optional export manager to enable uppercase export keys:\n * - `E` (errors) • `W` (warnings) • `I` (info) • `A` (all)\n *\n * Provide this only if your command supports writing combined log files.\n */\n exportMgr?: {\n /** Destination directory for exported artifacts. */\n exportsDir: string;\n /**\n * Write a combined log file for the selected severity and return the absolute path.\n *\n * @param logs - Log paths to combine.\n * @param which - Severity selection.\n * @returns Absolute path to the written file.\n */\n exportCombinedLogs: (logs: SlotPaths, which: 'error' | 'warn' | 'info' | 'all') => string;\n };\n\n /**\n * Optional “Exports” status map. If provided, the handler updates timestamps\n * when exports are written so your dashboard panel can reflect “last saved” times.\n */\n exportStatus?: ExportStatusMap;\n\n /** Standard output used for viewer and export messages. */\n stdout?: ShowCombinedLogsPorts['stdout'];\n\n /** Return the current Unix timestamp in milliseconds. */\n now?: () => number;\n\n /**\n * Optional custom key bindings for command-specific actions.\n * Each handler receives helpers to print messages and to update the exports panel.\n *\n * Example:\n * ```ts\n * custom: {\n * F: async ({ say, noteExport }) => {\n * const p = await writeFailingUpdatesCsv(...);\n * say(`Wrote failing updates to: ${p}`);\n * noteExport('failuresCsv', p);\n * }\n * }\n * ```\n */\n custom?: Record<\n string,\n (ctx: {\n /** Update {@link exportStatus} (if present) and repaint the dashboard. */\n noteExport: (slot: keyof ExportStatusMap, absPath: string) => void;\n /** Print a line to stdout, automatically newline-terminated. */\n say: (s: string) => void;\n }) => void | Promise<void>\n >;\n};\n\n/**\n * Create a keypress handler for interactive viewers/exports.\n * Shared handler for \"extra\" keyboard shortcuts used by the interactive dashboard.\n *\n * It wires:\n * - **Viewers (lowercase):** `e` (errors), `w` (warnings), `i` (info), `l` (all)\n * - **Exports (uppercase, optional):** `E` (errors), `W` (warnings), `I` (info), `A` (all)\n * - **Dismiss:** `Esc` or `Ctrl+]` exits a viewer and returns to the dashboard\n * - **Custom keys (optional):** Provide a `custom` map to handle command-specific bindings\n *\n * Usage (inside `runPool({... extraKeyHandler })`):\n * ```ts\n * extraKeyHandler: ({ logsBySlot, repaint, setPaused }) =>\n * createExtraKeyHandler({ logsBySlot, repaint, setPaused })\n * ```\n *\n * If you also want export hotkeys + an \"Exports\" panel:\n * ```ts\n * extraKeyHandler: ({ logsBySlot, repaint, setPaused }) =>\n * createExtraKeyHandler({\n * logsBySlot, repaint, setPaused,\n * exportMgr, // enables E/W/I/A\n * exportStatus, // keeps panel timestamps up to date\n * custom: { // optional, e.g. 'F' to export a CSV\n * F: async ({ say, noteExport }) => { ... }\n * }\n * })\n * ```\n *\n * @param opts - Configuration for viewers, exports, and custom keys.\n * @returns A `(buf: Buffer) => void` handler suitable for `process.stdin.on('data', ...)`.\n */\nexport function createExtraKeyHandler(opts: CreateExtraKeyHandlerOpts): (buf: Buffer) => void {\n const {\n logsBySlot,\n repaint,\n setPaused,\n exportMgr,\n exportStatus,\n stdout = process.stdout,\n now = Date.now,\n custom,\n } = opts;\n\n const say = (s: string): void => {\n stdout.write(`${s}\\n`);\n };\n\n /**\n * Record that an export was written and trigger a repaint so the dashboard’s\n * \"Exports\" panel shows the updated timestamp/path.\n *\n * @param slot - Slot name in {@link ExportStatusMap} (e.g., \"error\", \"warn\", etc.).\n * @param p - Absolute path to the exported file.\n */\n const noteExport = (slot: keyof ExportStatusMap, p: string): void => {\n const savedAt = now();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const cur: any = exportStatus?.[slot] ?? { path: p };\n if (exportStatus) {\n exportStatus[slot] = {\n path: p || cur.path,\n savedAt,\n exported: true,\n };\n repaint();\n }\n };\n\n let viewing = false; // optional guard to prevent stacking viewers\n\n /**\n * Show an inline combined log viewer for the selected sources/level.\n * Pauses dashboard repaint to keep the viewer visible until the user exits.\n *\n * @param sources - Log sources to include (e.g., \"err\", \"warn\", \"info\").\n * @param level - Severity level to filter by (e.g., \"error\", \"warn\", \"all\").\n */\n const view = (sources: LogLocation[], level: ViewLevel): void => {\n if (viewing) return;\n viewing = true;\n setPaused(true);\n\n // optional UX: clear screen and show a hint\n stdout.write('\\x1b[2J\\x1b[H'); // clear+home\n stdout.write('Combined logs viewer (press Esc or Ctrl+] to return)\\n\\n');\n\n (async () => {\n try {\n await showCombinedLogs(logsBySlot, sources, level, { stdout });\n // NOTE: do NOT unpause here; ESC will handle it.\n } catch {\n // If showCombinedLogs throws, recover and unpause\n viewing = false;\n setPaused(false);\n repaint();\n }\n })();\n };\n\n /**\n * Export combined logs (if an export manager was provided).\n *\n * @param which - Severity to export (e.g., \"error\", \"warn\", \"info\", \"all\").\n * @param label - Human-readable label for the export (e.g., \"error\", \"warn\").\n */\n const exportCombined = (which: 'error' | 'warn' | 'info' | 'all', label: string): void => {\n if (!exportMgr) return;\n try {\n const p = exportMgr.exportCombinedLogs(logsBySlot, which);\n say(`\\nWrote combined ${label} logs to: ${p}`);\n noteExport(which as keyof ExportStatusMap, p);\n } catch {\n say(`\\nFailed to write combined ${label} logs`);\n }\n };\n\n // The keypress handler the runner will attach to stdin.\n return (buf: Buffer): void => {\n const s = buf.toString('utf8');\n\n // Viewers (lowercase)\n if (s === 'e') {\n view(['err'], 'error');\n return;\n }\n if (s === 'w') {\n view(['warn', 'err'], 'warn');\n return;\n }\n if (s === 'i') {\n view(['info'], 'all');\n return;\n }\n if (s === 'l') {\n view(['out', 'err', 'structured'], 'all');\n return;\n }\n\n // Exports (uppercase) — enabled only when exportMgr is present\n if (s === 'E') {\n exportCombined('error', 'error');\n return;\n }\n if (s === 'W') {\n exportCombined('warn', 'warn');\n return;\n }\n if (s === 'I') {\n exportCombined('info', 'info');\n return;\n }\n if (s === 'A') {\n exportCombined('all', 'ALL');\n return;\n }\n\n // Command-specific bindings\n const fn = custom?.[s];\n if (fn) {\n fn({ noteExport, say });\n return;\n }\n\n // Exit a viewer (Esc / Ctrl+]) — resume dashboard\n if (s === '\\x1b' || s === '\\x1d') {\n viewing = false;\n setPaused(false);\n repaint();\n }\n };\n}\n","import * as readline from 'node:readline';\n\nimport type { ObjByString } from '@transcend-io/type-utils';\nimport type { RunPoolOptions } from '@transcend-io/utils';\n\nimport type { LocalContext } from '../../context.js';\nimport { createExtraKeyHandler } from './createExtraKeyHandler.js';\nimport { dashboardPlugin, type DashboardPlugin } from './dashboardPlugin.js';\nimport { installInteractiveSwitcher } from './installInteractiveSwitcher.js';\n\ntype PoolingCommandUi<TTotals extends ObjByString, TSlotState extends ObjByString> = Pick<\n RunPoolOptions<ObjByString, TSlotState, ObjByString, TTotals>,\n 'render' | 'installInteractiveSwitcher' | 'extraKeyHandler'\n>;\n\n/**\n * Bind the shared pooling UI to one command context.\n *\n * @param context - Command-owned streams.\n * @param plugin - Command-specific dashboard renderer.\n * @param viewerMode - Whether interactive worker attachment is disabled.\n * @returns Pool runner callbacks using the command context.\n */\nexport function createPoolingCommandUi<TTotals extends ObjByString, TSlotState extends ObjByString>(\n context: Pick<LocalContext, 'process'>,\n plugin: DashboardPlugin<TTotals, TSlotState>,\n viewerMode: boolean,\n): PoolingCommandUi<TTotals, TSlotState> {\n const { process } = context;\n\n return {\n render: (input) =>\n dashboardPlugin(input, plugin, viewerMode, {\n stdout: process.stdout,\n cursorTo: readline.cursorTo,\n clearScreenDown: readline.clearScreenDown,\n }),\n installInteractiveSwitcher: viewerMode\n ? undefined\n : ({ workers, onCtrlC, getLogPaths, replayBytes, replayWhich, setPaused, repaint }) =>\n installInteractiveSwitcher({\n workers,\n onCtrlC,\n getLogPaths,\n replayBytes,\n replayWhich,\n ports: {\n stdin: process.stdin,\n stdout: process.stdout,\n stderr: process.stderr,\n emitKeypressEvents: readline.emitKeypressEvents,\n },\n onAttach: () => setPaused(true),\n onDetach: () => {\n setPaused(false);\n repaint();\n },\n onEnterAttachScreen: (id) => {\n setPaused(true);\n process.stdout.write('\\x1b[2J\\x1b[H');\n process.stdout.write(\n `Attached to worker ${id}. (Esc/Ctrl+] detach • Ctrl+D EOF • Ctrl+C SIGINT)\\n`,\n );\n },\n }),\n extraKeyHandler: ({ logsBySlot, repaint, setPaused }) =>\n createExtraKeyHandler({\n logsBySlot,\n repaint,\n setPaused,\n stdout: process.stdout,\n }),\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"createPoolingCommandUi-BZr2HSiZ.mjs","names":["defaultPorts"],"sources":["../src/lib/pooling/showCombinedLogs.ts","../src/lib/pooling/keymap.ts","../src/lib/pooling/replayFileTailToStdout.ts","../src/lib/pooling/workerIds.ts","../src/lib/pooling/installInteractiveSwitcher.ts","../src/lib/pooling/dashboardPlugin.ts","../src/lib/pooling/uiPlugins.ts","../src/lib/pooling/createExtraKeyHandler.ts","../src/lib/pooling/createPoolingCommandUi.ts"],"sourcesContent":["/* eslint-disable no-continue, no-control-regex */\nimport { readFileSync } from 'node:fs';\n\nimport type { WorkerLogPaths } from '@transcend-io/utils';\n\n/**\n * Log locations\n */\nexport type LogLocation = 'out' | 'err' | 'structured' | 'warn' | 'info';\n\n/**\n * Which logs to show in the combined output.\n * Can include 'out' (stdout), 'err' (stderr), 'structured' (\n */\nexport type WhichLogs = Array<LogLocation>;\n\n/**\n * External operations used while rendering combined logs.\n */\nexport interface ShowCombinedLogsPorts {\n /** Read a UTF-8 log file from disk. */\n readFile: (path: string) => string;\n /** Write rendered output to the terminal. */\n stdout: Pick<NodeJS.WriteStream, 'write'>;\n}\n\nconst defaultPorts: ShowCombinedLogsPorts = {\n readFile: (path) => readFileSync(path, 'utf8'),\n stdout: process.stdout,\n};\n\n/**\n * Show combined logs from all worker processes.\n *\n * @param slotLogPaths - Map of worker IDs to their log file paths.\n * @param whichList - one or more sources to include (e.g., ['err','out'])\n * @param filterLevel - 'error', 'warn', or 'all' to filter log levels.\n * @param ports - Optional file and terminal operations.\n */\nexport function showCombinedLogs(\n slotLogPaths: Map<number, WorkerLogPaths | undefined>,\n whichList: WhichLogs,\n filterLevel: 'error' | 'warn' | 'all',\n ports: Partial<ShowCombinedLogsPorts> = {},\n): void {\n const readFile = ports.readFile ?? defaultPorts.readFile;\n const stdout = ports.stdout ?? defaultPorts.stdout;\n\n stdout.write('\\x1b[2J\\x1b[H');\n\n const isError = (t: string): boolean =>\n /\\b(ERROR|uncaughtException|unhandledRejection)\\b/i.test(t);\n const isWarnTag = (t: string): boolean => /\\b(WARN|WARNING)\\b/i.test(t);\n\n const lines: string[] = [];\n\n for (const [, paths] of slotLogPaths) {\n if (!paths) continue;\n\n const files: Array<{\n /** Absolute file path to read from */\n path: string;\n /** Source type for this file, used for classification */\n src: LogLocation;\n }> = [];\n for (const which of whichList) {\n if (which === 'out' && paths.outPath) {\n files.push({ path: paths.outPath, src: 'out' });\n }\n if (which === 'err' && paths.errPath) {\n files.push({ path: paths.errPath, src: 'err' });\n }\n if (which === 'structured' && paths.structuredPath) {\n files.push({ path: paths.structuredPath, src: 'structured' });\n }\n if (paths.warnPath && which === 'warn') {\n files.push({ path: paths.warnPath, src: 'warn' });\n }\n if (paths.infoPath && which === 'info') {\n files.push({ path: paths.infoPath, src: 'info' });\n }\n }\n\n for (const { path, src } of files) {\n let text = '';\n try {\n text = readFile(path);\n } catch {\n continue;\n }\n\n for (const ln of text.split('\\n')) {\n if (!ln) continue;\n\n const clean = ln.replace(/\\x1B\\[[0-9;]*m/g, '');\n\n if (filterLevel === 'all') {\n lines.push(ln);\n continue;\n }\n\n if (filterLevel === 'error') {\n if (isError(clean)) lines.push(ln);\n continue;\n }\n\n // filterLevel === 'warn'\n // Accept:\n // - explicit WARN tag anywhere\n // - OR lines from stderr that are NOT explicit errors (many warn libs print to stderr)\n // - OR lines containing the word \"warning\" (common in some libs)\n if (isWarnTag(clean) || (src === 'err' && !isError(clean))) {\n lines.push(ln);\n continue;\n }\n }\n }\n }\n\n // simple time-sort; each worker often prefixes ISO timestamps\n lines.sort((a, b) => {\n const ta = a.match(/\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}/)?.[0] ?? '';\n const tb = b.match(/\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}/)?.[0] ?? '';\n return ta.localeCompare(tb);\n });\n\n stdout.write(`${lines.join('\\n')}\\n`);\n stdout.write('\\nPress Esc/Ctrl+] to return to dashboard.\\n');\n}\n/* eslint-enable no-continue, no-control-regex */\n","import type * as readline from 'node:readline';\n\n/**\n * Map a key press to an action in the interactive dashboard.\n */\nexport type Action =\n | {\n /** Indicates attaching to a session by id. */\n type: 'ATTACH';\n /** The id of the session to attach to. */\n id: number;\n }\n | {\n /** Indicates cycling through sessions. */\n type: 'CYCLE';\n /** The direction to cycle: +1 for next, -1 for previous. */\n delta: number;\n }\n | {\n /** Indicates detaching from the current session. */\n type: 'DETACH';\n }\n | {\n /** Indicates the Ctrl+C key combination was pressed. */\n type: 'CTRL_C';\n }\n | {\n /** Indicates the Ctrl+D key combination was pressed. */\n type: 'CTRL_D';\n }\n | {\n /** Indicates quitting the dashboard. */\n type: 'QUIT';\n }\n | {\n /** Forwards an unhandled key sequence. */\n type: 'FORWARD';\n /** The key sequence to forward. */\n sequence: string;\n };\n\n/**\n * Map a key press to an action in the interactive dashboard.\n *\n * @param str - The string representation of the key press.\n * @param key - The key object containing details about the key press.\n * @param mode - The current mode of the dashboard, either 'dashboard' or 'attached'.\n * @returns An Action object representing the mapped action, or null if no action is mapped.\n */\nexport function keymap(\n str: string,\n key: readline.Key,\n mode: 'dashboard' | 'attached',\n): Action | null {\n if (key.ctrl && key.name === 'c') return { type: 'CTRL_C' };\n\n if (mode === 'dashboard') {\n if (key.name && /^[0-9]$/.test(key.name)) {\n return { type: 'ATTACH', id: Number(key.name) };\n }\n if (key.name === 'tab' && !key.shift) return { type: 'CYCLE', delta: +1 };\n if (key.name === 'tab' && key.shift) return { type: 'CYCLE', delta: -1 };\n if (key.name === 'q') return { type: 'QUIT' };\n return null;\n }\n\n // attached\n if (key.name === 'escape' || (key.ctrl && key.name === ']')) {\n return { type: 'DETACH' };\n }\n if (key.ctrl && key.name === 'd') return { type: 'CTRL_D' };\n\n const sequence = key.sequence ?? str ?? '';\n return sequence ? { type: 'FORWARD', sequence } : null;\n}\n","import { createReadStream, statSync } from 'node:fs';\n\n/** File metadata needed when replaying a tail. */\nexport interface ReplayFileStat {\n /** File size in bytes */\n readonly size: number;\n}\n\n/** Dependencies used to replay a file tail. */\nexport interface ReplayFileTailToStdoutDependencies {\n /** Create a stream for the requested file range. */\n createReadStream: typeof createReadStream;\n /** Read file metadata synchronously. */\n statSync: (path: string) => ReplayFileStat;\n}\n\nconst defaultDependencies: ReplayFileTailToStdoutDependencies = {\n createReadStream,\n statSync,\n};\n\n/**\n * Replay the tail of a file to stdout.\n *\n * @param path - The absolute path to the file to read.\n * @param maxBytes - The maximum number of bytes to read from the end of the file.\n * @param write - A function to write the output to stdout.\n * @param dependencies - File-system operations used by the replay.\n */\nexport async function replayFileTailToStdout(\n path: string,\n maxBytes: number,\n write: (s: string) => void,\n dependencies: ReplayFileTailToStdoutDependencies = defaultDependencies,\n): Promise<void> {\n await new Promise<void>((resolve) => {\n try {\n const st = dependencies.statSync(path);\n const start = Math.max(0, st.size - maxBytes);\n const stream = dependencies.createReadStream(path, { start, encoding: 'utf8' });\n stream.on('data', (chunk) => write(chunk as string));\n stream.on('end', () => resolve());\n stream.on('error', () => resolve());\n } catch {\n resolve();\n }\n });\n}\n","import type { ChildProcess } from 'node:child_process';\n\n/**\n * Get the sorted list of worker IDs from a map of ChildProcess instances.\n *\n * @param m - Map of worker IDs to ChildProcess instances.\n * @returns Sorted array of worker IDs.\n */\nexport function getWorkerIds(m: Map<number, ChildProcess>): number[] {\n return [...m.keys()].sort((a, b) => a - b);\n}\n\n/**\n * Cycles through an array of numeric IDs, returning the next ID based on a delta.\n *\n * If the `current` ID is not provided or not found in the array, the first ID is used as the starting point.\n * The function then moves forward or backward in the array by `delta` positions, wrapping around if necessary.\n *\n * @param ids - Array of numeric IDs to cycle through.\n * @param current - The current ID to start cycling from. If `null` or not found, starts from the first ID.\n * @param delta - The number of positions to move forward (positive) or backward (negative) in the array.\n * @returns The next ID in the array after cycling, or `null` if the array is empty.\n */\nexport function cycleWorkers(ids: number[], current: number | null, delta: number): number | null {\n if (!ids.length) return null;\n const cur = current == null ? ids[0] : current;\n let i = ids.indexOf(cur);\n if (i === -1) i = 0;\n i = (i + delta + ids.length) % ids.length;\n return ids[i]!;\n}\n","import type { ChildProcess } from 'node:child_process';\nimport * as readline from 'node:readline';\n\nimport type { WorkerLogPaths } from '@transcend-io/utils';\n\nimport { DEBUG } from '../../constants.js';\nimport { keymap } from './keymap.js';\nimport { replayFileTailToStdout } from './replayFileTailToStdout.js';\nimport type { WhichLogs } from './showCombinedLogs.js';\nimport { cycleWorkers, getWorkerIds } from './workerIds.js';\n\n/**\n * Key action types for the interactive switcher\n */\nexport type InteractiveDashboardMode = 'dashboard' | 'attached';\n\nexport interface SwitcherPorts {\n /** Standard input stream */\n stdin: NodeJS.ReadStream;\n /** Standard output stream */\n stdout: NodeJS.WriteStream;\n /** Standard error stream */\n stderr: NodeJS.WriteStream;\n /** Enable keypress events on the input stream */\n emitKeypressEvents?: typeof readline.emitKeypressEvents;\n}\n\n/**\n * Install an interactive switcher for managing worker processes.\n *\n * @param opts - Options for the switcher\n * @returns A cleanup function to remove the switcher\n */\nexport function installInteractiveSwitcher(opts: {\n /** Registry of live workers by id */\n workers: Map<number, ChildProcess>;\n /** Hooks */\n onAttach?: (id: number) => void;\n /** Optional detach handler */\n onDetach?: () => void;\n /** Optional Ctrl+C handler for parent graceful shutdown in dashboard */\n onCtrlC?: () => void; // parent graceful shutdown in dashboard\n /** Provide log paths so we can replay the tail on attach */\n getLogPaths?: (id: number) => WorkerLogPaths | undefined;\n /** How many bytes to replay from the end of each file (default 200 KB) */\n replayBytes?: number;\n /** Which logs to replay first (default ['out','err']) */\n replayWhich?: WhichLogs;\n /** Print a small banner/clear screen before replaying (optional) */\n onEnterAttachScreen?: (id: number) => void;\n /** Optional stdio ports for testing; defaults to process stdio */\n ports?: SwitcherPorts;\n}): () => void {\n const {\n workers,\n onAttach,\n onDetach,\n onCtrlC,\n getLogPaths,\n replayBytes = 200 * 1024,\n replayWhich = ['out', 'err'],\n onEnterAttachScreen,\n ports,\n } = opts;\n\n const stdin = ports?.stdin ?? process.stdin;\n const stdout = ports?.stdout ?? process.stdout;\n const stderr = ports?.stderr ?? process.stderr;\n\n const d = (...a: unknown[]): void => {\n if (DEBUG) {\n try {\n (ports?.stderr ?? process.stderr).write(`[keys] ${a.map(String).join(' ')}\\n`);\n } catch {\n // noop\n }\n }\n };\n\n if (!stdin.isTTY) {\n // Not a TTY; return a no-op cleanup\n return () => {\n // noop\n };\n }\n\n (ports?.emitKeypressEvents ?? readline.emitKeypressEvents)(stdin);\n stdin.setRawMode?.(true);\n\n let mode: InteractiveDashboardMode = 'dashboard';\n let focus: number | null = null;\n\n // live mirroring handlers while attached\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let outHandler: ((chunk: any) => void) | null = null;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let errHandler: ((chunk: any) => void) | null = null;\n\n /**\n * Cycle through worker IDs, wrapping around.\n *\n * @param id - The current worker ID to start cycling from.\n * @returns The next worker ID after cycling, or null if no workers are available.\n */\n async function replayLogs(id: number): Promise<void> {\n if (!getLogPaths) return;\n const paths = getLogPaths(id);\n if (!paths) return;\n\n const toReplay: string[] = [];\n for (const which of replayWhich) {\n if (which === 'out') toReplay.push(paths.outPath);\n if (which === 'err') toReplay.push(paths.errPath);\n if (which === 'structured') toReplay.push(paths.structuredPath);\n }\n\n if (toReplay.length) {\n stdout.write('\\n------------ replay ------------\\n');\n for (const p of toReplay) {\n stdout.write(`\\n--- ${p} (last ~${Math.floor(replayBytes / 1024)}KB) ---\\n`);\n await replayFileTailToStdout(p, replayBytes, (s) => stdout.write(s));\n }\n stdout.write('\\n--------------------------------\\n\\n');\n }\n }\n\n const attach = async (id: number): Promise<void> => {\n d('attach()', `id=${id}`); // at function entry\n\n const w = workers.get(id);\n if (!w) return;\n\n // Detach any previous focus\n if (mode === 'attached') detach();\n\n mode = 'attached';\n focus = id;\n\n // UX: clear + banner\n onEnterAttachScreen?.(id);\n\n onAttach?.(id); // prints “Attached to worker …” and clears\n await replayLogs(id); // now the tail stays visible\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n outHandler = (chunk: any) => stdout.write(chunk);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n errHandler = (chunk: any) => stderr.write(chunk);\n w.stdout?.on('data', outHandler);\n w.stderr?.on('data', errHandler);\n\n // auto-detach if child exits\n const onExit = (): void => {\n if (focus === id) detach();\n };\n w.once('exit', onExit);\n };\n\n const detach = (): void => {\n d('detach()', `id=${focus}`); // at function entry\n\n if (focus == null) return;\n const id = focus;\n const w = workers.get(id);\n if (w) {\n if (outHandler) w.stdout?.off('data', outHandler);\n if (errHandler) w.stderr?.off('data', errHandler);\n }\n outHandler = null;\n errHandler = null;\n focus = null;\n mode = 'dashboard';\n onDetach?.();\n };\n\n const onKey = (str: string, key: readline.Key): void => {\n d(\n 'keypress',\n JSON.stringify({\n str,\n name: key.name,\n seq: key.sequence,\n ctrl: key.ctrl,\n meta: key.meta,\n shift: key.shift,\n mode,\n }),\n );\n const act = keymap(str, key, mode);\n d('mapped', JSON.stringify(act));\n\n if (!act) return;\n\n // eslint-disable-next-line default-case\n switch (act.type) {\n case 'CTRL_C': {\n d('CTRL_C');\n if (mode === 'attached' && focus != null) {\n const w = workers.get(focus);\n try {\n w?.kill('SIGINT');\n } catch {\n // noop\n }\n // optional: auto-detach so second Ctrl+C exits parent\n detach();\n return;\n }\n onCtrlC?.();\n return;\n }\n\n case 'ATTACH': {\n d('ATTACH', `id=${act.id}`, `has=${workers.has(act.id)}`);\n\n if (mode !== 'dashboard') return;\n // eslint-disable-next-line no-void\n if (workers.has(act.id)) void attach(act.id);\n return;\n }\n\n case 'CYCLE': {\n d('CYCLE', `delta=${act.delta}`);\n if (mode !== 'dashboard') return;\n const next = cycleWorkers(getWorkerIds(workers), focus, act.delta);\n // eslint-disable-next-line no-void\n if (next != null) void attach(next);\n return;\n }\n\n case 'QUIT': {\n if (mode !== 'dashboard') return;\n onCtrlC?.();\n return;\n }\n\n case 'DETACH': {\n d('DETACH');\n if (mode === 'attached') detach();\n return;\n }\n\n case 'CTRL_D': {\n if (mode === 'attached' && focus != null) {\n const w = workers.get(focus);\n try {\n w?.stdin?.end();\n } catch {\n // noop\n }\n }\n return;\n }\n\n case 'FORWARD': {\n if (mode === 'attached' && focus != null) {\n const w = workers.get(focus);\n try {\n w?.stdin?.write(act.sequence);\n } catch {\n // noop\n }\n }\n }\n }\n };\n\n // Raw bytes fallback (usually not hit because keypress handles it)\n const onData = (chunk: Buffer): void => {\n if (mode === 'attached' && focus != null) {\n const w = workers.get(focus);\n try {\n w?.stdin?.write(chunk);\n } catch {\n // noop\n }\n }\n };\n\n const cleanup = (): void => {\n stdin.off('keypress', onKey);\n stdin.off('data', onData);\n stdin.setRawMode?.(false);\n stdout.write('\\x1b[?25h');\n };\n\n stdin.on('keypress', onKey);\n stdin.on('data', onData);\n\n return cleanup;\n}\n","// lib/pooling/dashboardPlugin.ts\nimport * as readline from 'node:readline';\n\nimport type { ObjByString } from '@transcend-io/type-utils';\nimport type { SlotState } from '@transcend-io/utils';\nimport colors from 'colors';\n\n/**\n * A dashboard plugin defines how to render the worker pool UI.\n * Commands can supply a plugin to customize:\n * - The header block (summary stats, title, etc.)\n * - Per-worker rows (one line per worker slot)\n * - Optional extras (artifact exports, breakdowns, footers)\n *\n * @template TTotals - The shape of the aggregate totals object maintained by the command.\n */\nexport interface DashboardPlugin<TTotals, TSlotState extends ObjByString> {\n /**\n * Render the header block of the dashboard.\n *\n * @param ctx - Context with pool/worker state, totals, and metadata.\n * @returns An array of strings, each representing one line in the header.\n */\n renderHeader: (ctx: CommonCtx<TTotals, TSlotState>) => string[];\n\n /**\n * Render per-worker rows, usually one line per worker slot.\n *\n * @param ctx - Context with pool/worker state, totals, and metadata.\n * @returns An array of strings, each representing one row in the workers section.\n */\n renderWorkers: (ctx: CommonCtx<TTotals, TSlotState>) => string[];\n\n /**\n * Render any optional extra blocks that appear after the worker rows.\n * Useful for printing export paths, aggregated metrics, breakdowns, etc.\n *\n * @param ctx - Context with pool/worker state, totals, and metadata.\n * @returns An array of strings, each representing one additional line.\n */\n renderExtras?: (ctx: CommonCtx<TTotals, TSlotState>) => string[];\n}\n\n/**\n * Shared context object passed into all render methods of a {@link DashboardPlugin}.\n *\n * @template TTotals - The shape of the aggregate totals object maintained by the command.\n */\nexport type CommonCtx<TTotals, TSlotState extends ObjByString> = {\n /** Human-readable title for the dashboard (e.g., \"Parallel uploader\"). */\n title: string;\n\n /** Number of worker processes spawned in the pool. */\n poolSize: number;\n\n /** Logical CPU count, included for informational display. */\n cpuCount: number;\n\n /** Total number of \"files\" or logical units the command expects to process. */\n filesTotal: number;\n\n /** Count of successfully completed files/tasks. */\n filesCompleted: number;\n\n /** Count of failed files/tasks. */\n filesFailed: number;\n\n /**\n * State of each worker slot, keyed by worker id.\n * Includes busy flag, file label, start time, last log badge, and progress.\n */\n workerState: Map<number, SlotState<TSlotState>>;\n\n /**\n * Aggregate totals maintained by the command’s hook logic.\n * Domain-specific metrics (e.g., rows uploaded, bytes processed) can be surfaced here.\n */\n totals: TTotals;\n\n /**\n * Throughput metrics tracked by the runner:\n * - successSoFar: convenience alias for completed count\n * - r10s: completions/sec averaged over last 10s\n * - r60s: completions/sec averaged over last 60s\n */\n throughput: {\n /** Cumulative count of successful completions so far. */\n successSoFar: number;\n /** Recent file-level throughput rate over the last 10 seconds. */\n r10s: number;\n /** Recent file-level throughput rate over the last 60 seconds. */\n r60s: number;\n /** Recent job/record-level throughput rate over the last 10 seconds. */\n jobsR10s: number;\n /** Recent job/record-level throughput rate over the last 60 seconds. */\n jobsR60s: number;\n };\n\n /** True when the pool has fully drained and all workers have exited. */\n final: boolean;\n\n /**\n * Optional export status payload provided by the command.\n * Useful for rendering artifact paths or \"latest export\" summaries.\n */\n exportStatus?: Record<string, unknown>;\n};\n\n/**\n * Terminal operations used to paint the dashboard.\n */\nexport interface DashboardPorts {\n /** Standard output stream used for dashboard rendering. */\n stdout: NodeJS.WriteStream;\n /** Move the output cursor before repainting. */\n cursorTo: typeof readline.cursorTo;\n /** Clear terminal content below the cursor. */\n clearScreenDown: typeof readline.clearScreenDown;\n}\n\nconst defaultPorts: DashboardPorts = {\n stdout: process.stdout,\n cursorTo: readline.cursorTo,\n clearScreenDown: readline.clearScreenDown,\n};\n\n/** Most recently rendered frame per output stream, cached to suppress duplicate renders. */\nconst lastFrames = new WeakMap<NodeJS.WriteStream, string>();\n\n/**\n * Generate the hotkeys hint string that appears at the bottom of the dashboard.\n *\n * @param poolSize - The number of worker slots in the pool.\n * @param final - Whether the run has completed.\n * @returns A dimmed string listing the supported hotkeys for attach/detach/quit.\n */\nexport const hotkeysHint = (poolSize: number, final: boolean): string => {\n const maxDigit = Math.min(poolSize - 1, 9);\n const digitRange = poolSize <= 1 ? '0' : `0-${maxDigit}`;\n const extra = poolSize > 10 ? ' (Tab/Shift+Tab for ≥10)' : '';\n return final\n ? colors.dim(\n 'Run complete — digits to view logs • Tab/Shift+Tab cycle • Esc/Ctrl+] detach • q to quit',\n )\n : colors.dim(\n `Hotkeys: [${digitRange}] attach${extra} • e=errors • w=warnings • i=info • l=logs • ` +\n 'Tab/Shift+Tab • Esc/Ctrl+] detach • Ctrl+C exit',\n );\n};\n\n/**\n * Render the dashboard using a supplied {@link DashboardPlugin}.\n *\n * The frame is composed of:\n * - Header lines\n * - A blank separator\n * - Worker rows\n * - A blank separator\n * - Hotkeys hint\n * - Optional extras (if plugin supplies them)\n *\n * Optimizations:\n * - Suppresses re-renders if the frame is identical to the previous frame (flicker-free).\n * - Hides the terminal cursor during live updates, restoring it when final.\n *\n * @param ctx - Shared context containing pool state, worker state, totals, throughput, etc.\n * @param plugin - The plugin that defines how to render the header, workers, and optional extras.\n * @param viewerMode - If true, renders in viewer mode (no ability to switch between files).\n * @param ports - Optional terminal operations used to paint the dashboard.\n */\nexport function dashboardPlugin<TTotals, TSlotState extends ObjByString>(\n ctx: CommonCtx<TTotals, TSlotState>,\n plugin: DashboardPlugin<TTotals, TSlotState>,\n viewerMode = false,\n ports: DashboardPorts = defaultPorts,\n): void {\n const frame = [\n ...plugin.renderHeader(ctx),\n '',\n ...plugin.renderWorkers(ctx),\n ...(viewerMode ? [] : ['', hotkeysHint(ctx.poolSize, ctx.final)]),\n ...(plugin.renderExtras ? [''].concat(plugin.renderExtras(ctx)) : []),\n ].join('\\n');\n\n // Skip duplicate renders during live runs to avoid flicker.\n if (!ctx.final && frame === lastFrames.get(ports.stdout)) return;\n lastFrames.set(ports.stdout, frame);\n\n if (!ctx.final) {\n // Hide cursor and repaint in place\n ports.stdout.write('\\x1b[?25l');\n ports.cursorTo(ports.stdout, 0, 0);\n ports.clearScreenDown(ports.stdout);\n } else {\n // Restore cursor on final render\n ports.stdout.write('\\x1b[?25h');\n }\n ports.stdout.write(`${frame}\\n`);\n}\n","import { basename } from 'node:path';\n\nimport type { ObjByString } from '@transcend-io/type-utils';\nimport colors from 'colors';\n\nimport type { CommonCtx } from './dashboardPlugin.js';\n\n/**\n * Progress snapshot for a worker slot in the chunk-csv command.\n */\nexport type ChunkSlotProgress = {\n /** Absolute path of the file being processed by this worker. */\n filePath?: string;\n /** Number of rows processed so far in this file. */\n processed?: number;\n /** Optional total number of rows in the file (if known). */\n total?: number;\n};\n\n/**\n * Format a number safely for display.\n *\n * @param n - The number to format (or `undefined`).\n * @returns A localized string representation, or \"0\".\n */\nexport function fmtNum(n: number | undefined): string {\n return typeof n === 'number' ? n.toLocaleString() : '0';\n}\n\n/**\n * Draw a horizontal bar of length `width` filled to `pct` percent.\n *\n * @param pct - Percentage 0..100.\n * @param width - Number of characters in the bar.\n * @returns A string like \"████░░░░\".\n */\nexport function pctBar(pct: number, width = 40): string {\n const clamped = Math.max(0, Math.min(100, Math.floor(pct)));\n const filled = Math.floor((clamped / 100) * width);\n return '█'.repeat(filled) + '░'.repeat(width - filled);\n}\n\n/**\n * Compute pool-wide progress values needed by headers.\n *\n * @param ctx - Dashboard context containing pool state, worker state, totals, etc.\n * @returns An object with `done`, `inProgress`, and `pct` properties.\n */\nexport function poolProgress<TTotals, TSlot extends ObjByString>(\n ctx: CommonCtx<TTotals, TSlot>,\n): {\n /** Count of successfully completed files/tasks. */\n done: number;\n /** Count of currently in-progress files/tasks. */\n inProgress: number;\n /** Percentage of completion (0-100). */\n pct: number;\n} {\n const inProgress = [...ctx.workerState.values()].filter((s) => s.busy).length;\n const done = ctx.filesCompleted + ctx.filesFailed;\n const pct = ctx.filesTotal === 0 ? 100 : Math.floor((done / Math.max(1, ctx.filesTotal)) * 100);\n return { done, inProgress, pct };\n}\n\n/**\n * Compose the common header lines (title, pool stats, progress bar, throughput).\n *\n * @param ctx - Dashboard context.\n * @param extraLines - Optional extra lines (e.g., totals block).\n * @returns Header lines.\n */\nexport function makeHeader<TTotals, TSlot extends ObjByString>(\n ctx: CommonCtx<TTotals, TSlot>,\n extraLines: string[] = [],\n): string[] {\n const { title, poolSize, cpuCount, filesTotal, filesCompleted, filesFailed, throughput } = ctx;\n const { inProgress, pct } = poolProgress(ctx);\n\n const lines: string[] = [\n `${colors.bold(title)} — ${poolSize} workers ${colors.dim(`(CPU avail: ${cpuCount})`)}`,\n `${colors.dim('Files')} ${fmtNum(filesTotal)} ${colors.dim(\n 'Completed',\n )} ${fmtNum(filesCompleted)} ${colors.dim('Failed')} ${\n filesFailed ? colors.red(fmtNum(filesFailed)) : fmtNum(filesFailed)\n } ${colors.dim('In-flight')} ${fmtNum(inProgress)}`,\n `[${pctBar(pct)}] ${pct}%`,\n ];\n\n if (throughput) {\n const jobsActive = throughput.jobsR10s > 0 || throughput.jobsR60s > 0;\n const perHour10 = Math.round(\n (jobsActive ? throughput.jobsR10s : throughput.r10s) * 3600,\n ).toLocaleString();\n const perHour60 = Math.round(\n (jobsActive ? throughput.jobsR60s : throughput.r60s) * 3600,\n ).toLocaleString();\n const unit = jobsActive ? 'rec' : 'files';\n const suffix =\n ctx.throughput?.successSoFar != null\n ? ` Newly uploaded: ${fmtNum(ctx.throughput.successSoFar)}`\n : '';\n lines.push(\n colors.cyan(`Throughput: ${perHour10} ${unit}/hr (1h: ${perHour60} ${unit}/hr)${suffix}`),\n );\n }\n\n return extraLines.length ? lines.concat(extraLines) : lines;\n}\n\n/**\n * Render per-worker rows with a compact progress bar and status badge.\n *\n * @param ctx - Dashboard context (slot progress type must have processed/total?).\n * @param getFileLabel - Optional: override how the filename is shown.\n * @returns Array of strings, each representing one worker row.\n */\nexport function makeWorkerRows<TTotals, TSlot extends Omit<ChunkSlotProgress, 'filePath'>>(\n ctx: CommonCtx<TTotals, TSlot>,\n getFileLabel: (file: string | null | undefined) => string = (file) =>\n file ? basename(file) : '-',\n): string[] {\n const miniWidth = 18;\n\n return [...ctx.workerState.entries()].map(([id, s]) => {\n const badge =\n s.lastLevel === 'error'\n ? colors.red('ERROR ')\n : s.lastLevel === 'warn'\n ? colors.yellow('WARN ')\n : s.busy\n ? colors.green('WORKING')\n : colors.dim('IDLE ');\n\n const fname = getFileLabel(s.file);\n const elapsed = s.startedAt ? `${Math.floor((Date.now() - s.startedAt) / 1000)}s` : '-';\n\n const processed = s.progress?.processed ?? 0;\n const total = s.progress?.total ?? 0;\n const pctw = total > 0 ? Math.floor((processed / total) * 100) : 0;\n const mini = total > 0 ? pctBar(pctw, miniWidth) : ' '.repeat(miniWidth);\n const miniTxt =\n total > 0\n ? `${processed.toLocaleString()}/${total.toLocaleString()} (${pctw}%)`\n : colors.dim('—');\n\n return ` [w${id}] ${badge} | ${fname} | ${elapsed} | [${mini}] ${miniTxt}`;\n });\n}\n","import type { ExportStatusMap, SlotPaths } from '@transcend-io/utils';\n\nimport {\n showCombinedLogs,\n type LogLocation,\n type ShowCombinedLogsPorts,\n} from './showCombinedLogs.js';\n\n/** Severity filter applied by the viewer. */\ntype ViewLevel = 'error' | 'warn' | 'all';\n\n/**\n * Options for {@link createExtraKeyHandler}.\n */\nexport type CreateExtraKeyHandlerOpts = {\n /**\n * Per-slot log file paths maintained by the runner; used to stream or export logs.\n */\n logsBySlot: SlotPaths;\n\n /**\n * Request an immediate dashboard repaint (e.g., after updating export status).\n */\n repaint: () => void;\n\n /**\n * Pause/unpause dashboard repainting. The handler pauses while a viewer is open\n * to prevent the dashboard from overwriting the viewer output, then resumes on exit.\n */\n setPaused: (p: boolean) => void;\n\n /**\n * Optional export manager to enable uppercase export keys:\n * - `E` (errors) • `W` (warnings) • `I` (info) • `A` (all)\n *\n * Provide this only if your command supports writing combined log files.\n */\n exportMgr?: {\n /** Destination directory for exported artifacts. */\n exportsDir: string;\n /**\n * Write a combined log file for the selected severity and return the absolute path.\n *\n * @param logs - Log paths to combine.\n * @param which - Severity selection.\n * @returns Absolute path to the written file.\n */\n exportCombinedLogs: (logs: SlotPaths, which: 'error' | 'warn' | 'info' | 'all') => string;\n };\n\n /**\n * Optional “Exports” status map. If provided, the handler updates timestamps\n * when exports are written so your dashboard panel can reflect “last saved” times.\n */\n exportStatus?: ExportStatusMap;\n\n /** Standard output used for viewer and export messages. */\n stdout?: ShowCombinedLogsPorts['stdout'];\n\n /** Return the current Unix timestamp in milliseconds. */\n now?: () => number;\n\n /**\n * Optional custom key bindings for command-specific actions.\n * Each handler receives helpers to print messages and to update the exports panel.\n *\n * Example:\n * ```ts\n * custom: {\n * F: async ({ say, noteExport }) => {\n * const p = await writeFailingUpdatesCsv(...);\n * say(`Wrote failing updates to: ${p}`);\n * noteExport('failuresCsv', p);\n * }\n * }\n * ```\n */\n custom?: Record<\n string,\n (ctx: {\n /** Update {@link exportStatus} (if present) and repaint the dashboard. */\n noteExport: (slot: keyof ExportStatusMap, absPath: string) => void;\n /** Print a line to stdout, automatically newline-terminated. */\n say: (s: string) => void;\n }) => void | Promise<void>\n >;\n};\n\n/**\n * Create a keypress handler for interactive viewers/exports.\n * Shared handler for \"extra\" keyboard shortcuts used by the interactive dashboard.\n *\n * It wires:\n * - **Viewers (lowercase):** `e` (errors), `w` (warnings), `i` (info), `l` (all)\n * - **Exports (uppercase, optional):** `E` (errors), `W` (warnings), `I` (info), `A` (all)\n * - **Dismiss:** `Esc` or `Ctrl+]` exits a viewer and returns to the dashboard\n * - **Custom keys (optional):** Provide a `custom` map to handle command-specific bindings\n *\n * Usage (inside `runPool({... extraKeyHandler })`):\n * ```ts\n * extraKeyHandler: ({ logsBySlot, repaint, setPaused }) =>\n * createExtraKeyHandler({ logsBySlot, repaint, setPaused })\n * ```\n *\n * If you also want export hotkeys + an \"Exports\" panel:\n * ```ts\n * extraKeyHandler: ({ logsBySlot, repaint, setPaused }) =>\n * createExtraKeyHandler({\n * logsBySlot, repaint, setPaused,\n * exportMgr, // enables E/W/I/A\n * exportStatus, // keeps panel timestamps up to date\n * custom: { // optional, e.g. 'F' to export a CSV\n * F: async ({ say, noteExport }) => { ... }\n * }\n * })\n * ```\n *\n * @param opts - Configuration for viewers, exports, and custom keys.\n * @returns A `(buf: Buffer) => void` handler suitable for `process.stdin.on('data', ...)`.\n */\nexport function createExtraKeyHandler(opts: CreateExtraKeyHandlerOpts): (buf: Buffer) => void {\n const {\n logsBySlot,\n repaint,\n setPaused,\n exportMgr,\n exportStatus,\n stdout = process.stdout,\n now = Date.now,\n custom,\n } = opts;\n\n const say = (s: string): void => {\n stdout.write(`${s}\\n`);\n };\n\n /**\n * Record that an export was written and trigger a repaint so the dashboard’s\n * \"Exports\" panel shows the updated timestamp/path.\n *\n * @param slot - Slot name in {@link ExportStatusMap} (e.g., \"error\", \"warn\", etc.).\n * @param p - Absolute path to the exported file.\n */\n const noteExport = (slot: keyof ExportStatusMap, p: string): void => {\n const savedAt = now();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const cur: any = exportStatus?.[slot] ?? { path: p };\n if (exportStatus) {\n exportStatus[slot] = {\n path: p || cur.path,\n savedAt,\n exported: true,\n };\n repaint();\n }\n };\n\n let viewing = false; // optional guard to prevent stacking viewers\n\n /**\n * Show an inline combined log viewer for the selected sources/level.\n * Pauses dashboard repaint to keep the viewer visible until the user exits.\n *\n * @param sources - Log sources to include (e.g., \"err\", \"warn\", \"info\").\n * @param level - Severity level to filter by (e.g., \"error\", \"warn\", \"all\").\n */\n const view = (sources: LogLocation[], level: ViewLevel): void => {\n if (viewing) return;\n viewing = true;\n setPaused(true);\n\n // optional UX: clear screen and show a hint\n stdout.write('\\x1b[2J\\x1b[H'); // clear+home\n stdout.write('Combined logs viewer (press Esc or Ctrl+] to return)\\n\\n');\n\n (async () => {\n try {\n await showCombinedLogs(logsBySlot, sources, level, { stdout });\n // NOTE: do NOT unpause here; ESC will handle it.\n } catch {\n // If showCombinedLogs throws, recover and unpause\n viewing = false;\n setPaused(false);\n repaint();\n }\n })();\n };\n\n /**\n * Export combined logs (if an export manager was provided).\n *\n * @param which - Severity to export (e.g., \"error\", \"warn\", \"info\", \"all\").\n * @param label - Human-readable label for the export (e.g., \"error\", \"warn\").\n */\n const exportCombined = (which: 'error' | 'warn' | 'info' | 'all', label: string): void => {\n if (!exportMgr) return;\n try {\n const p = exportMgr.exportCombinedLogs(logsBySlot, which);\n say(`\\nWrote combined ${label} logs to: ${p}`);\n noteExport(which as keyof ExportStatusMap, p);\n } catch {\n say(`\\nFailed to write combined ${label} logs`);\n }\n };\n\n // The keypress handler the runner will attach to stdin.\n return (buf: Buffer): void => {\n const s = buf.toString('utf8');\n\n // Viewers (lowercase)\n if (s === 'e') {\n view(['err'], 'error');\n return;\n }\n if (s === 'w') {\n view(['warn', 'err'], 'warn');\n return;\n }\n if (s === 'i') {\n view(['info'], 'all');\n return;\n }\n if (s === 'l') {\n view(['out', 'err', 'structured'], 'all');\n return;\n }\n\n // Exports (uppercase) — enabled only when exportMgr is present\n if (s === 'E') {\n exportCombined('error', 'error');\n return;\n }\n if (s === 'W') {\n exportCombined('warn', 'warn');\n return;\n }\n if (s === 'I') {\n exportCombined('info', 'info');\n return;\n }\n if (s === 'A') {\n exportCombined('all', 'ALL');\n return;\n }\n\n // Command-specific bindings\n const fn = custom?.[s];\n if (fn) {\n fn({ noteExport, say });\n return;\n }\n\n // Exit a viewer (Esc / Ctrl+]) — resume dashboard\n if (s === '\\x1b' || s === '\\x1d') {\n viewing = false;\n setPaused(false);\n repaint();\n }\n };\n}\n","import * as readline from 'node:readline';\n\nimport type { ObjByString } from '@transcend-io/type-utils';\nimport type { RunPoolOptions } from '@transcend-io/utils';\n\nimport type { LocalContext } from '../../context.js';\nimport { createExtraKeyHandler } from './createExtraKeyHandler.js';\nimport { dashboardPlugin, type DashboardPlugin } from './dashboardPlugin.js';\nimport { installInteractiveSwitcher } from './installInteractiveSwitcher.js';\n\ntype PoolingCommandUi<TTotals extends ObjByString, TSlotState extends ObjByString> = Pick<\n RunPoolOptions<ObjByString, TSlotState, ObjByString, TTotals>,\n 'render' | 'installInteractiveSwitcher' | 'extraKeyHandler'\n>;\n\n/**\n * Bind the shared pooling UI to one command context.\n *\n * @param context - Command-owned streams.\n * @param plugin - Command-specific dashboard renderer.\n * @param viewerMode - Whether interactive worker attachment is disabled.\n * @returns Pool runner callbacks using the command context.\n */\nexport function createPoolingCommandUi<TTotals extends ObjByString, TSlotState extends ObjByString>(\n context: Pick<LocalContext, 'process'>,\n plugin: DashboardPlugin<TTotals, TSlotState>,\n viewerMode: boolean,\n): PoolingCommandUi<TTotals, TSlotState> {\n const { process } = context;\n\n return {\n render: (input) =>\n dashboardPlugin(input, plugin, viewerMode, {\n stdout: process.stdout,\n cursorTo: readline.cursorTo,\n clearScreenDown: readline.clearScreenDown,\n }),\n installInteractiveSwitcher: viewerMode\n ? undefined\n : ({ workers, onCtrlC, getLogPaths, replayBytes, replayWhich, setPaused, repaint }) =>\n installInteractiveSwitcher({\n workers,\n onCtrlC,\n getLogPaths,\n replayBytes,\n replayWhich,\n ports: {\n stdin: process.stdin,\n stdout: process.stdout,\n stderr: process.stderr,\n emitKeypressEvents: readline.emitKeypressEvents,\n },\n onAttach: () => setPaused(true),\n onDetach: () => {\n setPaused(false);\n repaint();\n },\n onEnterAttachScreen: (id) => {\n setPaused(true);\n process.stdout.write('\\x1b[2J\\x1b[H');\n process.stdout.write(\n `Attached to worker ${id}. (Esc/Ctrl+] detach • Ctrl+D EOF • Ctrl+C SIGINT)\\n`,\n );\n },\n }),\n extraKeyHandler: ({ logsBySlot, repaint, setPaused }) =>\n createExtraKeyHandler({\n logsBySlot,\n repaint,\n setPaused,\n stdout: process.stdout,\n }),\n };\n}\n"],"mappings":"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"}
|
package/dist/{downloadPrivacyRequestFiles-C9-xSCpt.mjs → downloadPrivacyRequestFiles-CF_QjkR9.mjs}
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as e}from"./constants-
|
|
2
|
-
//# sourceMappingURL=downloadPrivacyRequestFiles-
|
|
1
|
+
import{a as e}from"./constants-I694qbRg.mjs";import{t}from"./logger-CPu6zbyQ.mjs";import{t as n}from"./request-BpKyN9Xm.mjs";import{r}from"./fetchAllRequests-BV9K0wXv.mjs";import{RequestAction as i,RequestStatus as a,TableEncryptionType as o}from"@transcend-io/privacy-types";import{dirname as s,join as c}from"node:path";import l from"colors";import{existsSync as u,mkdirSync as d,writeFileSync as f}from"node:fs";import{map as p}from"@transcend-io/utils";import{decodeCodec as m,valuesOf as h}from"@transcend-io/type-utils";import*as g from"io-ts";import{buildTranscendGraphQLClient as _,createSombraGotInstance as v,makeGraphQLRequest as y}from"@transcend-io/sdk";import b from"cli-progress";const x=g.type({defaultMessage:g.string,id:g.string}),S=g.type({downloadKey:g.string,error:g.union([g.null,g.string]),mimetype:g.string,size:g.string,fileName:g.string,dataPoint:g.type({id:g.string,title:g.union([x,g.null]),description:g.union([x,g.null]),name:g.string,slug:g.string,encryption:g.union([h(o),g.null]),dataSilo:g.type({id:g.string,title:g.string,description:g.string,type:g.string,outerType:g.union([g.string,g.null])}),path:g.array(g.string)})}),C=g.type({nodes:g.array(S),totalCount:g.number,_links:g.partial({next:g.union([g.string,g.null]),previous:g.union([g.string,g.null])})});async function w(e,{sombra:n,concurrency:r=5,limit:i=100}){t.info(l.magenta(`Pulling file metadata for ${e.length} requests`));let a=new Date().getTime(),o=new b.SingleBar({},b.Presets.shades_classic),s=0;o.start(e.length,0);let c=await p(e,async e=>{let t=[],r=!0,a=0;for(;r;){let o;try{o=m(C,await n.get(`v1/data-subject-request/${e.id}/download-keys`,{searchParams:{limit:i,offset:a}}).json()),t.push(...o.nodes),a+=i,r=!!o._links.next&&o.nodes.length===i}catch(e){throw Error(`Received an error from server: ${e?.response?.body||e?.message}`)}}return s+=1,o.update(s),[e,t]},{concurrency:r});o.stop();let u=new Date().getTime()-a;return t.info(l.green(`Successfully downloaded file metadata ${e.length} requests in "${u/1e3}" seconds!`)),c}async function T(e,{requestId:n,sombra:r,onFileDownloaded:i,concurrency:a=20}){await p(e,async e=>{try{await r.get(`v1/files`,{searchParams:{downloadKey:e.downloadKey}}).buffer().then(t=>i(e,t))}catch(r){if(r?.response?.body?.includes(`fileMetadata#verify`)){t.error(l.red(`Failed to pull file for: ${e.fileName} (request:${n}) - JWT expired. This likely means that the file is no longer available. Try restarting the request from scratch in Transcend Admin Dashboard. Skipping the download of this file.`));return}throw Error(`Received an error from server: ${r?.response?.body||r?.message}`)}},{concurrency:a})}async function E({auth:o,folderPath:m,requestIds:h,createdAtBefore:g,sombraAuth:x,createdAtAfter:S,updatedAtBefore:C,updatedAtAfter:E,statuses:D=[a.Approving,a.Downloadable],concurrency:O=5,transcendUrl:k=e,approveAfterDownload:A=!1}){let j=_(k,o),M=await v(k,o,{logger:t,sombraApiKey:x,sombraUrl:process.env.SOMBRA_URL});u(m)||d(m);let N=await r(j,{actions:[i.Access],createdAtBefore:g,createdAtAfter:S,updatedAtBefore:C,updatedAtAfter:E,statuses:D,requestIds:h}),P=await w(N,{sombra:M,concurrency:O}),F=new Date().getTime(),I=new b.SingleBar({},b.Presets.shades_classic),L=0,R=0;I.start(N.length,0),await p(P,async([e,r])=>{let i=c(m,e.id);u(i)||d(i),await T(r,{sombra:M,requestId:e.id,onFileDownloaded:(e,t)=>{let n=c(i,e.fileName),r=s(n);u(r)||d(r,{recursive:!0}),f(n,t)}}),A&&e.status===a.Approving&&(await y(j,n,{variables:{input:{requestId:e.id}},logger:t}),R+=1),L+=1,I.update(L)},{concurrency:O}),I.stop();let z=new Date().getTime()-F;return t.info(l.green(`Successfully downloaded ${L} requests in "${z/1e3}" seconds!`)),R>0&&t.info(l.green(`Approved ${R} requests in Transcend.`)),N.length}export{C as a,S as i,T as n,w as o,x as r,E as t};
|
|
2
|
+
//# sourceMappingURL=downloadPrivacyRequestFiles-CF_QjkR9.mjs.map
|