@transcend-io/cli 12.1.1 → 12.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/dist/{app-BAx2hvcN.mjs → app-CnGAV_Tf.mjs} +16 -16
  2. package/dist/{app-BAx2hvcN.mjs.map → app-CnGAV_Tf.mjs.map} +1 -1
  3. package/dist/{approvePrivacyRequests-B-4o7QMG.mjs → approvePrivacyRequests-fmkUKYAm.mjs} +2 -2
  4. package/dist/{approvePrivacyRequests-B-4o7QMG.mjs.map → approvePrivacyRequests-fmkUKYAm.mjs.map} +1 -1
  5. package/dist/bin/bash-complete.mjs +1 -1
  6. package/dist/bin/cli.mjs +1 -1
  7. package/dist/bin/deprecated-command.mjs +1 -1
  8. package/dist/{buildExamples-kR5_7dkU.mjs → buildExamples-DB_puA14.mjs} +2 -2
  9. package/dist/{buildExamples-kR5_7dkU.mjs.map → buildExamples-DB_puA14.mjs.map} +1 -1
  10. package/dist/{buildXdiSyncEndpoint-BSYwJ2vc.mjs → buildXdiSyncEndpoint-CwABTp_y.mjs} +2 -2
  11. package/dist/{buildXdiSyncEndpoint-BSYwJ2vc.mjs.map → buildXdiSyncEndpoint-CwABTp_y.mjs.map} +1 -1
  12. package/dist/{bulkRestartRequests-DhOfMz0S.mjs → bulkRestartRequests-3OpVNSrk.mjs} +2 -2
  13. package/dist/{bulkRestartRequests-DhOfMz0S.mjs.map → bulkRestartRequests-3OpVNSrk.mjs.map} +1 -1
  14. package/dist/{bulkRetryEnrichers-Cj94ftnY.mjs → bulkRetryEnrichers-CdcXcHsx.mjs} +2 -2
  15. package/dist/{bulkRetryEnrichers-Cj94ftnY.mjs.map → bulkRetryEnrichers-CdcXcHsx.mjs.map} +1 -1
  16. package/dist/{cancelPrivacyRequests-DgAhfcmg.mjs → cancelPrivacyRequests-Dcl2KZfB.mjs} +2 -2
  17. package/dist/{cancelPrivacyRequests-DgAhfcmg.mjs.map → cancelPrivacyRequests-Dcl2KZfB.mjs.map} +1 -1
  18. package/dist/{command-gwG8sQwM.mjs → command-BcxEDvsy.mjs} +2 -2
  19. package/dist/{command-gwG8sQwM.mjs.map → command-BcxEDvsy.mjs.map} +1 -1
  20. package/dist/{constants-BCmVqlrM.mjs → constants-boymUaFC.mjs} +2 -2
  21. package/dist/{constants-BCmVqlrM.mjs.map → constants-boymUaFC.mjs.map} +1 -1
  22. package/dist/{createPoolingCommandUi-ByyTeJx5.mjs → createPoolingCommandUi-D7gyxeTT.mjs} +2 -2
  23. package/dist/{createPoolingCommandUi-ByyTeJx5.mjs.map → createPoolingCommandUi-D7gyxeTT.mjs.map} +1 -1
  24. package/dist/{downloadPrivacyRequestFiles-yiE6NzXz.mjs → downloadPrivacyRequestFiles-930ENWDi.mjs} +2 -2
  25. package/dist/{downloadPrivacyRequestFiles-yiE6NzXz.mjs.map → downloadPrivacyRequestFiles-930ENWDi.mjs.map} +1 -1
  26. package/dist/{generateCrossAccountApiKeys-CoSsZwSz.mjs → generateCrossAccountApiKeys-B_p8gQ2C.mjs} +2 -2
  27. package/dist/{generateCrossAccountApiKeys-CoSsZwSz.mjs.map → generateCrossAccountApiKeys-B_p8gQ2C.mjs.map} +1 -1
  28. package/dist/{impl-DdUe8ClA.mjs → impl-2VeGCbJq.mjs} +2 -2
  29. package/dist/{impl-DdUe8ClA.mjs.map → impl-2VeGCbJq.mjs.map} +1 -1
  30. package/dist/{impl-tYHONQMu.mjs → impl-2YeOxW2m.mjs} +2 -2
  31. package/dist/{impl-tYHONQMu.mjs.map → impl-2YeOxW2m.mjs.map} +1 -1
  32. package/dist/{impl-BCP4X_zk.mjs → impl-5Pko18bX.mjs} +2 -2
  33. package/dist/{impl-BCP4X_zk.mjs.map → impl-5Pko18bX.mjs.map} +1 -1
  34. package/dist/{impl-pA1b-G6B.mjs → impl-7YqcGwra.mjs} +2 -2
  35. package/dist/{impl-pA1b-G6B.mjs.map → impl-7YqcGwra.mjs.map} +1 -1
  36. package/dist/{impl-DSPxdc-T.mjs → impl-B5-UipRP.mjs} +2 -2
  37. package/dist/{impl-DSPxdc-T.mjs.map → impl-B5-UipRP.mjs.map} +1 -1
  38. package/dist/{impl-DE_cRfz52.mjs → impl-B8PyNdje2.mjs} +2 -2
  39. package/dist/{impl-DE_cRfz52.mjs.map → impl-B8PyNdje2.mjs.map} +1 -1
  40. package/dist/{impl-C1vn0Y7f.mjs → impl-BIYT-9BY.mjs} +2 -2
  41. package/dist/{impl-C1vn0Y7f.mjs.map → impl-BIYT-9BY.mjs.map} +1 -1
  42. package/dist/{impl-CyDdvH5a.mjs → impl-BP110k4H.mjs} +2 -2
  43. package/dist/{impl-CyDdvH5a.mjs.map → impl-BP110k4H.mjs.map} +1 -1
  44. package/dist/{impl-C0R2azuF.mjs → impl-BPdvRtQ9.mjs} +2 -2
  45. package/dist/{impl-C0R2azuF.mjs.map → impl-BPdvRtQ9.mjs.map} +1 -1
  46. package/dist/{impl-D0cf-epj.mjs → impl-BUJbKCd4.mjs} +2 -2
  47. package/dist/{impl-D0cf-epj.mjs.map → impl-BUJbKCd4.mjs.map} +1 -1
  48. package/dist/{impl-BKKBmXv4.mjs → impl-BbzX8auO.mjs} +2 -2
  49. package/dist/{impl-BKKBmXv4.mjs.map → impl-BbzX8auO.mjs.map} +1 -1
  50. package/dist/{impl-4WoLxjdU.mjs → impl-Bs4T0u8t.mjs} +2 -2
  51. package/dist/{impl-4WoLxjdU.mjs.map → impl-Bs4T0u8t.mjs.map} +1 -1
  52. package/dist/{impl-BRCAL4RM.mjs → impl-BtIse0WU.mjs} +2 -2
  53. package/dist/{impl-BRCAL4RM.mjs.map → impl-BtIse0WU.mjs.map} +1 -1
  54. package/dist/{impl-CuDZY6y7.mjs → impl-C4AjYGSc.mjs} +2 -2
  55. package/dist/{impl-CuDZY6y7.mjs.map → impl-C4AjYGSc.mjs.map} +1 -1
  56. package/dist/{impl-DqnzZv43.mjs → impl-CFyD51rZ.mjs} +2 -2
  57. package/dist/{impl-DqnzZv43.mjs.map → impl-CFyD51rZ.mjs.map} +1 -1
  58. package/dist/{impl-F2lRJEqw.mjs → impl-CKeiISid.mjs} +2 -2
  59. package/dist/{impl-F2lRJEqw.mjs.map → impl-CKeiISid.mjs.map} +1 -1
  60. package/dist/{impl-DCF3CF3O.mjs → impl-CKlQglut.mjs} +2 -2
  61. package/dist/{impl-DCF3CF3O.mjs.map → impl-CKlQglut.mjs.map} +1 -1
  62. package/dist/{impl-DNRA5i0w.mjs → impl-CPyd-ahE.mjs} +2 -2
  63. package/dist/{impl-DNRA5i0w.mjs.map → impl-CPyd-ahE.mjs.map} +1 -1
  64. package/dist/{impl-CxpGBeJ0.mjs → impl-CtMcCdUA.mjs} +2 -2
  65. package/dist/{impl-CxpGBeJ0.mjs.map → impl-CtMcCdUA.mjs.map} +1 -1
  66. package/dist/{impl-BaGN4J-w.mjs → impl-CwWY8MCP.mjs} +2 -2
  67. package/dist/{impl-BaGN4J-w.mjs.map → impl-CwWY8MCP.mjs.map} +1 -1
  68. package/dist/{impl-DrlfmH8g.mjs → impl-CwjhuIx0.mjs} +2 -2
  69. package/dist/{impl-DrlfmH8g.mjs.map → impl-CwjhuIx0.mjs.map} +1 -1
  70. package/dist/{impl-u1nIo2-E.mjs → impl-Cx97QWq8.mjs} +2 -2
  71. package/dist/{impl-u1nIo2-E.mjs.map → impl-Cx97QWq8.mjs.map} +1 -1
  72. package/dist/{impl-BJnxahnW.mjs → impl-D-Cz399O.mjs} +2 -2
  73. package/dist/{impl-BJnxahnW.mjs.map → impl-D-Cz399O.mjs.map} +1 -1
  74. package/dist/{impl-BM2SWSKF.mjs → impl-D3LHj5lS.mjs} +2 -2
  75. package/dist/{impl-BM2SWSKF.mjs.map → impl-D3LHj5lS.mjs.map} +1 -1
  76. package/dist/{impl-Dl8xSx8K.mjs → impl-D9kZ4naw.mjs} +2 -2
  77. package/dist/{impl-Dl8xSx8K.mjs.map → impl-D9kZ4naw.mjs.map} +1 -1
  78. package/dist/{impl-f59q7WnK.mjs → impl-DBMuhicF.mjs} +2 -2
  79. package/dist/{impl-f59q7WnK.mjs.map → impl-DBMuhicF.mjs.map} +1 -1
  80. package/dist/{impl-LDY2dgtc.mjs → impl-DDsOdK7O.mjs} +2 -2
  81. package/dist/{impl-LDY2dgtc.mjs.map → impl-DDsOdK7O.mjs.map} +1 -1
  82. package/dist/{impl-BFSZEQ2s.mjs → impl-DFhAp1Ky.mjs} +2 -2
  83. package/dist/{impl-BFSZEQ2s.mjs.map → impl-DFhAp1Ky.mjs.map} +1 -1
  84. package/dist/{impl-C1hh_5On.mjs → impl-DGm6q0bm.mjs} +2 -2
  85. package/dist/{impl-C1hh_5On.mjs.map → impl-DGm6q0bm.mjs.map} +1 -1
  86. package/dist/{impl-BMX6B40F.mjs → impl-DPb4YZxb.mjs} +2 -2
  87. package/dist/{impl-BMX6B40F.mjs.map → impl-DPb4YZxb.mjs.map} +1 -1
  88. package/dist/{impl-DbddBTRj2.mjs → impl-DRmJog0S2.mjs} +2 -2
  89. package/dist/{impl-DbddBTRj2.mjs.map → impl-DRmJog0S2.mjs.map} +1 -1
  90. package/dist/{impl-Bf2Qli6c2.mjs → impl-DT4VbZ-Q2.mjs} +2 -2
  91. package/dist/{impl-Bf2Qli6c2.mjs.map → impl-DT4VbZ-Q2.mjs.map} +1 -1
  92. package/dist/{impl-D8YS5Bge.mjs → impl-Ds6ROQi7.mjs} +2 -2
  93. package/dist/{impl-D8YS5Bge.mjs.map → impl-Ds6ROQi7.mjs.map} +1 -1
  94. package/dist/{impl-ClhMRgQZ.mjs → impl-P1hltmA6.mjs} +2 -2
  95. package/dist/{impl-ClhMRgQZ.mjs.map → impl-P1hltmA6.mjs.map} +1 -1
  96. package/dist/{impl-BrdCuU0I.mjs → impl-XztvsE6N.mjs} +2 -2
  97. package/dist/{impl-BrdCuU0I.mjs.map → impl-XztvsE6N.mjs.map} +1 -1
  98. package/dist/{impl-C54oUOeM2.mjs → impl-qTeP2Voo2.mjs} +2 -2
  99. package/dist/{impl-C54oUOeM2.mjs.map → impl-qTeP2Voo2.mjs.map} +1 -1
  100. package/dist/{impl-OXGlZyUo.mjs → impl-xR4iG_u5.mjs} +2 -2
  101. package/dist/{impl-OXGlZyUo.mjs.map → impl-xR4iG_u5.mjs.map} +1 -1
  102. package/dist/{impl-CKg5Xu-r.mjs → impl-yJETNVcT.mjs} +2 -2
  103. package/dist/{impl-CKg5Xu-r.mjs.map → impl-yJETNVcT.mjs.map} +1 -1
  104. package/dist/index.mjs +1 -1
  105. package/dist/{markRequestDataSiloIdsCompleted-B-Dgme2r.mjs → markRequestDataSiloIdsCompleted-CU0ba_mE.mjs} +2 -2
  106. package/dist/{markRequestDataSiloIdsCompleted-B-Dgme2r.mjs.map → markRequestDataSiloIdsCompleted-CU0ba_mE.mjs.map} +1 -1
  107. package/dist/{markSilentPrivacyRequests-CPU4Q2lF.mjs → markSilentPrivacyRequests-Bf_4pCaq.mjs} +2 -2
  108. package/dist/{markSilentPrivacyRequests-CPU4Q2lF.mjs.map → markSilentPrivacyRequests-Bf_4pCaq.mjs.map} +1 -1
  109. package/dist/{notifyPrivacyRequestsAdditionalTime-BWsm5Y5K.mjs → notifyPrivacyRequestsAdditionalTime-rnUXdvBR.mjs} +2 -2
  110. package/dist/{notifyPrivacyRequestsAdditionalTime-BWsm5Y5K.mjs.map → notifyPrivacyRequestsAdditionalTime-rnUXdvBR.mjs.map} +1 -1
  111. package/dist/{policy-bundle-manifest-BDey6EBs.mjs → policy-bundle-manifest-B1Herd_n.mjs} +2 -2
  112. package/dist/{policy-bundle-manifest-BDey6EBs.mjs.map → policy-bundle-manifest-B1Herd_n.mjs.map} +1 -1
  113. package/dist/{policy-project-discovery-DV5PnFJq.mjs → policy-project-discovery-oc49b2gr.mjs} +2 -2
  114. package/dist/{policy-project-discovery-DV5PnFJq.mjs.map → policy-project-discovery-oc49b2gr.mjs.map} +1 -1
  115. package/dist/{policy-scaffold-config-DtRELUZx.mjs → policy-scaffold-config-CpXR9KgF.mjs} +2 -2
  116. package/dist/{policy-scaffold-config-DtRELUZx.mjs.map → policy-scaffold-config-CpXR9KgF.mjs.map} +1 -1
  117. package/dist/{pullChunkedCustomSiloOutstandingIdentifiers-BaHp-jxs.mjs → pullChunkedCustomSiloOutstandingIdentifiers-BcBj9prQ.mjs} +2 -2
  118. package/dist/{pullChunkedCustomSiloOutstandingIdentifiers-BaHp-jxs.mjs.map → pullChunkedCustomSiloOutstandingIdentifiers-BcBj9prQ.mjs.map} +1 -1
  119. package/dist/{pullManualEnrichmentIdentifiersToCsv-CiEFIvl_.mjs → pullManualEnrichmentIdentifiersToCsv-BSUXUp8v.mjs} +2 -2
  120. package/dist/{pullManualEnrichmentIdentifiersToCsv-CiEFIvl_.mjs.map → pullManualEnrichmentIdentifiersToCsv-BSUXUp8v.mjs.map} +1 -1
  121. package/dist/{pushCronIdentifiersFromCsv-DeS78zyp.mjs → pushCronIdentifiersFromCsv-D-l3erLO.mjs} +2 -2
  122. package/dist/{pushCronIdentifiersFromCsv-DeS78zyp.mjs.map → pushCronIdentifiersFromCsv-D-l3erLO.mjs.map} +1 -1
  123. package/dist/{pushManualEnrichmentIdentifiersFromCsv-B3iVwuvs.mjs → pushManualEnrichmentIdentifiersFromCsv-CqLIw5MV.mjs} +2 -2
  124. package/dist/{pushManualEnrichmentIdentifiersFromCsv-B3iVwuvs.mjs.map → pushManualEnrichmentIdentifiersFromCsv-CqLIw5MV.mjs.map} +1 -1
  125. package/dist/{removeUnverifiedRequestIdentifiers-qS5A_eRZ.mjs → removeUnverifiedRequestIdentifiers-B-Az2HJ8.mjs} +2 -2
  126. package/dist/{removeUnverifiedRequestIdentifiers-qS5A_eRZ.mjs.map → removeUnverifiedRequestIdentifiers-B-Az2HJ8.mjs.map} +1 -1
  127. package/dist/{retryRequestDataSilos-Cxaka-kA.mjs → retryRequestDataSilos-D70pC9JF.mjs} +2 -2
  128. package/dist/{retryRequestDataSilos-Cxaka-kA.mjs.map → retryRequestDataSilos-D70pC9JF.mjs.map} +1 -1
  129. package/dist/{skipPreflightJobs-FdIm_Imj.mjs → skipPreflightJobs-B0YG7mhP.mjs} +2 -2
  130. package/dist/{skipPreflightJobs-FdIm_Imj.mjs.map → skipPreflightJobs-B0YG7mhP.mjs.map} +1 -1
  131. package/dist/{skipRequestDataSilos-MTbj-OfW.mjs → skipRequestDataSilos-DV5Dy5Dw.mjs} +2 -2
  132. package/dist/{skipRequestDataSilos-MTbj-OfW.mjs.map → skipRequestDataSilos-DV5Dy5Dw.mjs.map} +1 -1
  133. package/dist/{streamPrivacyRequestsToCsv-BVzYSXNY.mjs → streamPrivacyRequestsToCsv-Qm2r7Iy2.mjs} +2 -2
  134. package/dist/{streamPrivacyRequestsToCsv-BVzYSXNY.mjs.map → streamPrivacyRequestsToCsv-Qm2r7Iy2.mjs.map} +1 -1
  135. package/dist/{updateConsentManagerVersionToLatest-Udi7Iazb.mjs → updateConsentManagerVersionToLatest-cryDInky.mjs} +2 -2
  136. package/dist/{updateConsentManagerVersionToLatest-Udi7Iazb.mjs.map → updateConsentManagerVersionToLatest-cryDInky.mjs.map} +1 -1
  137. package/dist/{uploadConsents-w7FE65Zg.mjs → uploadConsents-Din2Qdws.mjs} +2 -2
  138. package/dist/{uploadConsents-w7FE65Zg.mjs.map → uploadConsents-Din2Qdws.mjs.map} +1 -1
  139. package/dist/{uploadPrivacyRequestsFromCsv-B__QsguT.mjs → uploadPrivacyRequestsFromCsv-DHYbgV8h.mjs} +2 -2
  140. package/dist/{uploadPrivacyRequestsFromCsv-B__QsguT.mjs.map → uploadPrivacyRequestsFromCsv-DHYbgV8h.mjs.map} +1 -1
  141. package/package.json +5 -5
@@ -1,4 +1,4 @@
1
- import{_ as e,c as t,d as n,f as r,g as i,o as a,s as o,u as s}from"./policy-project-discovery-DV5PnFJq.mjs";import{n as c,r as l,t as u}from"./jsonc-CdTubr53.mjs";import{posix as d,relative as f,sep as p}from"node:path";const m=`0.42.0`,h=`.github/workflows/transcend-policy.yml`,g=`d23441a48e516b6c34aea4fa41551a30e30af803`,_=`b2b258e089860efaadaaf71bf6e3aecb4a3eeff1`,v=`761188c3b435761fa254beca508a44875619648f`;function y(e,t){return e===`.`?t:d.join(e,t)}function b(e){let t=e.bundleDirectories??[y(e.workspaceDirectory,s)],r=[y(e.workspaceDirectory,`**/*.rego`),y(e.workspaceDirectory,`**/*.json`),y(e.workspaceDirectory,`**/*.yaml`),y(e.workspaceDirectory,`**/*.yml`),y(e.workspaceDirectory,`**/.manifest`),y(e.workspaceDirectory,`.regal/config.yaml`),y(e.workspaceDirectory,`.regal.yaml`),h].map(e=>` - ${JSON.stringify(e)}`).join(`
1
+ import{_ as e,c as t,d as n,f as r,g as i,o as a,s as o,u as s}from"./policy-project-discovery-oc49b2gr.mjs";import{n as c,r as l,t as u}from"./jsonc-CdTubr53.mjs";import{posix as d,relative as f,sep as p}from"node:path";const m=`0.42.0`,h=`.github/workflows/transcend-policy.yml`,g=`d23441a48e516b6c34aea4fa41551a30e30af803`,_=`b2b258e089860efaadaaf71bf6e3aecb4a3eeff1`,v=`761188c3b435761fa254beca508a44875619648f`;function y(e,t){return e===`.`?t:d.join(e,t)}function b(e){let t=e.bundleDirectories??[y(e.workspaceDirectory,s)],r=[y(e.workspaceDirectory,`**/*.rego`),y(e.workspaceDirectory,`**/*.json`),y(e.workspaceDirectory,`**/*.yaml`),y(e.workspaceDirectory,`**/*.yml`),y(e.workspaceDirectory,`**/.manifest`),y(e.workspaceDirectory,`.regal/config.yaml`),y(e.workspaceDirectory,`.regal.yaml`),h].map(e=>` - ${JSON.stringify(e)}`).join(`
2
2
  `),i;return i=t.length===0?` lint:
3
3
  runs-on: ubuntu-latest
4
4
  steps:
@@ -73,4 +73,4 @@ ${i}`}const x=`tsandall.opa`,S=`glebbash.opa-highlight-only`;function C(t,n){ret
73
73
  `,warning:`${t instanceof Error?t.message:String(t)} Existing content was left unchanged.`}}}function E(e,t,n,r){if(t.length===0)return{contents:e??`{}
74
74
  `,warnings:r};try{return{contents:u(e,t,n),warnings:r}}catch(t){return{contents:e??`{}
75
75
  `,warnings:[...r,`${t instanceof Error?t.message:String(t)} Existing content was left unchanged.`]}}}function D(e,t,n,r){t.forEach(({path:t,value:i,label:a})=>{let o=t.reduce((e,t)=>e&&typeof e==`object`&&!Array.isArray(e)?e[t]:void 0,e);o===void 0?n.push({path:t,value:i}):w(o,i)||r.push(`VS Code setting "${a}" has a repository-specific value and was left unchanged.`)})}function O(e){return e===`.`?"${workspaceFolder}":`\${workspaceFolder}/${e}`}function k(e,t){return f(e,t).split(p).join(`/`)||`.`}function A(e,t,n=C(r)){let i=k(e,t);return O(i===`.`?n.bundleDir:`${i}/${n.bundleDir}`)}function j(e,n){let r=k(e,n);return r===`.`?`**/${t}`:`**/${r}/**/${t}`}function M(e,t,n=C(r)){let s=k(e,t),c=s===`.`?`/${n.bundleDir}`:`/${s}/${n.bundleDir}`,l=n.template===i.Permissions?a:s===`.`?`./${n.bundleDir}/${o}`:`./${s}/${n.bundleDir}/${o}`;return{fileMatch:[`${c}/input.json`,`${c}/input.example.json`],url:l}}function N(e,t,n,r){let i=T(e,`VS Code policy settings`);if(`warning`in i)return{contents:i.contents,warnings:[i.warning]};let{current:a}=i,o=[],s=[];if(r&&r.length>0){let e=a[`opa.roots`],i=r.map(e=>A(t,n,e));if(e===void 0)o.push({path:[`opa.roots`],value:i});else if(Array.isArray(e)&&e.every(e=>typeof e==`string`)){let t=c(e,i);w(t,e)||o.push({path:[`opa.roots`],value:t})}else s.push(`VS Code setting "opa.roots" is customized and was left unchanged.`)}D(a,[{path:[`opa.checkOnSave`],value:!0,label:`opa.checkOnSave`},{path:[`opa.strictMode`],value:!0,label:`opa.strictMode`},{path:[`opa.bundleMode`],value:!0,label:`opa.bundleMode`},{path:[`opa.formatter`],value:`opa-fmt-rego-v1`,label:`opa.formatter`}],o,s);let l=a[`files.associations`],u=j(t,n);if(l===void 0?o.push({path:[`files.associations`],value:{[u]:`json`}}):typeof l==`object`&&l&&!Array.isArray(l)?D(a,[{path:[`files.associations`,u],value:`json`,label:`files.associations."${u}"`}],o,s):s.push(`VS Code setting "files.associations" is customized and was left unchanged.`),r&&r.length>0){let e=r.map(e=>M(t,n,e)),i=a[`json.schemas`];if(i===void 0)o.push({path:[`json.schemas`],value:e});else if(Array.isArray(i)){let t=[...i];e.forEach(e=>{if(t.some(t=>typeof t==`object`&&!!t&&!Array.isArray(t)&&w(t,e)))return;let n=t.findIndex(t=>typeof t==`object`&&!!t&&!Array.isArray(t)&&typeof t.url==`string`&&t.url===e.url);if(n===-1){t=[...t,e];return}let r=t[n],i=r.fileMatch;if(!Array.isArray(i)||!i.every(e=>typeof e==`string`)){s.push(`VS Code setting "json.schemas" has a repository-specific entry for the ${e.url} schema and was left unchanged.`);return}let a=c(i,e.fileMatch);w(a,i)||(t=t.map((e,t)=>t===n?{...r,fileMatch:a}:e))}),w(t,i)||o.push({path:[`json.schemas`],value:t})}else s.push(`VS Code setting "json.schemas" is customized and was left unchanged.`)}let d=a[`[rego]`];return d===void 0?o.push({path:[`[rego]`],value:{"editor.defaultFormatter":x,"editor.formatOnSave":!0,"editor.insertSpaces":!1,"editor.tabSize":4}}):typeof d==`object`&&d&&!Array.isArray(d)?D(a,[{path:[`[rego]`,`editor.defaultFormatter`],value:x,label:`[rego].editor.defaultFormatter`},{path:[`[rego]`,`editor.formatOnSave`],value:!0,label:`[rego].editor.formatOnSave`},{path:[`[rego]`,`editor.insertSpaces`],value:!1,label:`[rego].editor.insertSpaces`},{path:[`[rego]`,`editor.tabSize`],value:4,label:`[rego].editor.tabSize`}],o,s):s.push(`VS Code language setting "[rego]" is customized and was left unchanged.`),E(e,o,`VS Code policy settings`,s)}function P(e){let t=T(e,`VS Code policy extension recommendations`);if(`warning`in t)return{contents:t.contents,warnings:[t.warning]};let{current:n}=t,r=[],i=[],a=n.recommendations;if(a===void 0||Array.isArray(a)&&a.every(e=>typeof e==`string`)){let e=a??[],t=c(e,[x]).filter(e=>e!==S);w(e,t)||r.push({path:[`recommendations`],value:t})}else i.push(`VS Code extension recommendations use an unknown shape and were left unchanged.`);let o=n.unwantedRecommendations;if(o===void 0||Array.isArray(o)&&o.every(e=>typeof e==`string`)){let e=o??[],t=c(e,[S]);w(e,t)||r.push({path:[`unwantedRecommendations`],value:t})}else i.push(`VS Code unwanted extension recommendations use an unknown shape and were left unchanged.`);return E(e,r,`VS Code policy extension recommendations`,i)}function F(e,t,n=C(r)){let i=k(e,t),a=i===`.`?n.bundleDir:`${i}/${n.bundleDir}`;return{label:`policy: lint ${n.root}`,type:`process`,command:`transcend`,args:[`policy`,`lint`,a,`--noInteractive`],group:{kind:`test`,isDefault:!1},problemMatcher:[]}}function I(e){return{label:`policy: lint`,dependsOn:e.map(e=>`policy: lint ${e.root}`),dependsOrder:`sequence`,group:{kind:`test`,isDefault:!0},problemMatcher:[]}}function L(e,t,n,r){let i=T(e,`VS Code policy tasks`);if(`warning`in i)return{contents:i.contents,warnings:[i.warning]};let{current:a}=i,o=[],s=[],c=a.version;if(c===void 0?o.push({path:[`version`],value:`2.0.0`}):c!==`2.0.0`&&s.push(`VS Code task schema version is customized and was left unchanged.`),!r||r.length===0)return E(e,o,`VS Code policy tasks`,s);let l=r.map(e=>F(t,n,e)),u=I(r),d=[...l,u],f=new Set(d.map(e=>e.label)),p=a.tasks;if(p===void 0)o.push({path:[`tasks`],value:d});else if(Array.isArray(p)){let e=[...p],t=!1;d.forEach(n=>{let r=n.label,i=e.findIndex(e=>typeof e==`object`&&!!e&&!Array.isArray(e)&&e.label===r);if(i===-1){e=[...e,n],t=!0;return}w(e[i],n)||(f.has(r)?(e=e.map((e,t)=>t===i?n:e),t=!0):s.push(`VS Code task "${r}" has repository-specific customization and was left unchanged.`))}),t&&o.push({path:[`tasks`],value:e})}else s.push(`VS Code tasks use an unknown shape and were left unchanged.`);return E(e,o,`VS Code policy tasks`,s)}export{h as a,C as i,N as n,b as o,L as r,P as t};
76
- //# sourceMappingURL=policy-scaffold-config-DtRELUZx.mjs.map
76
+ //# sourceMappingURL=policy-scaffold-config-CpXR9KgF.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"policy-scaffold-config-DtRELUZx.mjs","names":[],"sources":["../src/lib/policy/policy-scaffold-artifacts.ts","../src/lib/policy/policy-scaffold-config.ts"],"sourcesContent":["import { posix } from 'node:path';\n\nimport {\n POLICY_STARTER_BUNDLE_DIRECTORY,\n POLICY_STARTER_OPA_VERSION,\n} from './policy-scaffold-templates.js';\n\n/** Regal version pinned by generated policy validation. */\nexport const POLICY_STARTER_REGAL_VERSION = '0.42.0';\n\n/** Repository-relative generated workflow path. */\nexport const POLICY_CI_WORKFLOW_PATH = '.github/workflows/transcend-policy.yml';\n\n/** Immutable actions/checkout v6 release commit. */\nexport const ACTIONS_CHECKOUT_SHA = 'd23441a48e516b6c34aea4fa41551a30e30af803';\n\n/** Immutable open-policy-agent/setup-opa v2.4.0 release commit. */\nexport const SETUP_OPA_SHA = 'b2b258e089860efaadaaf71bf6e3aecb4a3eeff1';\n\n/** Immutable open-policy-agent/setup-regal v2.0.0 release commit. */\nexport const SETUP_REGAL_SHA = '761188c3b435761fa254beca508a44875619648f';\n\n/**\n * Prefix a path below the repository-relative policy workspace.\n *\n * @param workspaceDirectory - POSIX repository-relative workspace\n * @param child - POSIX workspace-relative path or pattern\n * @returns Repository-relative path or pattern\n */\nfunction workspacePath(workspaceDirectory: string, child: string): string {\n return workspaceDirectory === '.' ? child : posix.join(workspaceDirectory, child);\n}\n\n/**\n * Generate credential-free, validation-only Policy Engine CI.\n *\n * Path filters watch the whole policy workspace. Lint runs against each\n * publishable `{root}-bundle/` directory.\n *\n * @param options - Repository paths and exact CLI release\n * @returns Complete GitHub Actions workflow YAML\n */\nexport function generatePolicyGithubActionsWorkflow(options: {\n /** Exact released Transcend CLI version. */\n cliVersion: string;\n /** Policy workspace directory relative to the repository root. */\n workspaceDirectory: string;\n /**\n * Publish directories to lint, relative to the repository root.\n *\n * Pass an empty array for a workspace with no bundles yet (`policy init`).\n * Defaults to the disposable starter bundle under the workspace when omitted.\n */\n bundleDirectories?: readonly string[];\n}): string {\n const bundleDirectories = options.bundleDirectories ?? [\n workspacePath(options.workspaceDirectory, POLICY_STARTER_BUNDLE_DIRECTORY),\n ];\n const watchedPaths = [\n workspacePath(options.workspaceDirectory, '**/*.rego'),\n workspacePath(options.workspaceDirectory, '**/*.json'),\n workspacePath(options.workspaceDirectory, '**/*.yaml'),\n workspacePath(options.workspaceDirectory, '**/*.yml'),\n workspacePath(options.workspaceDirectory, '**/.manifest'),\n workspacePath(options.workspaceDirectory, '.regal/config.yaml'),\n workspacePath(options.workspaceDirectory, '.regal.yaml'),\n POLICY_CI_WORKFLOW_PATH,\n ];\n const pathFilters = watchedPaths.map((path) => ` - ${JSON.stringify(path)}`).join('\\n');\n\n let lintJob: string;\n if (bundleDirectories.length === 0) {\n lintJob = ` lint:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@${ACTIONS_CHECKOUT_SHA} # v6\n - name: No publishable bundles yet\n run: >-\n echo \"Run transcend policy new to add a bundle;\n policy new refreshes this workflow automatically.\"\n`;\n } else if (bundleDirectories.length === 1) {\n lintJob = ` lint:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@${ACTIONS_CHECKOUT_SHA} # v6\n - name: Set up OPA\n uses: open-policy-agent/setup-opa@${SETUP_OPA_SHA} # v2.4.0\n with:\n version: ${JSON.stringify(POLICY_STARTER_OPA_VERSION)}\n - name: Set up Regal\n uses: open-policy-agent/setup-regal@${SETUP_REGAL_SHA} # v2.0.0\n with:\n version: ${JSON.stringify(POLICY_STARTER_REGAL_VERSION)}\n - name: Install the Transcend CLI\n run: npm install --global @transcend-io/cli@${options.cliVersion}\n - name: Validate Policy Engine project\n env:\n POLICY_DIRECTORY: ${JSON.stringify(bundleDirectories[0])}\n run: >-\n transcend policy lint\n \"$POLICY_DIRECTORY\"\n --noInteractive\n --json\n`;\n } else {\n lintJob = ` lint:\n runs-on: ubuntu-latest\n strategy:\n fail-fast: false\n matrix:\n policy_directory:\n${bundleDirectories.map((directory) => ` - ${JSON.stringify(directory)}`).join('\\n')}\n steps:\n - uses: actions/checkout@${ACTIONS_CHECKOUT_SHA} # v6\n - name: Set up OPA\n uses: open-policy-agent/setup-opa@${SETUP_OPA_SHA} # v2.4.0\n with:\n version: ${JSON.stringify(POLICY_STARTER_OPA_VERSION)}\n - name: Set up Regal\n uses: open-policy-agent/setup-regal@${SETUP_REGAL_SHA} # v2.0.0\n with:\n version: ${JSON.stringify(POLICY_STARTER_REGAL_VERSION)}\n - name: Install the Transcend CLI\n run: npm install --global @transcend-io/cli@${options.cliVersion}\n - name: Validate Policy Engine project\n env:\n POLICY_DIRECTORY: \\${{ matrix.policy_directory }}\n run: transcend policy lint \"$POLICY_DIRECTORY\" --noInteractive --json\n`;\n }\n\n return `# Generated by transcend policy init.\nname: Transcend Policy\n\non:\n pull_request:\n paths:\n${pathFilters}\n push:\n paths:\n${pathFilters}\n\npermissions:\n contents: read\n\njobs:\n${lintJob}`;\n}\n","import { relative, sep } from 'node:path';\n\nimport {\n mergeJsonc,\n mergeStringArray,\n parseJsoncObject,\n type JsoncUpdate,\n} from '../scaffolding/jsonc.js';\nimport {\n buildBundleDirectoryName,\n PERMISSIONS_POLICY_INPUT_SCHEMA_ID,\n POLICY_INPUT_SCHEMA_FILENAME,\n POLICY_MANIFEST_FILENAME,\n POLICY_STARTER_ROOT,\n PolicyTemplate,\n type PolicyTemplateName,\n} from './policy-scaffold-templates.js';\n\n/** Recommended VS Code extension for OPA and Regal authoring. */\nexport const POLICY_VSCODE_EXTENSION = 'tsandall.opa';\n\n/** Conflicting syntax-only VS Code extension. */\nexport const CONFLICTING_POLICY_VSCODE_EXTENSION = 'glebbash.opa-highlight-only';\n\n/**\n * Local publish unit: Rego package root plus its workspace-relative folder basename.\n *\n * `bundleDir` is not required to be `{root}-bundle` — `policy new --bundle-dir` may override it.\n */\nexport interface PolicyBundleRef {\n /** Package-path root (`.manifest` / Regal `project.roots`). */\n root: string;\n /** Basename of the publish directory under the policy workspace. */\n bundleDir: string;\n /**\n * Scaffold template that produced this bundle, when known.\n *\n * Permissions bundles point VS Code `json.schemas` at the published input\n * schema `$id` so editor validation tracks the live contract.\n */\n template?: PolicyTemplateName;\n}\n\n/**\n * Build a bundle ref with the default `{root}-bundle` directory.\n *\n * @param root - Package root\n * @param template - Optional scaffold template\n * @returns Bundle ref\n */\nexport function policyBundleRef(root: string, template?: PolicyTemplateName): PolicyBundleRef {\n return template === undefined\n ? { root, bundleDir: buildBundleDirectoryName(root) }\n : { root, bundleDir: buildBundleDirectoryName(root), template };\n}\n\n/** Result of safely merging one repository editor artifact. */\nexport interface PolicyEditorMergeResult {\n /** Complete merged contents, or the unchanged original when unsafe. */\n contents: string;\n /** Actionable preservation warnings. */\n warnings: string[];\n}\n\n/** One desired scalar setting that must not replace a customization. */\ninterface DesiredSetting {\n /** JSONC object path. */\n path: string[];\n /** Desired generated value. */\n value: unknown;\n /** Human-readable setting key. */\n label: string;\n}\n\n/**\n * Compare JSON-compatible configuration values.\n *\n * @param left - Existing value\n * @param right - Desired value\n * @returns Whether the values are structurally equal\n */\nfunction configurationValuesEqual(left: unknown, right: unknown): boolean {\n return JSON.stringify(left) === JSON.stringify(right);\n}\n\n/**\n * Parse an editor object while preserving malformed existing content.\n *\n * @param contents - Existing JSONC, or null\n * @param label - Configuration label\n * @returns Parsed object or a preservation result\n */\nfunction parseEditorObject(\n contents: string | null,\n label: string,\n):\n | {\n /** Parsed JSONC object. */\n current: Record<string, unknown>;\n }\n | {\n /** Complete unchanged contents. */\n contents: string;\n /** Actionable warning. */\n warning: string;\n } {\n try {\n return { current: parseJsoncObject(contents ?? '{}\\n', label) };\n } catch (error) {\n return {\n contents: contents ?? '{}\\n',\n warning: `${error instanceof Error ? error.message : String(error)} Existing content was left unchanged.`,\n };\n }\n}\n\n/**\n * Apply safe JSONC updates or preserve the existing file when comments prevent it.\n *\n * @param contents - Existing JSONC\n * @param updates - Proven-safe updates\n * @param label - Configuration label\n * @param warnings - Existing merge warnings\n * @returns Complete contents and warnings\n */\nfunction applyEditorUpdates(\n contents: string | null,\n updates: readonly JsoncUpdate[],\n label: string,\n warnings: string[],\n): PolicyEditorMergeResult {\n if (updates.length === 0) {\n return { contents: contents ?? '{}\\n', warnings };\n }\n try {\n return {\n contents: mergeJsonc(contents, updates, label),\n warnings,\n };\n } catch (error) {\n return {\n contents: contents ?? '{}\\n',\n warnings: [\n ...warnings,\n `${error instanceof Error ? error.message : String(error)} Existing content was left unchanged.`,\n ],\n };\n }\n}\n\n/**\n * Add absent scalar settings without replacing repository customizations.\n *\n * @param current - Parsed editor object\n * @param desired - Desired settings\n * @param updates - Safe updates to append to\n * @param warnings - Conflict warnings to append to\n */\nfunction collectSafeScalarUpdates(\n current: Record<string, unknown>,\n desired: readonly DesiredSetting[],\n updates: JsoncUpdate[],\n warnings: string[],\n): void {\n desired.forEach(({ path, value, label }) => {\n const existing = path.reduce<unknown>((parent, key) => {\n return parent && typeof parent === 'object' && !Array.isArray(parent)\n ? (parent as Record<string, unknown>)[key]\n : undefined;\n }, current);\n if (existing === undefined) {\n updates.push({ path, value });\n } else if (!configurationValuesEqual(existing, value)) {\n warnings.push(\n `VS Code setting \"${label}\" has a repository-specific value and was left unchanged.`,\n );\n }\n });\n}\n\n/**\n * Build a `${workspaceFolder}/…` path for a repository-relative POSIX path.\n *\n * @param repositoryRelativePath - Path relative to the repository root\n * @returns VS Code workspace path\n */\nfunction buildWorkspaceFolderPath(repositoryRelativePath: string): string {\n return repositoryRelativePath === '.'\n ? '${workspaceFolder}'\n : `\\${workspaceFolder}/${repositoryRelativePath}`;\n}\n\n/**\n * Repository-relative POSIX path from the root that owns `.vscode`.\n *\n * @param repositoryRoot - Root that owns `.vscode`\n * @param absolutePath - Absolute path below the repository\n * @returns POSIX repository-relative path\n */\nfunction repositoryRelativePosix(repositoryRoot: string, absolutePath: string): string {\n return relative(repositoryRoot, absolutePath).split(sep).join('/') || '.';\n}\n\n/**\n * Build the workspace-relative OPA publish root for a bundle.\n *\n * @param repositoryRoot - Root that owns `.vscode`\n * @param workspaceDirectory - Selected policy workspace directory\n * @param bundle - Package root and local directory basename\n * @returns VS Code workspace path\n */\nfunction buildOpaBundleRoot(\n repositoryRoot: string,\n workspaceDirectory: string,\n bundle: PolicyBundleRef = policyBundleRef(POLICY_STARTER_ROOT),\n): string {\n const workspace = repositoryRelativePosix(repositoryRoot, workspaceDirectory);\n const path = workspace === '.' ? bundle.bundleDir : `${workspace}/${bundle.bundleDir}`;\n return buildWorkspaceFolderPath(path);\n}\n\n/**\n * Build a `files.associations` glob for every `.manifest` under the workspace.\n *\n * Patterns that contain `/` are matched against the absolute file path, so a\n * leading `**\\/` prefix is required for a portable workspace-relative association.\n *\n * @param repositoryRoot - Root that owns `.vscode`\n * @param workspaceDirectory - Selected policy workspace directory\n * @returns Association pattern key\n */\nfunction buildPolicyManifestAssociationPattern(\n repositoryRoot: string,\n workspaceDirectory: string,\n): string {\n const workspace = repositoryRelativePosix(repositoryRoot, workspaceDirectory);\n return workspace === '.'\n ? `**/${POLICY_MANIFEST_FILENAME}`\n : `**/${workspace}/**/${POLICY_MANIFEST_FILENAME}`;\n}\n\n/**\n * Build `json.schemas` fileMatch globs for a bundle's input documents.\n *\n * Permissions templates use the published schema `$id` so VS Code validation\n * tracks the live contract. Generic bundles use the local `input.schema.json`\n * (OPA / offline tooling still always use the local file via `--schema`).\n *\n * @param repositoryRoot - Root that owns `.vscode`\n * @param workspaceDirectory - Selected policy workspace directory\n * @param bundle - Package root and local directory basename\n * @returns Absolute-style fileMatch paths and schema URL\n */\nfunction buildBundleInputJsonSchema(\n repositoryRoot: string,\n workspaceDirectory: string,\n bundle: PolicyBundleRef = policyBundleRef(POLICY_STARTER_ROOT),\n): {\n /** VS Code fileMatch globs (leading slash = workspace-relative). */\n fileMatch: string[];\n /** Schema URL (local path or published Permissions `$id`). */\n url: string;\n} {\n const workspace = repositoryRelativePosix(repositoryRoot, workspaceDirectory);\n const bundlePrefix =\n workspace === '.' ? `/${bundle.bundleDir}` : `/${workspace}/${bundle.bundleDir}`;\n const schemaUrl =\n bundle.template === PolicyTemplate.Permissions\n ? PERMISSIONS_POLICY_INPUT_SCHEMA_ID\n : workspace === '.'\n ? `./${bundle.bundleDir}/${POLICY_INPUT_SCHEMA_FILENAME}`\n : `./${workspace}/${bundle.bundleDir}/${POLICY_INPUT_SCHEMA_FILENAME}`;\n return {\n fileMatch: [`${bundlePrefix}/input.json`, `${bundlePrefix}/input.example.json`],\n url: schemaUrl,\n };\n}\n\n/**\n * Merge strict workspace-scoped OPA and Rego editor settings.\n *\n * Setting names match the authoritative `open-policy-agent/vscode-opa`\n * extension metadata. Existing conflicting values are preserved and reported.\n *\n * When `bundles` is provided, `opa.roots` and `json.schemas` are populated\n * for those bundles. When omitted (init without bundles), `opa.roots` and\n * `json.schemas` are left empty or unset.\n *\n * @param contents - Existing `.vscode/settings.json` JSONC\n * @param repositoryRoot - Root that owns `.vscode`\n * @param workspaceDirectory - Selected policy workspace directory\n * @param bundles - Local publish units to add to `opa.roots` and `json.schemas`\n * @returns Merged settings and conflict warnings\n */\nexport function mergePolicyEditorSettings(\n contents: string | null,\n repositoryRoot: string,\n workspaceDirectory: string,\n bundles?: readonly PolicyBundleRef[],\n): PolicyEditorMergeResult {\n const parsed = parseEditorObject(contents, 'VS Code policy settings');\n if ('warning' in parsed) {\n return { contents: parsed.contents, warnings: [parsed.warning] };\n }\n const { current } = parsed;\n const updates: JsoncUpdate[] = [];\n const warnings: string[] = [];\n\n if (bundles && bundles.length > 0) {\n const roots = current['opa.roots'];\n const desiredRoots = bundles.map((bundle) =>\n buildOpaBundleRoot(repositoryRoot, workspaceDirectory, bundle),\n );\n if (roots === undefined) {\n updates.push({ path: ['opa.roots'], value: desiredRoots });\n } else if (Array.isArray(roots) && roots.every((value) => typeof value === 'string')) {\n const merged = mergeStringArray(roots, desiredRoots);\n if (!configurationValuesEqual(merged, roots)) {\n updates.push({ path: ['opa.roots'], value: merged });\n }\n } else {\n warnings.push('VS Code setting \"opa.roots\" is customized and was left unchanged.');\n }\n }\n\n collectSafeScalarUpdates(\n current,\n [\n { path: ['opa.checkOnSave'], value: true, label: 'opa.checkOnSave' },\n { path: ['opa.strictMode'], value: true, label: 'opa.strictMode' },\n { path: ['opa.bundleMode'], value: true, label: 'opa.bundleMode' },\n { path: ['opa.formatter'], value: 'opa-fmt-rego-v1', label: 'opa.formatter' },\n ],\n updates,\n warnings,\n );\n\n const associations = current['files.associations'];\n const manifestAssociation = buildPolicyManifestAssociationPattern(\n repositoryRoot,\n workspaceDirectory,\n );\n if (associations === undefined) {\n updates.push({\n path: ['files.associations'],\n value: { [manifestAssociation]: 'json' },\n });\n } else if (\n associations !== null &&\n typeof associations === 'object' &&\n !Array.isArray(associations)\n ) {\n collectSafeScalarUpdates(\n current,\n [\n {\n path: ['files.associations', manifestAssociation],\n value: 'json',\n label: `files.associations.\"${manifestAssociation}\"`,\n },\n ],\n updates,\n warnings,\n );\n } else {\n warnings.push('VS Code setting \"files.associations\" is customized and was left unchanged.');\n }\n\n if (bundles && bundles.length > 0) {\n const desiredJsonSchemas = bundles.map((bundle) =>\n buildBundleInputJsonSchema(repositoryRoot, workspaceDirectory, bundle),\n );\n const jsonSchemas = current['json.schemas'];\n if (jsonSchemas === undefined) {\n updates.push({ path: ['json.schemas'], value: desiredJsonSchemas });\n } else if (Array.isArray(jsonSchemas)) {\n let merged = [...jsonSchemas];\n desiredJsonSchemas.forEach((desired) => {\n const hasExactMatch = merged.some(\n (entry) =>\n entry !== null &&\n typeof entry === 'object' &&\n !Array.isArray(entry) &&\n configurationValuesEqual(entry, desired),\n );\n if (hasExactMatch) {\n return;\n }\n const sameUrlIndex = merged.findIndex(\n (entry) =>\n entry !== null &&\n typeof entry === 'object' &&\n !Array.isArray(entry) &&\n typeof (entry as Record<string, unknown>).url === 'string' &&\n (entry as Record<string, unknown>).url === desired.url,\n );\n if (sameUrlIndex === -1) {\n merged = [...merged, desired];\n return;\n }\n const existing = merged[sameUrlIndex] as Record<string, unknown>;\n const existingMatches = existing.fileMatch;\n if (\n !Array.isArray(existingMatches) ||\n !existingMatches.every((m) => typeof m === 'string')\n ) {\n warnings.push(\n `VS Code setting \"json.schemas\" has a repository-specific entry for the ${desired.url} schema and was left unchanged.`,\n );\n return;\n }\n const unioned = mergeStringArray(existingMatches, desired.fileMatch);\n if (!configurationValuesEqual(unioned, existingMatches)) {\n merged = merged.map((entry, index) =>\n index === sameUrlIndex ? { ...existing, fileMatch: unioned } : entry,\n );\n }\n });\n if (!configurationValuesEqual(merged, jsonSchemas)) {\n updates.push({ path: ['json.schemas'], value: merged });\n }\n } else {\n warnings.push('VS Code setting \"json.schemas\" is customized and was left unchanged.');\n }\n }\n\n const regoSettings = current['[rego]'];\n if (regoSettings === undefined) {\n updates.push({\n path: ['[rego]'],\n value: {\n 'editor.defaultFormatter': POLICY_VSCODE_EXTENSION,\n 'editor.formatOnSave': true,\n 'editor.insertSpaces': false,\n 'editor.tabSize': 4,\n },\n });\n } else if (\n regoSettings !== null &&\n typeof regoSettings === 'object' &&\n !Array.isArray(regoSettings)\n ) {\n collectSafeScalarUpdates(\n current,\n [\n {\n path: ['[rego]', 'editor.defaultFormatter'],\n value: POLICY_VSCODE_EXTENSION,\n label: '[rego].editor.defaultFormatter',\n },\n {\n path: ['[rego]', 'editor.formatOnSave'],\n value: true,\n label: '[rego].editor.formatOnSave',\n },\n {\n path: ['[rego]', 'editor.insertSpaces'],\n value: false,\n label: '[rego].editor.insertSpaces',\n },\n {\n path: ['[rego]', 'editor.tabSize'],\n value: 4,\n label: '[rego].editor.tabSize',\n },\n ],\n updates,\n warnings,\n );\n } else {\n warnings.push('VS Code language setting \"[rego]\" is customized and was left unchanged.');\n }\n\n return applyEditorUpdates(contents, updates, 'VS Code policy settings', warnings);\n}\n\n/**\n * Merge OPA extension recommendations without disturbing unknown entries.\n *\n * VS Code's `extensions.json` schema supports `unwantedRecommendations`, so\n * the syntax-only extension can be removed from recommendations and discouraged.\n *\n * @param contents - Existing `.vscode/extensions.json` JSONC\n * @returns Merged extension recommendations and warnings\n */\nexport function mergePolicyEditorExtensions(contents: string | null): PolicyEditorMergeResult {\n const parsed = parseEditorObject(contents, 'VS Code policy extension recommendations');\n if ('warning' in parsed) {\n return { contents: parsed.contents, warnings: [parsed.warning] };\n }\n const { current } = parsed;\n const updates: JsoncUpdate[] = [];\n const warnings: string[] = [];\n\n const recommendations = current.recommendations;\n if (\n recommendations === undefined ||\n (Array.isArray(recommendations) && recommendations.every((value) => typeof value === 'string'))\n ) {\n const existing = recommendations ?? [];\n const desired = mergeStringArray(existing, [POLICY_VSCODE_EXTENSION]).filter(\n (value) => value !== CONFLICTING_POLICY_VSCODE_EXTENSION,\n );\n if (!configurationValuesEqual(existing, desired)) {\n updates.push({ path: ['recommendations'], value: desired });\n }\n } else {\n warnings.push(\n 'VS Code extension recommendations use an unknown shape and were left unchanged.',\n );\n }\n\n const unwanted = current.unwantedRecommendations;\n if (\n unwanted === undefined ||\n (Array.isArray(unwanted) && unwanted.every((value) => typeof value === 'string'))\n ) {\n const existing = unwanted ?? [];\n const desired = mergeStringArray(existing, [CONFLICTING_POLICY_VSCODE_EXTENSION]);\n if (!configurationValuesEqual(existing, desired)) {\n updates.push({ path: ['unwantedRecommendations'], value: desired });\n }\n } else {\n warnings.push(\n 'VS Code unwanted extension recommendations use an unknown shape and were left unchanged.',\n );\n }\n\n return applyEditorUpdates(\n contents,\n updates,\n 'VS Code policy extension recommendations',\n warnings,\n );\n}\n\n/**\n * Build a VS Code lint task for one bundle.\n *\n * @param repositoryRoot - Root that owns `.vscode`\n * @param workspaceDirectory - Selected policy workspace directory\n * @param bundle - Package root and local directory basename\n * @returns VS Code task object\n */\nexport function buildPolicyLintTask(\n repositoryRoot: string,\n workspaceDirectory: string,\n bundle: PolicyBundleRef = policyBundleRef(POLICY_STARTER_ROOT),\n): Record<string, unknown> {\n const workspace = repositoryRelativePosix(repositoryRoot, workspaceDirectory);\n const path = workspace === '.' ? bundle.bundleDir : `${workspace}/${bundle.bundleDir}`;\n return {\n label: `policy: lint ${bundle.root}`,\n type: 'process',\n command: 'transcend',\n args: ['policy', 'lint', path, '--noInteractive'],\n group: {\n kind: 'test',\n isDefault: false,\n },\n problemMatcher: [],\n };\n}\n\n/**\n * Build the aggregate \"policy: lint\" task (runs all bundles).\n *\n * @param bundles - Local publish units\n * @returns VS Code task object\n */\nexport function buildPolicyAggregateLintTask(\n bundles: readonly PolicyBundleRef[],\n): Record<string, unknown> {\n return {\n label: 'policy: lint',\n dependsOn: bundles.map((bundle) => `policy: lint ${bundle.root}`),\n dependsOrder: 'sequence',\n group: {\n kind: 'test',\n isDefault: true,\n },\n problemMatcher: [],\n };\n}\n\n/**\n * Merge policy lint tasks while preserving custom tasks.\n *\n * When `bundles` is provided, creates per-bundle tasks and an aggregate.\n * Managed `policy: lint*` tasks are updated in place as roots grow. When\n * omitted, skips task creation (init without bundles).\n *\n * @param contents - Existing `.vscode/tasks.json` JSONC\n * @param repositoryRoot - Root that owns `.vscode`\n * @param workspaceDirectory - Selected policy workspace directory\n * @param bundles - Local publish units to generate tasks for\n * @returns Merged task configuration and warnings\n */\nexport function mergePolicyEditorTasks(\n contents: string | null,\n repositoryRoot: string,\n workspaceDirectory: string,\n bundles?: readonly PolicyBundleRef[],\n): PolicyEditorMergeResult {\n const parsed = parseEditorObject(contents, 'VS Code policy tasks');\n if ('warning' in parsed) {\n return { contents: parsed.contents, warnings: [parsed.warning] };\n }\n const { current } = parsed;\n const updates: JsoncUpdate[] = [];\n const warnings: string[] = [];\n const version = current.version;\n if (version === undefined) {\n updates.push({ path: ['version'], value: '2.0.0' });\n } else if (version !== '2.0.0') {\n warnings.push('VS Code task schema version is customized and was left unchanged.');\n }\n\n if (!bundles || bundles.length === 0) {\n return applyEditorUpdates(contents, updates, 'VS Code policy tasks', warnings);\n }\n\n const desiredPerBundleTasks = bundles.map((bundle) =>\n buildPolicyLintTask(repositoryRoot, workspaceDirectory, bundle),\n );\n const desiredAggregateTask = buildPolicyAggregateLintTask(bundles);\n const desiredTasks = [...desiredPerBundleTasks, desiredAggregateTask];\n const managedLabels = new Set(desiredTasks.map((task) => task.label as string));\n const tasks = current.tasks;\n if (tasks === undefined) {\n updates.push({ path: ['tasks'], value: desiredTasks });\n } else if (Array.isArray(tasks)) {\n let merged = [...tasks];\n let changed = false;\n desiredTasks.forEach((desired) => {\n const label = desired.label as string;\n const matchingIndex = merged.findIndex(\n (task) =>\n task !== null &&\n typeof task === 'object' &&\n !Array.isArray(task) &&\n (task as Record<string, unknown>).label === label,\n );\n if (matchingIndex === -1) {\n merged = [...merged, desired];\n changed = true;\n return;\n }\n if (!configurationValuesEqual(merged[matchingIndex], desired)) {\n if (managedLabels.has(label)) {\n merged = merged.map((task, index) => (index === matchingIndex ? desired : task));\n changed = true;\n } else {\n warnings.push(\n `VS Code task \"${label}\" has repository-specific customization and was left unchanged.`,\n );\n }\n }\n });\n if (changed) {\n updates.push({ path: ['tasks'], value: merged });\n }\n } else {\n warnings.push('VS Code tasks use an unknown shape and were left unchanged.');\n }\n\n return applyEditorUpdates(contents, updates, 'VS Code policy tasks', warnings);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"policy-scaffold-config-CpXR9KgF.mjs","names":[],"sources":["../src/lib/policy/policy-scaffold-artifacts.ts","../src/lib/policy/policy-scaffold-config.ts"],"sourcesContent":["import { posix } from 'node:path';\n\nimport {\n POLICY_STARTER_BUNDLE_DIRECTORY,\n POLICY_STARTER_OPA_VERSION,\n} from './policy-scaffold-templates.js';\n\n/** Regal version pinned by generated policy validation. */\nexport const POLICY_STARTER_REGAL_VERSION = '0.42.0';\n\n/** Repository-relative generated workflow path. */\nexport const POLICY_CI_WORKFLOW_PATH = '.github/workflows/transcend-policy.yml';\n\n/** Immutable actions/checkout v6 release commit. */\nexport const ACTIONS_CHECKOUT_SHA = 'd23441a48e516b6c34aea4fa41551a30e30af803';\n\n/** Immutable open-policy-agent/setup-opa v2.4.0 release commit. */\nexport const SETUP_OPA_SHA = 'b2b258e089860efaadaaf71bf6e3aecb4a3eeff1';\n\n/** Immutable open-policy-agent/setup-regal v2.0.0 release commit. */\nexport const SETUP_REGAL_SHA = '761188c3b435761fa254beca508a44875619648f';\n\n/**\n * Prefix a path below the repository-relative policy workspace.\n *\n * @param workspaceDirectory - POSIX repository-relative workspace\n * @param child - POSIX workspace-relative path or pattern\n * @returns Repository-relative path or pattern\n */\nfunction workspacePath(workspaceDirectory: string, child: string): string {\n return workspaceDirectory === '.' ? child : posix.join(workspaceDirectory, child);\n}\n\n/**\n * Generate credential-free, validation-only Policy Engine CI.\n *\n * Path filters watch the whole policy workspace. Lint runs against each\n * publishable `{root}-bundle/` directory.\n *\n * @param options - Repository paths and exact CLI release\n * @returns Complete GitHub Actions workflow YAML\n */\nexport function generatePolicyGithubActionsWorkflow(options: {\n /** Exact released Transcend CLI version. */\n cliVersion: string;\n /** Policy workspace directory relative to the repository root. */\n workspaceDirectory: string;\n /**\n * Publish directories to lint, relative to the repository root.\n *\n * Pass an empty array for a workspace with no bundles yet (`policy init`).\n * Defaults to the disposable starter bundle under the workspace when omitted.\n */\n bundleDirectories?: readonly string[];\n}): string {\n const bundleDirectories = options.bundleDirectories ?? [\n workspacePath(options.workspaceDirectory, POLICY_STARTER_BUNDLE_DIRECTORY),\n ];\n const watchedPaths = [\n workspacePath(options.workspaceDirectory, '**/*.rego'),\n workspacePath(options.workspaceDirectory, '**/*.json'),\n workspacePath(options.workspaceDirectory, '**/*.yaml'),\n workspacePath(options.workspaceDirectory, '**/*.yml'),\n workspacePath(options.workspaceDirectory, '**/.manifest'),\n workspacePath(options.workspaceDirectory, '.regal/config.yaml'),\n workspacePath(options.workspaceDirectory, '.regal.yaml'),\n POLICY_CI_WORKFLOW_PATH,\n ];\n const pathFilters = watchedPaths.map((path) => ` - ${JSON.stringify(path)}`).join('\\n');\n\n let lintJob: string;\n if (bundleDirectories.length === 0) {\n lintJob = ` lint:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@${ACTIONS_CHECKOUT_SHA} # v6\n - name: No publishable bundles yet\n run: >-\n echo \"Run transcend policy new to add a bundle;\n policy new refreshes this workflow automatically.\"\n`;\n } else if (bundleDirectories.length === 1) {\n lintJob = ` lint:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@${ACTIONS_CHECKOUT_SHA} # v6\n - name: Set up OPA\n uses: open-policy-agent/setup-opa@${SETUP_OPA_SHA} # v2.4.0\n with:\n version: ${JSON.stringify(POLICY_STARTER_OPA_VERSION)}\n - name: Set up Regal\n uses: open-policy-agent/setup-regal@${SETUP_REGAL_SHA} # v2.0.0\n with:\n version: ${JSON.stringify(POLICY_STARTER_REGAL_VERSION)}\n - name: Install the Transcend CLI\n run: npm install --global @transcend-io/cli@${options.cliVersion}\n - name: Validate Policy Engine project\n env:\n POLICY_DIRECTORY: ${JSON.stringify(bundleDirectories[0])}\n run: >-\n transcend policy lint\n \"$POLICY_DIRECTORY\"\n --noInteractive\n --json\n`;\n } else {\n lintJob = ` lint:\n runs-on: ubuntu-latest\n strategy:\n fail-fast: false\n matrix:\n policy_directory:\n${bundleDirectories.map((directory) => ` - ${JSON.stringify(directory)}`).join('\\n')}\n steps:\n - uses: actions/checkout@${ACTIONS_CHECKOUT_SHA} # v6\n - name: Set up OPA\n uses: open-policy-agent/setup-opa@${SETUP_OPA_SHA} # v2.4.0\n with:\n version: ${JSON.stringify(POLICY_STARTER_OPA_VERSION)}\n - name: Set up Regal\n uses: open-policy-agent/setup-regal@${SETUP_REGAL_SHA} # v2.0.0\n with:\n version: ${JSON.stringify(POLICY_STARTER_REGAL_VERSION)}\n - name: Install the Transcend CLI\n run: npm install --global @transcend-io/cli@${options.cliVersion}\n - name: Validate Policy Engine project\n env:\n POLICY_DIRECTORY: \\${{ matrix.policy_directory }}\n run: transcend policy lint \"$POLICY_DIRECTORY\" --noInteractive --json\n`;\n }\n\n return `# Generated by transcend policy init.\nname: Transcend Policy\n\non:\n pull_request:\n paths:\n${pathFilters}\n push:\n paths:\n${pathFilters}\n\npermissions:\n contents: read\n\njobs:\n${lintJob}`;\n}\n","import { relative, sep } from 'node:path';\n\nimport {\n mergeJsonc,\n mergeStringArray,\n parseJsoncObject,\n type JsoncUpdate,\n} from '../scaffolding/jsonc.js';\nimport {\n buildBundleDirectoryName,\n PERMISSIONS_POLICY_INPUT_SCHEMA_ID,\n POLICY_INPUT_SCHEMA_FILENAME,\n POLICY_MANIFEST_FILENAME,\n POLICY_STARTER_ROOT,\n PolicyTemplate,\n type PolicyTemplateName,\n} from './policy-scaffold-templates.js';\n\n/** Recommended VS Code extension for OPA and Regal authoring. */\nexport const POLICY_VSCODE_EXTENSION = 'tsandall.opa';\n\n/** Conflicting syntax-only VS Code extension. */\nexport const CONFLICTING_POLICY_VSCODE_EXTENSION = 'glebbash.opa-highlight-only';\n\n/**\n * Local publish unit: Rego package root plus its workspace-relative folder basename.\n *\n * `bundleDir` is not required to be `{root}-bundle` — `policy new --bundle-dir` may override it.\n */\nexport interface PolicyBundleRef {\n /** Package-path root (`.manifest` / Regal `project.roots`). */\n root: string;\n /** Basename of the publish directory under the policy workspace. */\n bundleDir: string;\n /**\n * Scaffold template that produced this bundle, when known.\n *\n * Permissions bundles point VS Code `json.schemas` at the published input\n * schema `$id` so editor validation tracks the live contract.\n */\n template?: PolicyTemplateName;\n}\n\n/**\n * Build a bundle ref with the default `{root}-bundle` directory.\n *\n * @param root - Package root\n * @param template - Optional scaffold template\n * @returns Bundle ref\n */\nexport function policyBundleRef(root: string, template?: PolicyTemplateName): PolicyBundleRef {\n return template === undefined\n ? { root, bundleDir: buildBundleDirectoryName(root) }\n : { root, bundleDir: buildBundleDirectoryName(root), template };\n}\n\n/** Result of safely merging one repository editor artifact. */\nexport interface PolicyEditorMergeResult {\n /** Complete merged contents, or the unchanged original when unsafe. */\n contents: string;\n /** Actionable preservation warnings. */\n warnings: string[];\n}\n\n/** One desired scalar setting that must not replace a customization. */\ninterface DesiredSetting {\n /** JSONC object path. */\n path: string[];\n /** Desired generated value. */\n value: unknown;\n /** Human-readable setting key. */\n label: string;\n}\n\n/**\n * Compare JSON-compatible configuration values.\n *\n * @param left - Existing value\n * @param right - Desired value\n * @returns Whether the values are structurally equal\n */\nfunction configurationValuesEqual(left: unknown, right: unknown): boolean {\n return JSON.stringify(left) === JSON.stringify(right);\n}\n\n/**\n * Parse an editor object while preserving malformed existing content.\n *\n * @param contents - Existing JSONC, or null\n * @param label - Configuration label\n * @returns Parsed object or a preservation result\n */\nfunction parseEditorObject(\n contents: string | null,\n label: string,\n):\n | {\n /** Parsed JSONC object. */\n current: Record<string, unknown>;\n }\n | {\n /** Complete unchanged contents. */\n contents: string;\n /** Actionable warning. */\n warning: string;\n } {\n try {\n return { current: parseJsoncObject(contents ?? '{}\\n', label) };\n } catch (error) {\n return {\n contents: contents ?? '{}\\n',\n warning: `${error instanceof Error ? error.message : String(error)} Existing content was left unchanged.`,\n };\n }\n}\n\n/**\n * Apply safe JSONC updates or preserve the existing file when comments prevent it.\n *\n * @param contents - Existing JSONC\n * @param updates - Proven-safe updates\n * @param label - Configuration label\n * @param warnings - Existing merge warnings\n * @returns Complete contents and warnings\n */\nfunction applyEditorUpdates(\n contents: string | null,\n updates: readonly JsoncUpdate[],\n label: string,\n warnings: string[],\n): PolicyEditorMergeResult {\n if (updates.length === 0) {\n return { contents: contents ?? '{}\\n', warnings };\n }\n try {\n return {\n contents: mergeJsonc(contents, updates, label),\n warnings,\n };\n } catch (error) {\n return {\n contents: contents ?? '{}\\n',\n warnings: [\n ...warnings,\n `${error instanceof Error ? error.message : String(error)} Existing content was left unchanged.`,\n ],\n };\n }\n}\n\n/**\n * Add absent scalar settings without replacing repository customizations.\n *\n * @param current - Parsed editor object\n * @param desired - Desired settings\n * @param updates - Safe updates to append to\n * @param warnings - Conflict warnings to append to\n */\nfunction collectSafeScalarUpdates(\n current: Record<string, unknown>,\n desired: readonly DesiredSetting[],\n updates: JsoncUpdate[],\n warnings: string[],\n): void {\n desired.forEach(({ path, value, label }) => {\n const existing = path.reduce<unknown>((parent, key) => {\n return parent && typeof parent === 'object' && !Array.isArray(parent)\n ? (parent as Record<string, unknown>)[key]\n : undefined;\n }, current);\n if (existing === undefined) {\n updates.push({ path, value });\n } else if (!configurationValuesEqual(existing, value)) {\n warnings.push(\n `VS Code setting \"${label}\" has a repository-specific value and was left unchanged.`,\n );\n }\n });\n}\n\n/**\n * Build a `${workspaceFolder}/…` path for a repository-relative POSIX path.\n *\n * @param repositoryRelativePath - Path relative to the repository root\n * @returns VS Code workspace path\n */\nfunction buildWorkspaceFolderPath(repositoryRelativePath: string): string {\n return repositoryRelativePath === '.'\n ? '${workspaceFolder}'\n : `\\${workspaceFolder}/${repositoryRelativePath}`;\n}\n\n/**\n * Repository-relative POSIX path from the root that owns `.vscode`.\n *\n * @param repositoryRoot - Root that owns `.vscode`\n * @param absolutePath - Absolute path below the repository\n * @returns POSIX repository-relative path\n */\nfunction repositoryRelativePosix(repositoryRoot: string, absolutePath: string): string {\n return relative(repositoryRoot, absolutePath).split(sep).join('/') || '.';\n}\n\n/**\n * Build the workspace-relative OPA publish root for a bundle.\n *\n * @param repositoryRoot - Root that owns `.vscode`\n * @param workspaceDirectory - Selected policy workspace directory\n * @param bundle - Package root and local directory basename\n * @returns VS Code workspace path\n */\nfunction buildOpaBundleRoot(\n repositoryRoot: string,\n workspaceDirectory: string,\n bundle: PolicyBundleRef = policyBundleRef(POLICY_STARTER_ROOT),\n): string {\n const workspace = repositoryRelativePosix(repositoryRoot, workspaceDirectory);\n const path = workspace === '.' ? bundle.bundleDir : `${workspace}/${bundle.bundleDir}`;\n return buildWorkspaceFolderPath(path);\n}\n\n/**\n * Build a `files.associations` glob for every `.manifest` under the workspace.\n *\n * Patterns that contain `/` are matched against the absolute file path, so a\n * leading `**\\/` prefix is required for a portable workspace-relative association.\n *\n * @param repositoryRoot - Root that owns `.vscode`\n * @param workspaceDirectory - Selected policy workspace directory\n * @returns Association pattern key\n */\nfunction buildPolicyManifestAssociationPattern(\n repositoryRoot: string,\n workspaceDirectory: string,\n): string {\n const workspace = repositoryRelativePosix(repositoryRoot, workspaceDirectory);\n return workspace === '.'\n ? `**/${POLICY_MANIFEST_FILENAME}`\n : `**/${workspace}/**/${POLICY_MANIFEST_FILENAME}`;\n}\n\n/**\n * Build `json.schemas` fileMatch globs for a bundle's input documents.\n *\n * Permissions templates use the published schema `$id` so VS Code validation\n * tracks the live contract. Generic bundles use the local `input.schema.json`\n * (OPA / offline tooling still always use the local file via `--schema`).\n *\n * @param repositoryRoot - Root that owns `.vscode`\n * @param workspaceDirectory - Selected policy workspace directory\n * @param bundle - Package root and local directory basename\n * @returns Absolute-style fileMatch paths and schema URL\n */\nfunction buildBundleInputJsonSchema(\n repositoryRoot: string,\n workspaceDirectory: string,\n bundle: PolicyBundleRef = policyBundleRef(POLICY_STARTER_ROOT),\n): {\n /** VS Code fileMatch globs (leading slash = workspace-relative). */\n fileMatch: string[];\n /** Schema URL (local path or published Permissions `$id`). */\n url: string;\n} {\n const workspace = repositoryRelativePosix(repositoryRoot, workspaceDirectory);\n const bundlePrefix =\n workspace === '.' ? `/${bundle.bundleDir}` : `/${workspace}/${bundle.bundleDir}`;\n const schemaUrl =\n bundle.template === PolicyTemplate.Permissions\n ? PERMISSIONS_POLICY_INPUT_SCHEMA_ID\n : workspace === '.'\n ? `./${bundle.bundleDir}/${POLICY_INPUT_SCHEMA_FILENAME}`\n : `./${workspace}/${bundle.bundleDir}/${POLICY_INPUT_SCHEMA_FILENAME}`;\n return {\n fileMatch: [`${bundlePrefix}/input.json`, `${bundlePrefix}/input.example.json`],\n url: schemaUrl,\n };\n}\n\n/**\n * Merge strict workspace-scoped OPA and Rego editor settings.\n *\n * Setting names match the authoritative `open-policy-agent/vscode-opa`\n * extension metadata. Existing conflicting values are preserved and reported.\n *\n * When `bundles` is provided, `opa.roots` and `json.schemas` are populated\n * for those bundles. When omitted (init without bundles), `opa.roots` and\n * `json.schemas` are left empty or unset.\n *\n * @param contents - Existing `.vscode/settings.json` JSONC\n * @param repositoryRoot - Root that owns `.vscode`\n * @param workspaceDirectory - Selected policy workspace directory\n * @param bundles - Local publish units to add to `opa.roots` and `json.schemas`\n * @returns Merged settings and conflict warnings\n */\nexport function mergePolicyEditorSettings(\n contents: string | null,\n repositoryRoot: string,\n workspaceDirectory: string,\n bundles?: readonly PolicyBundleRef[],\n): PolicyEditorMergeResult {\n const parsed = parseEditorObject(contents, 'VS Code policy settings');\n if ('warning' in parsed) {\n return { contents: parsed.contents, warnings: [parsed.warning] };\n }\n const { current } = parsed;\n const updates: JsoncUpdate[] = [];\n const warnings: string[] = [];\n\n if (bundles && bundles.length > 0) {\n const roots = current['opa.roots'];\n const desiredRoots = bundles.map((bundle) =>\n buildOpaBundleRoot(repositoryRoot, workspaceDirectory, bundle),\n );\n if (roots === undefined) {\n updates.push({ path: ['opa.roots'], value: desiredRoots });\n } else if (Array.isArray(roots) && roots.every((value) => typeof value === 'string')) {\n const merged = mergeStringArray(roots, desiredRoots);\n if (!configurationValuesEqual(merged, roots)) {\n updates.push({ path: ['opa.roots'], value: merged });\n }\n } else {\n warnings.push('VS Code setting \"opa.roots\" is customized and was left unchanged.');\n }\n }\n\n collectSafeScalarUpdates(\n current,\n [\n { path: ['opa.checkOnSave'], value: true, label: 'opa.checkOnSave' },\n { path: ['opa.strictMode'], value: true, label: 'opa.strictMode' },\n { path: ['opa.bundleMode'], value: true, label: 'opa.bundleMode' },\n { path: ['opa.formatter'], value: 'opa-fmt-rego-v1', label: 'opa.formatter' },\n ],\n updates,\n warnings,\n );\n\n const associations = current['files.associations'];\n const manifestAssociation = buildPolicyManifestAssociationPattern(\n repositoryRoot,\n workspaceDirectory,\n );\n if (associations === undefined) {\n updates.push({\n path: ['files.associations'],\n value: { [manifestAssociation]: 'json' },\n });\n } else if (\n associations !== null &&\n typeof associations === 'object' &&\n !Array.isArray(associations)\n ) {\n collectSafeScalarUpdates(\n current,\n [\n {\n path: ['files.associations', manifestAssociation],\n value: 'json',\n label: `files.associations.\"${manifestAssociation}\"`,\n },\n ],\n updates,\n warnings,\n );\n } else {\n warnings.push('VS Code setting \"files.associations\" is customized and was left unchanged.');\n }\n\n if (bundles && bundles.length > 0) {\n const desiredJsonSchemas = bundles.map((bundle) =>\n buildBundleInputJsonSchema(repositoryRoot, workspaceDirectory, bundle),\n );\n const jsonSchemas = current['json.schemas'];\n if (jsonSchemas === undefined) {\n updates.push({ path: ['json.schemas'], value: desiredJsonSchemas });\n } else if (Array.isArray(jsonSchemas)) {\n let merged = [...jsonSchemas];\n desiredJsonSchemas.forEach((desired) => {\n const hasExactMatch = merged.some(\n (entry) =>\n entry !== null &&\n typeof entry === 'object' &&\n !Array.isArray(entry) &&\n configurationValuesEqual(entry, desired),\n );\n if (hasExactMatch) {\n return;\n }\n const sameUrlIndex = merged.findIndex(\n (entry) =>\n entry !== null &&\n typeof entry === 'object' &&\n !Array.isArray(entry) &&\n typeof (entry as Record<string, unknown>).url === 'string' &&\n (entry as Record<string, unknown>).url === desired.url,\n );\n if (sameUrlIndex === -1) {\n merged = [...merged, desired];\n return;\n }\n const existing = merged[sameUrlIndex] as Record<string, unknown>;\n const existingMatches = existing.fileMatch;\n if (\n !Array.isArray(existingMatches) ||\n !existingMatches.every((m) => typeof m === 'string')\n ) {\n warnings.push(\n `VS Code setting \"json.schemas\" has a repository-specific entry for the ${desired.url} schema and was left unchanged.`,\n );\n return;\n }\n const unioned = mergeStringArray(existingMatches, desired.fileMatch);\n if (!configurationValuesEqual(unioned, existingMatches)) {\n merged = merged.map((entry, index) =>\n index === sameUrlIndex ? { ...existing, fileMatch: unioned } : entry,\n );\n }\n });\n if (!configurationValuesEqual(merged, jsonSchemas)) {\n updates.push({ path: ['json.schemas'], value: merged });\n }\n } else {\n warnings.push('VS Code setting \"json.schemas\" is customized and was left unchanged.');\n }\n }\n\n const regoSettings = current['[rego]'];\n if (regoSettings === undefined) {\n updates.push({\n path: ['[rego]'],\n value: {\n 'editor.defaultFormatter': POLICY_VSCODE_EXTENSION,\n 'editor.formatOnSave': true,\n 'editor.insertSpaces': false,\n 'editor.tabSize': 4,\n },\n });\n } else if (\n regoSettings !== null &&\n typeof regoSettings === 'object' &&\n !Array.isArray(regoSettings)\n ) {\n collectSafeScalarUpdates(\n current,\n [\n {\n path: ['[rego]', 'editor.defaultFormatter'],\n value: POLICY_VSCODE_EXTENSION,\n label: '[rego].editor.defaultFormatter',\n },\n {\n path: ['[rego]', 'editor.formatOnSave'],\n value: true,\n label: '[rego].editor.formatOnSave',\n },\n {\n path: ['[rego]', 'editor.insertSpaces'],\n value: false,\n label: '[rego].editor.insertSpaces',\n },\n {\n path: ['[rego]', 'editor.tabSize'],\n value: 4,\n label: '[rego].editor.tabSize',\n },\n ],\n updates,\n warnings,\n );\n } else {\n warnings.push('VS Code language setting \"[rego]\" is customized and was left unchanged.');\n }\n\n return applyEditorUpdates(contents, updates, 'VS Code policy settings', warnings);\n}\n\n/**\n * Merge OPA extension recommendations without disturbing unknown entries.\n *\n * VS Code's `extensions.json` schema supports `unwantedRecommendations`, so\n * the syntax-only extension can be removed from recommendations and discouraged.\n *\n * @param contents - Existing `.vscode/extensions.json` JSONC\n * @returns Merged extension recommendations and warnings\n */\nexport function mergePolicyEditorExtensions(contents: string | null): PolicyEditorMergeResult {\n const parsed = parseEditorObject(contents, 'VS Code policy extension recommendations');\n if ('warning' in parsed) {\n return { contents: parsed.contents, warnings: [parsed.warning] };\n }\n const { current } = parsed;\n const updates: JsoncUpdate[] = [];\n const warnings: string[] = [];\n\n const recommendations = current.recommendations;\n if (\n recommendations === undefined ||\n (Array.isArray(recommendations) && recommendations.every((value) => typeof value === 'string'))\n ) {\n const existing = recommendations ?? [];\n const desired = mergeStringArray(existing, [POLICY_VSCODE_EXTENSION]).filter(\n (value) => value !== CONFLICTING_POLICY_VSCODE_EXTENSION,\n );\n if (!configurationValuesEqual(existing, desired)) {\n updates.push({ path: ['recommendations'], value: desired });\n }\n } else {\n warnings.push(\n 'VS Code extension recommendations use an unknown shape and were left unchanged.',\n );\n }\n\n const unwanted = current.unwantedRecommendations;\n if (\n unwanted === undefined ||\n (Array.isArray(unwanted) && unwanted.every((value) => typeof value === 'string'))\n ) {\n const existing = unwanted ?? [];\n const desired = mergeStringArray(existing, [CONFLICTING_POLICY_VSCODE_EXTENSION]);\n if (!configurationValuesEqual(existing, desired)) {\n updates.push({ path: ['unwantedRecommendations'], value: desired });\n }\n } else {\n warnings.push(\n 'VS Code unwanted extension recommendations use an unknown shape and were left unchanged.',\n );\n }\n\n return applyEditorUpdates(\n contents,\n updates,\n 'VS Code policy extension recommendations',\n warnings,\n );\n}\n\n/**\n * Build a VS Code lint task for one bundle.\n *\n * @param repositoryRoot - Root that owns `.vscode`\n * @param workspaceDirectory - Selected policy workspace directory\n * @param bundle - Package root and local directory basename\n * @returns VS Code task object\n */\nexport function buildPolicyLintTask(\n repositoryRoot: string,\n workspaceDirectory: string,\n bundle: PolicyBundleRef = policyBundleRef(POLICY_STARTER_ROOT),\n): Record<string, unknown> {\n const workspace = repositoryRelativePosix(repositoryRoot, workspaceDirectory);\n const path = workspace === '.' ? bundle.bundleDir : `${workspace}/${bundle.bundleDir}`;\n return {\n label: `policy: lint ${bundle.root}`,\n type: 'process',\n command: 'transcend',\n args: ['policy', 'lint', path, '--noInteractive'],\n group: {\n kind: 'test',\n isDefault: false,\n },\n problemMatcher: [],\n };\n}\n\n/**\n * Build the aggregate \"policy: lint\" task (runs all bundles).\n *\n * @param bundles - Local publish units\n * @returns VS Code task object\n */\nexport function buildPolicyAggregateLintTask(\n bundles: readonly PolicyBundleRef[],\n): Record<string, unknown> {\n return {\n label: 'policy: lint',\n dependsOn: bundles.map((bundle) => `policy: lint ${bundle.root}`),\n dependsOrder: 'sequence',\n group: {\n kind: 'test',\n isDefault: true,\n },\n problemMatcher: [],\n };\n}\n\n/**\n * Merge policy lint tasks while preserving custom tasks.\n *\n * When `bundles` is provided, creates per-bundle tasks and an aggregate.\n * Managed `policy: lint*` tasks are updated in place as roots grow. When\n * omitted, skips task creation (init without bundles).\n *\n * @param contents - Existing `.vscode/tasks.json` JSONC\n * @param repositoryRoot - Root that owns `.vscode`\n * @param workspaceDirectory - Selected policy workspace directory\n * @param bundles - Local publish units to generate tasks for\n * @returns Merged task configuration and warnings\n */\nexport function mergePolicyEditorTasks(\n contents: string | null,\n repositoryRoot: string,\n workspaceDirectory: string,\n bundles?: readonly PolicyBundleRef[],\n): PolicyEditorMergeResult {\n const parsed = parseEditorObject(contents, 'VS Code policy tasks');\n if ('warning' in parsed) {\n return { contents: parsed.contents, warnings: [parsed.warning] };\n }\n const { current } = parsed;\n const updates: JsoncUpdate[] = [];\n const warnings: string[] = [];\n const version = current.version;\n if (version === undefined) {\n updates.push({ path: ['version'], value: '2.0.0' });\n } else if (version !== '2.0.0') {\n warnings.push('VS Code task schema version is customized and was left unchanged.');\n }\n\n if (!bundles || bundles.length === 0) {\n return applyEditorUpdates(contents, updates, 'VS Code policy tasks', warnings);\n }\n\n const desiredPerBundleTasks = bundles.map((bundle) =>\n buildPolicyLintTask(repositoryRoot, workspaceDirectory, bundle),\n );\n const desiredAggregateTask = buildPolicyAggregateLintTask(bundles);\n const desiredTasks = [...desiredPerBundleTasks, desiredAggregateTask];\n const managedLabels = new Set(desiredTasks.map((task) => task.label as string));\n const tasks = current.tasks;\n if (tasks === undefined) {\n updates.push({ path: ['tasks'], value: desiredTasks });\n } else if (Array.isArray(tasks)) {\n let merged = [...tasks];\n let changed = false;\n desiredTasks.forEach((desired) => {\n const label = desired.label as string;\n const matchingIndex = merged.findIndex(\n (task) =>\n task !== null &&\n typeof task === 'object' &&\n !Array.isArray(task) &&\n (task as Record<string, unknown>).label === label,\n );\n if (matchingIndex === -1) {\n merged = [...merged, desired];\n changed = true;\n return;\n }\n if (!configurationValuesEqual(merged[matchingIndex], desired)) {\n if (managedLabels.has(label)) {\n merged = merged.map((task, index) => (index === matchingIndex ? desired : task));\n changed = true;\n } else {\n warnings.push(\n `VS Code task \"${label}\" has repository-specific customization and was left unchanged.`,\n );\n }\n }\n });\n if (changed) {\n updates.push({ path: ['tasks'], value: merged });\n }\n } else {\n warnings.push('VS Code tasks use an unknown shape and were left unchanged.');\n }\n\n return applyEditorUpdates(contents, updates, 'VS Code policy tasks', warnings);\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{a as e}from"./constants-BCmVqlrM.mjs";import{t}from"./logger-CPu6zbyQ.mjs";import n from"colors";import{mapSeries as r}from"@transcend-io/utils";import{decodeCodec as i}from"@transcend-io/type-utils";import*as a from"io-ts";import{REDUCED_REQUESTS_FOR_DATA_SILO_COUNT as o,buildTranscendGraphQLClient as s,createSombraGotInstance as c,makeGraphQLRequest as l,withTransientRetry as u}from"@transcend-io/sdk";import d from"cli-progress";async function f(e,{dataSiloId:n}){let{listReducedRequestsForDataSilo:{totalCount:r}}=await l(e,o,{variables:{input:{dataSiloId:n,isResolved:!1}},logger:t});return r}const p=a.type({id:a.string,name:a.string,partition:a.string}),m=a.intersection([a.type({identifier:a.string,type:a.string,coreIdentifier:a.string,dataSiloId:a.string,requestId:a.string,nonce:a.string,requestCreatedAt:a.string,daysUntilOverdue:a.number,attributes:a.array(a.type({key:a.string,values:a.array(a.string)}))}),a.partial({partition:a.union([p,a.null])})]);async function h(e,{dataSiloId:n,limit:r=100,offset:o=0,requestType:s}){try{let c=await u(`pullCronPageOfIdentifiers`,()=>e.get(`v1/data-silo/${n}/pending-requests/${s}`,{searchParams:{offset:o,limit:r}}).json(),{logger:t,maxAttempts:6,baseDelayMs:500}),{items:l}=i(a.type({items:a.array(m)}),c);return l}catch(e){throw Error(`Received an error from server: ${e?.response?.body||e?.message}`)}}async function g({dataSiloId:i,auth:a,sombraAuth:o,actions:l,apiPageSize:u=100,savePageSize:p=1e3,onSave:m,transcendUrl:g=e,skipRequestCount:_=!1}){if(p%u!==0)throw Error(`savePageSize must be a multiple of apiPageSize. savePageSize: ${p}, apiPageSize: ${u}`);let v=await c(g,a,{logger:t,sombraApiKey:o,sombraUrl:process.env.SOMBRA_URL}),y=s(g,a),b=0;_||(b=await f(y,{dataSiloId:i})),t.info(n.magenta(`Pulling ${_?`all`:b} outstanding request identifiers for data silo: "${i}" for requests of types "${l.join(`", "`)}"`));let x=new Date().getTime(),S=new d.SingleBar({},d.Presets.shades_classic),C=new Set,w=[],T=[];_||S.start(b,0),await r(l,async e=>{let r=0,a=!0;for(;a;){let o=await h(v,{dataSiloId:i,limit:u,offset:r,requestType:e}),s=o.map(t=>(C.add(t.requestId),{...t,action:e})),c=s.map(({attributes:e,partition:t,...n})=>({...n,...t?{partitionId:t.id,partitionName:t.name,partitionKey:t.partition}:{},...e.reduce((e,t)=>Object.assign(e,{[t.key]:t.values.join(`,`)}),{})}));w.push(...s),T.push(...c),T.length>=p&&(await m(T),T=[]),a=o.length===u,r+=u,_?t.info(n.magenta(`Pulled ${o.length} outstanding identifiers for ${C.size} requests`)):S.update(C.size)}}),T.length>0&&await m(T),_||S.stop();let E=new Date().getTime()-x;return t.info(n.green(`Successfully pulled ${w.length} outstanding identifiers from ${C.size} requests in "${E/1e3}" seconds!`)),{identifiers:w}}export{f as a,h as i,m as n,p as r,g as t};
2
- //# sourceMappingURL=pullChunkedCustomSiloOutstandingIdentifiers-BaHp-jxs.mjs.map
1
+ import{a as e}from"./constants-boymUaFC.mjs";import{t}from"./logger-CPu6zbyQ.mjs";import n from"colors";import{mapSeries as r}from"@transcend-io/utils";import{decodeCodec as i}from"@transcend-io/type-utils";import*as a from"io-ts";import{REDUCED_REQUESTS_FOR_DATA_SILO_COUNT as o,buildTranscendGraphQLClient as s,createSombraGotInstance as c,makeGraphQLRequest as l,withTransientRetry as u}from"@transcend-io/sdk";import d from"cli-progress";async function f(e,{dataSiloId:n}){let{listReducedRequestsForDataSilo:{totalCount:r}}=await l(e,o,{variables:{input:{dataSiloId:n,isResolved:!1}},logger:t});return r}const p=a.type({id:a.string,name:a.string,partition:a.string}),m=a.intersection([a.type({identifier:a.string,type:a.string,coreIdentifier:a.string,dataSiloId:a.string,requestId:a.string,nonce:a.string,requestCreatedAt:a.string,daysUntilOverdue:a.number,attributes:a.array(a.type({key:a.string,values:a.array(a.string)}))}),a.partial({partition:a.union([p,a.null])})]);async function h(e,{dataSiloId:n,limit:r=100,offset:o=0,requestType:s}){try{let c=await u(`pullCronPageOfIdentifiers`,()=>e.get(`v1/data-silo/${n}/pending-requests/${s}`,{searchParams:{offset:o,limit:r}}).json(),{logger:t,maxAttempts:6,baseDelayMs:500}),{items:l}=i(a.type({items:a.array(m)}),c);return l}catch(e){throw Error(`Received an error from server: ${e?.response?.body||e?.message}`)}}async function g({dataSiloId:i,auth:a,sombraAuth:o,actions:l,apiPageSize:u=100,savePageSize:p=1e3,onSave:m,transcendUrl:g=e,skipRequestCount:_=!1}){if(p%u!==0)throw Error(`savePageSize must be a multiple of apiPageSize. savePageSize: ${p}, apiPageSize: ${u}`);let v=await c(g,a,{logger:t,sombraApiKey:o,sombraUrl:process.env.SOMBRA_URL}),y=s(g,a),b=0;_||(b=await f(y,{dataSiloId:i})),t.info(n.magenta(`Pulling ${_?`all`:b} outstanding request identifiers for data silo: "${i}" for requests of types "${l.join(`", "`)}"`));let x=new Date().getTime(),S=new d.SingleBar({},d.Presets.shades_classic),C=new Set,w=[],T=[];_||S.start(b,0),await r(l,async e=>{let r=0,a=!0;for(;a;){let o=await h(v,{dataSiloId:i,limit:u,offset:r,requestType:e}),s=o.map(t=>(C.add(t.requestId),{...t,action:e})),c=s.map(({attributes:e,partition:t,...n})=>({...n,...t?{partitionId:t.id,partitionName:t.name,partitionKey:t.partition}:{},...e.reduce((e,t)=>Object.assign(e,{[t.key]:t.values.join(`,`)}),{})}));w.push(...s),T.push(...c),T.length>=p&&(await m(T),T=[]),a=o.length===u,r+=u,_?t.info(n.magenta(`Pulled ${o.length} outstanding identifiers for ${C.size} requests`)):S.update(C.size)}}),T.length>0&&await m(T),_||S.stop();let E=new Date().getTime()-x;return t.info(n.green(`Successfully pulled ${w.length} outstanding identifiers from ${C.size} requests in "${E/1e3}" seconds!`)),{identifiers:w}}export{f as a,h as i,m as n,p as r,g as t};
2
+ //# sourceMappingURL=pullChunkedCustomSiloOutstandingIdentifiers-BcBj9prQ.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"pullChunkedCustomSiloOutstandingIdentifiers-BaHp-jxs.mjs","names":[],"sources":["../src/lib/graphql/fetchRequestDataSiloActiveCount.ts","../src/lib/cron/pullCronPageOfIdentifiers.ts","../src/lib/cron/pullChunkedCustomSiloOutstandingIdentifiers.ts"],"sourcesContent":["import { makeGraphQLRequest, REDUCED_REQUESTS_FOR_DATA_SILO_COUNT } from '@transcend-io/sdk';\nimport { GraphQLClient } from 'graphql-request';\n\nimport { logger } from '../../logger.js';\n\n/**\n * Get number of open requests for a data silo\n *\n * @param client - GraphQL client\n * @param options - Filter options\n * @returns List of request identifiers\n */\nexport async function fetchRequestDataSiloActiveCount(\n client: GraphQLClient,\n {\n dataSiloId,\n }: {\n /** Data silo ID */\n dataSiloId: string;\n },\n): Promise<number> {\n const {\n listReducedRequestsForDataSilo: { totalCount },\n } = await makeGraphQLRequest<{\n /** Requests */\n listReducedRequestsForDataSilo: {\n /** Total count */\n totalCount: number;\n };\n }>(client, REDUCED_REQUESTS_FOR_DATA_SILO_COUNT, {\n variables: {\n input: {\n dataSiloId,\n isResolved: false,\n },\n },\n logger,\n });\n\n return totalCount;\n}\n","import { RequestAction } from '@transcend-io/privacy-types';\nimport { withTransientRetry } from '@transcend-io/sdk';\nimport { decodeCodec } from '@transcend-io/type-utils';\nimport type { Got } from 'got';\nimport * as t from 'io-ts';\n\nimport { logger } from '../../logger.js';\n\n/** Consent partition metadata when a DSR is scoped to a partition */\nexport const CronPartition = t.type({\n /** ID of the partition */\n id: t.string,\n /** The human readable name of the partition */\n name: t.string,\n /** The unique identifying partition value */\n partition: t.string,\n});\n\n/** Type override */\nexport type CronPartition = t.TypeOf<typeof CronPartition>;\n\nexport const CronIdentifier = t.intersection([\n t.type({\n /** The identifier value */\n identifier: t.string,\n /** The type of identifier */\n type: t.string,\n /** The core identifier of the request */\n coreIdentifier: t.string,\n /** The ID of the underlying data silo */\n dataSiloId: t.string,\n /** The ID of the underlying request */\n requestId: t.string,\n /** The request nonce */\n nonce: t.string,\n /** The time the request was created */\n requestCreatedAt: t.string,\n /** The number of days until the request is overdue */\n daysUntilOverdue: t.number,\n /** Request attributes */\n attributes: t.array(\n t.type({\n key: t.string,\n values: t.array(t.string),\n }),\n ),\n }),\n t.partial({\n /** The consent partition that scopes this request, when applicable */\n partition: t.union([CronPartition, t.null]),\n }),\n]);\n\n/** Type override */\nexport type CronIdentifier = t.TypeOf<typeof CronIdentifier>;\n\n/**\n * Pull a offset of identifiers for a cron job\n *\n * @see https://docs.transcend.io/docs/api-reference/GET/v1/data-silo/(id)/pending-requests/(type)\n * @param sombra - Sombra instance configured to make requests\n * @param options - Additional options\n * @returns Successfully submitted request\n */\nexport async function pullCronPageOfIdentifiers(\n sombra: Got,\n {\n dataSiloId,\n limit = 100,\n offset = 0,\n requestType,\n }: {\n /** Data Silo ID */\n dataSiloId: string;\n /** Type of request */\n requestType: RequestAction;\n /** Number of identifiers to pull in */\n limit?: number;\n /** Page to pull in */\n offset?: number;\n },\n): Promise<CronIdentifier[]> {\n try {\n // `GET pending-requests` is a read and therefore safe to retry on transient\n // gateway / network errors. Customers running `transcend cron\n // pull-identifiers` against large silos would otherwise see the command\n // abort on a single 502 from the Sombra reverse tunnel.\n const response = await withTransientRetry(\n 'pullCronPageOfIdentifiers',\n () =>\n sombra\n .get(`v1/data-silo/${dataSiloId}/pending-requests/${requestType}`, {\n searchParams: {\n offset,\n limit,\n },\n })\n .json(),\n { logger, maxAttempts: 6, baseDelayMs: 500 },\n );\n\n const { items } = decodeCodec(\n t.type({\n items: t.array(CronIdentifier),\n }),\n response,\n );\n return items;\n } catch (err) {\n throw new Error(`Received an error from server: ${err?.response?.body || err?.message}`);\n }\n}\n","import { RequestAction } from '@transcend-io/privacy-types';\nimport { buildTranscendGraphQLClient, createSombraGotInstance } from '@transcend-io/sdk';\nimport { mapSeries } from '@transcend-io/utils';\nimport cliProgress from 'cli-progress';\nimport colors from 'colors';\n\nimport { DEFAULT_TRANSCEND_API } from '../../constants.js';\nimport { logger } from '../../logger.js';\nimport { fetchRequestDataSiloActiveCount } from '../graphql/index.js';\nimport { pullCronPageOfIdentifiers, CronIdentifier } from './pullCronPageOfIdentifiers.js';\n\n/**\n * A CSV formatted identifier\n */\nexport type CsvFormattedIdentifier = {\n [k in string]: string | null | boolean | number;\n};\n\nexport interface CronIdentifierWithAction extends CronIdentifier {\n /** The request action that the identifier relates to */\n action: RequestAction;\n}\n\n/**\n * Pull the set of identifiers outstanding for a cron or AVC integration\n *\n * This function is designed to be used in a loop, and will call the onSave callback\n * with a chunk of identifiers when the savePageSize is reached.\n *\n * @param options - Options\n * @returns The identifiers and identifiers formatted for CSV\n */\nexport async function pullChunkedCustomSiloOutstandingIdentifiers({\n dataSiloId,\n auth,\n sombraAuth,\n actions,\n apiPageSize = 100,\n savePageSize = 1000,\n onSave,\n transcendUrl = DEFAULT_TRANSCEND_API,\n skipRequestCount = false,\n}: {\n /** Transcend API key authentication */\n auth: string;\n /** Data Silo ID to pull down jobs for */\n dataSiloId: string;\n /** The request actions to fetch */\n actions: RequestAction[];\n /** How many identifiers to pull in a single call to the backend */\n apiPageSize: number;\n /** How many identifiers to save at a time (usually to a CSV file, should be a multiple of apiPageSize) */\n savePageSize: number;\n /** Callback function called when a chunk of identifiers is ready to be saved */\n onSave: (chunk: CsvFormattedIdentifier[]) => Promise<void>;\n /** API URL for Transcend backend */\n transcendUrl?: string;\n /** Sombra API key authentication */\n sombraAuth?: string;\n /** Skip request count */\n skipRequestCount?: boolean;\n}): Promise<{\n /** Raw Identifiers */\n identifiers: CronIdentifierWithAction[];\n}> {\n // Validate savePageSize\n if (savePageSize % apiPageSize !== 0) {\n throw new Error(\n `savePageSize must be a multiple of apiPageSize. savePageSize: ${savePageSize}, apiPageSize: ${apiPageSize}`,\n );\n }\n\n // Create sombra instance to communicate with\n const sombra = await createSombraGotInstance(transcendUrl, auth, {\n logger,\n sombraApiKey: sombraAuth,\n sombraUrl: process.env.SOMBRA_URL,\n });\n\n // Create GraphQL client to connect to Transcend backend\n const client = buildTranscendGraphQLClient(transcendUrl, auth);\n\n let totalRequestCount = 0;\n if (!skipRequestCount) {\n totalRequestCount = await fetchRequestDataSiloActiveCount(client, {\n dataSiloId,\n });\n }\n\n logger.info(\n colors.magenta(\n `Pulling ${skipRequestCount ? 'all' : totalRequestCount} outstanding request identifiers ` +\n `for data silo: \"${dataSiloId}\" for requests of types \"${actions.join('\", \"')}\"`,\n ),\n );\n\n // Time duration\n const t0 = new Date().getTime();\n // create a new progress bar instance and use shades_classic theme\n const progressBar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic);\n const foundRequestIds = new Set<string>();\n\n // identifiers found in total\n const identifiers: CronIdentifierWithAction[] = [];\n // current chunk of identifiers to be saved\n let currentChunk: CsvFormattedIdentifier[] = [];\n\n // map over each action\n if (!skipRequestCount) {\n progressBar.start(totalRequestCount, 0);\n }\n await mapSeries(actions, async (action) => {\n let offset = 0;\n let shouldContinue = true;\n\n // Fetch a page of identifiers\n while (shouldContinue) {\n const pageIdentifiers = await pullCronPageOfIdentifiers(sombra, {\n dataSiloId,\n limit: apiPageSize,\n offset,\n requestType: action,\n });\n\n const identifiersWithAction: CronIdentifierWithAction[] = pageIdentifiers.map(\n (identifier) => {\n foundRequestIds.add(identifier.requestId);\n return {\n ...identifier,\n action,\n };\n },\n );\n\n const csvFormattedIdentifiers = identifiersWithAction.map(\n ({ attributes, partition, ...identifier }) => ({\n ...identifier,\n ...(partition\n ? {\n partitionId: partition.id,\n partitionName: partition.name,\n partitionKey: partition.partition,\n }\n : {}),\n ...attributes.reduce(\n (acc, val) =>\n Object.assign(acc, {\n [val.key]: val.values.join(','),\n }),\n {},\n ),\n }),\n );\n\n identifiers.push(...identifiersWithAction);\n currentChunk.push(...csvFormattedIdentifiers);\n\n // Check if we've reached the savePageSize and call the onSave callback\n if (currentChunk.length >= savePageSize) {\n await onSave(currentChunk);\n currentChunk = [];\n }\n\n shouldContinue = pageIdentifiers.length === apiPageSize;\n offset += apiPageSize;\n if (!skipRequestCount) {\n progressBar.update(foundRequestIds.size);\n } else {\n logger.info(\n colors.magenta(\n `Pulled ${pageIdentifiers.length} outstanding identifiers for ${foundRequestIds.size} requests`,\n ),\n );\n }\n }\n });\n\n // Save any remaining identifiers in the current chunk\n if (currentChunk.length > 0) {\n await onSave(currentChunk);\n }\n\n if (!skipRequestCount) {\n progressBar.stop();\n }\n const t1 = new Date().getTime();\n const totalTime = t1 - t0;\n\n logger.info(\n colors.green(\n `Successfully pulled ${identifiers.length} outstanding identifiers from ${\n foundRequestIds.size\n } requests in \"${totalTime / 1000}\" seconds!`,\n ),\n );\n\n return { identifiers };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"pullChunkedCustomSiloOutstandingIdentifiers-BcBj9prQ.mjs","names":[],"sources":["../src/lib/graphql/fetchRequestDataSiloActiveCount.ts","../src/lib/cron/pullCronPageOfIdentifiers.ts","../src/lib/cron/pullChunkedCustomSiloOutstandingIdentifiers.ts"],"sourcesContent":["import { makeGraphQLRequest, REDUCED_REQUESTS_FOR_DATA_SILO_COUNT } from '@transcend-io/sdk';\nimport { GraphQLClient } from 'graphql-request';\n\nimport { logger } from '../../logger.js';\n\n/**\n * Get number of open requests for a data silo\n *\n * @param client - GraphQL client\n * @param options - Filter options\n * @returns List of request identifiers\n */\nexport async function fetchRequestDataSiloActiveCount(\n client: GraphQLClient,\n {\n dataSiloId,\n }: {\n /** Data silo ID */\n dataSiloId: string;\n },\n): Promise<number> {\n const {\n listReducedRequestsForDataSilo: { totalCount },\n } = await makeGraphQLRequest<{\n /** Requests */\n listReducedRequestsForDataSilo: {\n /** Total count */\n totalCount: number;\n };\n }>(client, REDUCED_REQUESTS_FOR_DATA_SILO_COUNT, {\n variables: {\n input: {\n dataSiloId,\n isResolved: false,\n },\n },\n logger,\n });\n\n return totalCount;\n}\n","import { RequestAction } from '@transcend-io/privacy-types';\nimport { withTransientRetry } from '@transcend-io/sdk';\nimport { decodeCodec } from '@transcend-io/type-utils';\nimport type { Got } from 'got';\nimport * as t from 'io-ts';\n\nimport { logger } from '../../logger.js';\n\n/** Consent partition metadata when a DSR is scoped to a partition */\nexport const CronPartition = t.type({\n /** ID of the partition */\n id: t.string,\n /** The human readable name of the partition */\n name: t.string,\n /** The unique identifying partition value */\n partition: t.string,\n});\n\n/** Type override */\nexport type CronPartition = t.TypeOf<typeof CronPartition>;\n\nexport const CronIdentifier = t.intersection([\n t.type({\n /** The identifier value */\n identifier: t.string,\n /** The type of identifier */\n type: t.string,\n /** The core identifier of the request */\n coreIdentifier: t.string,\n /** The ID of the underlying data silo */\n dataSiloId: t.string,\n /** The ID of the underlying request */\n requestId: t.string,\n /** The request nonce */\n nonce: t.string,\n /** The time the request was created */\n requestCreatedAt: t.string,\n /** The number of days until the request is overdue */\n daysUntilOverdue: t.number,\n /** Request attributes */\n attributes: t.array(\n t.type({\n key: t.string,\n values: t.array(t.string),\n }),\n ),\n }),\n t.partial({\n /** The consent partition that scopes this request, when applicable */\n partition: t.union([CronPartition, t.null]),\n }),\n]);\n\n/** Type override */\nexport type CronIdentifier = t.TypeOf<typeof CronIdentifier>;\n\n/**\n * Pull a offset of identifiers for a cron job\n *\n * @see https://docs.transcend.io/docs/api-reference/GET/v1/data-silo/(id)/pending-requests/(type)\n * @param sombra - Sombra instance configured to make requests\n * @param options - Additional options\n * @returns Successfully submitted request\n */\nexport async function pullCronPageOfIdentifiers(\n sombra: Got,\n {\n dataSiloId,\n limit = 100,\n offset = 0,\n requestType,\n }: {\n /** Data Silo ID */\n dataSiloId: string;\n /** Type of request */\n requestType: RequestAction;\n /** Number of identifiers to pull in */\n limit?: number;\n /** Page to pull in */\n offset?: number;\n },\n): Promise<CronIdentifier[]> {\n try {\n // `GET pending-requests` is a read and therefore safe to retry on transient\n // gateway / network errors. Customers running `transcend cron\n // pull-identifiers` against large silos would otherwise see the command\n // abort on a single 502 from the Sombra reverse tunnel.\n const response = await withTransientRetry(\n 'pullCronPageOfIdentifiers',\n () =>\n sombra\n .get(`v1/data-silo/${dataSiloId}/pending-requests/${requestType}`, {\n searchParams: {\n offset,\n limit,\n },\n })\n .json(),\n { logger, maxAttempts: 6, baseDelayMs: 500 },\n );\n\n const { items } = decodeCodec(\n t.type({\n items: t.array(CronIdentifier),\n }),\n response,\n );\n return items;\n } catch (err) {\n throw new Error(`Received an error from server: ${err?.response?.body || err?.message}`);\n }\n}\n","import { RequestAction } from '@transcend-io/privacy-types';\nimport { buildTranscendGraphQLClient, createSombraGotInstance } from '@transcend-io/sdk';\nimport { mapSeries } from '@transcend-io/utils';\nimport cliProgress from 'cli-progress';\nimport colors from 'colors';\n\nimport { DEFAULT_TRANSCEND_API } from '../../constants.js';\nimport { logger } from '../../logger.js';\nimport { fetchRequestDataSiloActiveCount } from '../graphql/index.js';\nimport { pullCronPageOfIdentifiers, CronIdentifier } from './pullCronPageOfIdentifiers.js';\n\n/**\n * A CSV formatted identifier\n */\nexport type CsvFormattedIdentifier = {\n [k in string]: string | null | boolean | number;\n};\n\nexport interface CronIdentifierWithAction extends CronIdentifier {\n /** The request action that the identifier relates to */\n action: RequestAction;\n}\n\n/**\n * Pull the set of identifiers outstanding for a cron or AVC integration\n *\n * This function is designed to be used in a loop, and will call the onSave callback\n * with a chunk of identifiers when the savePageSize is reached.\n *\n * @param options - Options\n * @returns The identifiers and identifiers formatted for CSV\n */\nexport async function pullChunkedCustomSiloOutstandingIdentifiers({\n dataSiloId,\n auth,\n sombraAuth,\n actions,\n apiPageSize = 100,\n savePageSize = 1000,\n onSave,\n transcendUrl = DEFAULT_TRANSCEND_API,\n skipRequestCount = false,\n}: {\n /** Transcend API key authentication */\n auth: string;\n /** Data Silo ID to pull down jobs for */\n dataSiloId: string;\n /** The request actions to fetch */\n actions: RequestAction[];\n /** How many identifiers to pull in a single call to the backend */\n apiPageSize: number;\n /** How many identifiers to save at a time (usually to a CSV file, should be a multiple of apiPageSize) */\n savePageSize: number;\n /** Callback function called when a chunk of identifiers is ready to be saved */\n onSave: (chunk: CsvFormattedIdentifier[]) => Promise<void>;\n /** API URL for Transcend backend */\n transcendUrl?: string;\n /** Sombra API key authentication */\n sombraAuth?: string;\n /** Skip request count */\n skipRequestCount?: boolean;\n}): Promise<{\n /** Raw Identifiers */\n identifiers: CronIdentifierWithAction[];\n}> {\n // Validate savePageSize\n if (savePageSize % apiPageSize !== 0) {\n throw new Error(\n `savePageSize must be a multiple of apiPageSize. savePageSize: ${savePageSize}, apiPageSize: ${apiPageSize}`,\n );\n }\n\n // Create sombra instance to communicate with\n const sombra = await createSombraGotInstance(transcendUrl, auth, {\n logger,\n sombraApiKey: sombraAuth,\n sombraUrl: process.env.SOMBRA_URL,\n });\n\n // Create GraphQL client to connect to Transcend backend\n const client = buildTranscendGraphQLClient(transcendUrl, auth);\n\n let totalRequestCount = 0;\n if (!skipRequestCount) {\n totalRequestCount = await fetchRequestDataSiloActiveCount(client, {\n dataSiloId,\n });\n }\n\n logger.info(\n colors.magenta(\n `Pulling ${skipRequestCount ? 'all' : totalRequestCount} outstanding request identifiers ` +\n `for data silo: \"${dataSiloId}\" for requests of types \"${actions.join('\", \"')}\"`,\n ),\n );\n\n // Time duration\n const t0 = new Date().getTime();\n // create a new progress bar instance and use shades_classic theme\n const progressBar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic);\n const foundRequestIds = new Set<string>();\n\n // identifiers found in total\n const identifiers: CronIdentifierWithAction[] = [];\n // current chunk of identifiers to be saved\n let currentChunk: CsvFormattedIdentifier[] = [];\n\n // map over each action\n if (!skipRequestCount) {\n progressBar.start(totalRequestCount, 0);\n }\n await mapSeries(actions, async (action) => {\n let offset = 0;\n let shouldContinue = true;\n\n // Fetch a page of identifiers\n while (shouldContinue) {\n const pageIdentifiers = await pullCronPageOfIdentifiers(sombra, {\n dataSiloId,\n limit: apiPageSize,\n offset,\n requestType: action,\n });\n\n const identifiersWithAction: CronIdentifierWithAction[] = pageIdentifiers.map(\n (identifier) => {\n foundRequestIds.add(identifier.requestId);\n return {\n ...identifier,\n action,\n };\n },\n );\n\n const csvFormattedIdentifiers = identifiersWithAction.map(\n ({ attributes, partition, ...identifier }) => ({\n ...identifier,\n ...(partition\n ? {\n partitionId: partition.id,\n partitionName: partition.name,\n partitionKey: partition.partition,\n }\n : {}),\n ...attributes.reduce(\n (acc, val) =>\n Object.assign(acc, {\n [val.key]: val.values.join(','),\n }),\n {},\n ),\n }),\n );\n\n identifiers.push(...identifiersWithAction);\n currentChunk.push(...csvFormattedIdentifiers);\n\n // Check if we've reached the savePageSize and call the onSave callback\n if (currentChunk.length >= savePageSize) {\n await onSave(currentChunk);\n currentChunk = [];\n }\n\n shouldContinue = pageIdentifiers.length === apiPageSize;\n offset += apiPageSize;\n if (!skipRequestCount) {\n progressBar.update(foundRequestIds.size);\n } else {\n logger.info(\n colors.magenta(\n `Pulled ${pageIdentifiers.length} outstanding identifiers for ${foundRequestIds.size} requests`,\n ),\n );\n }\n }\n });\n\n // Save any remaining identifiers in the current chunk\n if (currentChunk.length > 0) {\n await onSave(currentChunk);\n }\n\n if (!skipRequestCount) {\n progressBar.stop();\n }\n const t1 = new Date().getTime();\n const totalTime = t1 - t0;\n\n logger.info(\n colors.green(\n `Successfully pulled ${identifiers.length} outstanding identifiers from ${\n foundRequestIds.size\n } requests in \"${totalTime / 1000}\" seconds!`,\n ),\n );\n\n return { identifiers };\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{a as e}from"./constants-BCmVqlrM.mjs";import{t}from"./logger-CPu6zbyQ.mjs";import{r as n}from"./fetchAllRequests-BI0tFl_a.mjs";import{a as r}from"./writeCsv-CNZrq3zU.mjs";import{RequestStatus as i}from"@transcend-io/privacy-types";import{groupBy as a,uniq as o}from"lodash-es";import s from"colors";import{map as c}from"@transcend-io/utils";import{buildTranscendGraphQLClient as l,createSombraGotInstance as u,fetchAllRequestEnrichers as d,fetchAllRequestIdentifiers as f,validateSombraVersion as p}from"@transcend-io/sdk";async function m({file:m,auth:h,sombraAuth:g,requestActions:_=[],concurrency:v=100,transcendUrl:y=e}){let b=l(y,h),x=await u(y,h,{logger:t,sombraApiKey:g,sombraUrl:process.env.SOMBRA_URL});t.info(s.magenta(`Pulling manual enrichment requests, filtered for actions: ${_.join(`,`)}`));let S=await n(b,{actions:_,statuses:[i.Enriching]});await p(b,{logger:t});let C=[];await c(S,async e=>{let n=await d(b,{filterBy:{requestId:e.id},logger:t});if(n.filter(({status:e})=>e===`ACTION_REQUIRED`)){let r=await f(b,x,{filterBy:{requestId:e.id},skipSombraCheck:!0,logger:t});C.push({...e,requestIdentifiers:r,requestEnrichers:n})}},{concurrency:v});let w=C.map(({attributeValues:e,requestIdentifiers:t,requestEnrichers:n,...r})=>({...r,...Object.entries(a(t,`name`)).reduce((e,[t,n])=>Object.assign(e,{[t]:n.map(({value:e})=>e).join(`,`)}),{}),...Object.entries(a(e,`attributeKey.name`)).reduce((e,[t,n])=>Object.assign(e,{[t]:n.map(({name:e})=>e).join(`,`)}),{})}));return await r(m,w,o(w.map(e=>Object.keys(e)).flat())),t.info(s.green(`Successfully wrote ${C.length} requests to file "${m}"`)),C}export{m as t};
2
- //# sourceMappingURL=pullManualEnrichmentIdentifiersToCsv-CiEFIvl_.mjs.map
1
+ import{a as e}from"./constants-boymUaFC.mjs";import{t}from"./logger-CPu6zbyQ.mjs";import{r as n}from"./fetchAllRequests-BI0tFl_a.mjs";import{a as r}from"./writeCsv-CNZrq3zU.mjs";import{RequestStatus as i}from"@transcend-io/privacy-types";import{groupBy as a,uniq as o}from"lodash-es";import s from"colors";import{map as c}from"@transcend-io/utils";import{buildTranscendGraphQLClient as l,createSombraGotInstance as u,fetchAllRequestEnrichers as d,fetchAllRequestIdentifiers as f,validateSombraVersion as p}from"@transcend-io/sdk";async function m({file:m,auth:h,sombraAuth:g,requestActions:_=[],concurrency:v=100,transcendUrl:y=e}){let b=l(y,h),x=await u(y,h,{logger:t,sombraApiKey:g,sombraUrl:process.env.SOMBRA_URL});t.info(s.magenta(`Pulling manual enrichment requests, filtered for actions: ${_.join(`,`)}`));let S=await n(b,{actions:_,statuses:[i.Enriching]});await p(b,{logger:t});let C=[];await c(S,async e=>{let n=await d(b,{filterBy:{requestId:e.id},logger:t});if(n.filter(({status:e})=>e===`ACTION_REQUIRED`)){let r=await f(b,x,{filterBy:{requestId:e.id},skipSombraCheck:!0,logger:t});C.push({...e,requestIdentifiers:r,requestEnrichers:n})}},{concurrency:v});let w=C.map(({attributeValues:e,requestIdentifiers:t,requestEnrichers:n,...r})=>({...r,...Object.entries(a(t,`name`)).reduce((e,[t,n])=>Object.assign(e,{[t]:n.map(({value:e})=>e).join(`,`)}),{}),...Object.entries(a(e,`attributeKey.name`)).reduce((e,[t,n])=>Object.assign(e,{[t]:n.map(({name:e})=>e).join(`,`)}),{})}));return await r(m,w,o(w.map(e=>Object.keys(e)).flat())),t.info(s.green(`Successfully wrote ${C.length} requests to file "${m}"`)),C}export{m as t};
2
+ //# sourceMappingURL=pullManualEnrichmentIdentifiersToCsv-BSUXUp8v.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"pullManualEnrichmentIdentifiersToCsv-CiEFIvl_.mjs","names":[],"sources":["../src/lib/manual-enrichment/pullManualEnrichmentIdentifiersToCsv.ts"],"sourcesContent":["import { RequestAction, RequestStatus } from '@transcend-io/privacy-types';\nimport {\n buildTranscendGraphQLClient,\n createSombraGotInstance,\n fetchAllRequestEnrichers,\n fetchAllRequestIdentifiers,\n validateSombraVersion,\n type RequestEnricher,\n type RequestIdentifier,\n} from '@transcend-io/sdk';\nimport { map } from '@transcend-io/utils';\nimport colors from 'colors';\nimport { groupBy, uniq } from 'lodash-es';\n\nimport { DEFAULT_TRANSCEND_API } from '../../constants.js';\nimport { logger } from '../../logger.js';\nimport { type PrivacyRequest, fetchAllRequests } from '../graphql/index.js';\nimport { writeCsv } from '../helpers/writeCsv.js';\n\nexport interface PrivacyRequestWithIdentifiers extends PrivacyRequest {\n /** Request Enrichers */\n requestEnrichers: RequestEnricher[];\n /** Request Identifiers */\n requestIdentifiers: RequestIdentifier[];\n}\n\n/**\n * Pull the set of manual enrichment jobs to CSV\n *\n * @param options - Options\n * @returns List of requests with identifiers\n */\nexport async function pullManualEnrichmentIdentifiersToCsv({\n file,\n auth,\n sombraAuth,\n requestActions = [],\n concurrency = 100,\n transcendUrl = DEFAULT_TRANSCEND_API,\n}: {\n /** CSV file path */\n file: string;\n /** Transcend API key authentication */\n auth: string;\n /** Sombra API key */\n sombraAuth?: string;\n /** Concurrency */\n concurrency?: number;\n /** The request actions to fetch */\n requestActions?: RequestAction[];\n /** API URL for Transcend backend */\n transcendUrl?: string;\n}): Promise<PrivacyRequestWithIdentifiers[]> {\n // Find all requests made before createdAt that are in a removing data state\n const client = buildTranscendGraphQLClient(transcendUrl, auth);\n const sombra = await createSombraGotInstance(transcendUrl, auth, {\n logger,\n sombraApiKey: sombraAuth,\n sombraUrl: process.env.SOMBRA_URL,\n });\n\n logger.info(\n colors.magenta(\n `Pulling manual enrichment requests, filtered for actions: ${requestActions.join(',')}`,\n ),\n );\n\n // Pull all privacy requests\n const allRequests = await fetchAllRequests(client, {\n actions: requestActions,\n statuses: [RequestStatus.Enriching],\n });\n\n await validateSombraVersion(client, { logger });\n\n // Requests to save\n const savedRequests: PrivacyRequestWithIdentifiers[] = [];\n\n // Filter down requests to what is needed\n await map(\n allRequests,\n async (request) => {\n // Fetch enrichers\n const requestEnrichers = await fetchAllRequestEnrichers(client, {\n filterBy: { requestId: request.id },\n logger,\n });\n\n // Check if manual enrichment exists for that request\n const hasManualEnrichment = requestEnrichers.filter(\n ({ status }) => status === 'ACTION_REQUIRED',\n );\n\n // Save request to queue\n if (hasManualEnrichment) {\n const requestIdentifiers = await fetchAllRequestIdentifiers(client, sombra, {\n filterBy: { requestId: request.id },\n skipSombraCheck: true,\n logger,\n });\n savedRequests.push({\n ...request,\n requestIdentifiers,\n requestEnrichers,\n });\n }\n },\n {\n concurrency,\n },\n );\n\n const data = savedRequests.map(\n ({\n attributeValues,\n requestIdentifiers,\n requestEnrichers, // eslint-disable-line @typescript-eslint/no-unused-vars\n ...request\n }) => ({\n ...request,\n // flatten identifiers\n ...Object.entries(groupBy(requestIdentifiers, 'name')).reduce(\n (acc, [key, values]) =>\n Object.assign(acc, {\n [key]: values.map(({ value }) => value).join(','),\n }),\n {},\n ),\n // flatten attributes\n ...Object.entries(groupBy(attributeValues, 'attributeKey.name')).reduce(\n (acc, [key, values]) =>\n Object.assign(acc, {\n [key]: values.map(({ name }) => name).join(','),\n }),\n {},\n ),\n }),\n );\n\n // Write out to CSV\n const headers = uniq(data.map((d) => Object.keys(d)).flat());\n await writeCsv(file, data, headers);\n\n logger.info(\n colors.green(`Successfully wrote ${savedRequests.length} requests to file \"${file}\"`),\n );\n\n return savedRequests;\n}\n"],"mappings":"khBAgCA,eAAsB,EAAqC,CACzD,OACA,OACA,aACA,iBAAiB,EAAE,CACnB,cAAc,IACd,eAAe,GAc4B,CAE3C,IAAM,EAAS,EAA4B,EAAc,EAAK,CACxD,EAAS,MAAM,EAAwB,EAAc,EAAM,CAC/D,SACA,aAAc,EACd,UAAW,QAAQ,IAAI,WACxB,CAAC,CAEF,EAAO,KACL,EAAO,QACL,6DAA6D,EAAe,KAAK,IAAI,GACtF,CACF,CAGD,IAAM,EAAc,MAAM,EAAiB,EAAQ,CACjD,QAAS,EACT,SAAU,CAAC,EAAc,UAAU,CACpC,CAAC,CAEF,MAAM,EAAsB,EAAQ,CAAE,SAAQ,CAAC,CAG/C,IAAM,EAAiD,EAAE,CAGzD,MAAM,EACJ,EACA,KAAO,IAAY,CAEjB,IAAM,EAAmB,MAAM,EAAyB,EAAQ,CAC9D,SAAU,CAAE,UAAW,EAAQ,GAAI,CACnC,SACD,CAAC,CAQF,GAL4B,EAAiB,QAC1C,CAAE,YAAa,IAAW,kBAIN,CAAE,CACvB,IAAM,EAAqB,MAAM,EAA2B,EAAQ,EAAQ,CAC1E,SAAU,CAAE,UAAW,EAAQ,GAAI,CACnC,gBAAiB,GACjB,SACD,CAAC,CACF,EAAc,KAAK,CACjB,GAAG,EACH,qBACA,mBACD,CAAC,GAGN,CACE,cACD,CACF,CAED,IAAM,EAAO,EAAc,KACxB,CACC,kBACA,qBACA,mBACA,GAAG,MACE,CACL,GAAG,EAEH,GAAG,OAAO,QAAQ,EAAQ,EAAoB,OAAO,CAAC,CAAC,QACpD,EAAK,CAAC,EAAK,KACV,OAAO,OAAO,EAAK,EAChB,GAAM,EAAO,KAAK,CAAE,WAAY,EAAM,CAAC,KAAK,IAAI,CAClD,CAAC,CACJ,EAAE,CACH,CAED,GAAG,OAAO,QAAQ,EAAQ,EAAiB,oBAAoB,CAAC,CAAC,QAC9D,EAAK,CAAC,EAAK,KACV,OAAO,OAAO,EAAK,EAChB,GAAM,EAAO,KAAK,CAAE,UAAW,EAAK,CAAC,KAAK,IAAI,CAChD,CAAC,CACJ,EAAE,CACH,CACF,EACF,CAUD,OANA,MAAM,EAAS,EAAM,EADL,EAAK,EAAK,IAAK,GAAM,OAAO,KAAK,EAAE,CAAC,CAAC,MAAM,CACzB,CAAC,CAEnC,EAAO,KACL,EAAO,MAAM,sBAAsB,EAAc,OAAO,qBAAqB,EAAK,GAAG,CACtF,CAEM"}
1
+ {"version":3,"file":"pullManualEnrichmentIdentifiersToCsv-BSUXUp8v.mjs","names":[],"sources":["../src/lib/manual-enrichment/pullManualEnrichmentIdentifiersToCsv.ts"],"sourcesContent":["import { RequestAction, RequestStatus } from '@transcend-io/privacy-types';\nimport {\n buildTranscendGraphQLClient,\n createSombraGotInstance,\n fetchAllRequestEnrichers,\n fetchAllRequestIdentifiers,\n validateSombraVersion,\n type RequestEnricher,\n type RequestIdentifier,\n} from '@transcend-io/sdk';\nimport { map } from '@transcend-io/utils';\nimport colors from 'colors';\nimport { groupBy, uniq } from 'lodash-es';\n\nimport { DEFAULT_TRANSCEND_API } from '../../constants.js';\nimport { logger } from '../../logger.js';\nimport { type PrivacyRequest, fetchAllRequests } from '../graphql/index.js';\nimport { writeCsv } from '../helpers/writeCsv.js';\n\nexport interface PrivacyRequestWithIdentifiers extends PrivacyRequest {\n /** Request Enrichers */\n requestEnrichers: RequestEnricher[];\n /** Request Identifiers */\n requestIdentifiers: RequestIdentifier[];\n}\n\n/**\n * Pull the set of manual enrichment jobs to CSV\n *\n * @param options - Options\n * @returns List of requests with identifiers\n */\nexport async function pullManualEnrichmentIdentifiersToCsv({\n file,\n auth,\n sombraAuth,\n requestActions = [],\n concurrency = 100,\n transcendUrl = DEFAULT_TRANSCEND_API,\n}: {\n /** CSV file path */\n file: string;\n /** Transcend API key authentication */\n auth: string;\n /** Sombra API key */\n sombraAuth?: string;\n /** Concurrency */\n concurrency?: number;\n /** The request actions to fetch */\n requestActions?: RequestAction[];\n /** API URL for Transcend backend */\n transcendUrl?: string;\n}): Promise<PrivacyRequestWithIdentifiers[]> {\n // Find all requests made before createdAt that are in a removing data state\n const client = buildTranscendGraphQLClient(transcendUrl, auth);\n const sombra = await createSombraGotInstance(transcendUrl, auth, {\n logger,\n sombraApiKey: sombraAuth,\n sombraUrl: process.env.SOMBRA_URL,\n });\n\n logger.info(\n colors.magenta(\n `Pulling manual enrichment requests, filtered for actions: ${requestActions.join(',')}`,\n ),\n );\n\n // Pull all privacy requests\n const allRequests = await fetchAllRequests(client, {\n actions: requestActions,\n statuses: [RequestStatus.Enriching],\n });\n\n await validateSombraVersion(client, { logger });\n\n // Requests to save\n const savedRequests: PrivacyRequestWithIdentifiers[] = [];\n\n // Filter down requests to what is needed\n await map(\n allRequests,\n async (request) => {\n // Fetch enrichers\n const requestEnrichers = await fetchAllRequestEnrichers(client, {\n filterBy: { requestId: request.id },\n logger,\n });\n\n // Check if manual enrichment exists for that request\n const hasManualEnrichment = requestEnrichers.filter(\n ({ status }) => status === 'ACTION_REQUIRED',\n );\n\n // Save request to queue\n if (hasManualEnrichment) {\n const requestIdentifiers = await fetchAllRequestIdentifiers(client, sombra, {\n filterBy: { requestId: request.id },\n skipSombraCheck: true,\n logger,\n });\n savedRequests.push({\n ...request,\n requestIdentifiers,\n requestEnrichers,\n });\n }\n },\n {\n concurrency,\n },\n );\n\n const data = savedRequests.map(\n ({\n attributeValues,\n requestIdentifiers,\n requestEnrichers, // eslint-disable-line @typescript-eslint/no-unused-vars\n ...request\n }) => ({\n ...request,\n // flatten identifiers\n ...Object.entries(groupBy(requestIdentifiers, 'name')).reduce(\n (acc, [key, values]) =>\n Object.assign(acc, {\n [key]: values.map(({ value }) => value).join(','),\n }),\n {},\n ),\n // flatten attributes\n ...Object.entries(groupBy(attributeValues, 'attributeKey.name')).reduce(\n (acc, [key, values]) =>\n Object.assign(acc, {\n [key]: values.map(({ name }) => name).join(','),\n }),\n {},\n ),\n }),\n );\n\n // Write out to CSV\n const headers = uniq(data.map((d) => Object.keys(d)).flat());\n await writeCsv(file, data, headers);\n\n logger.info(\n colors.green(`Successfully wrote ${savedRequests.length} requests to file \"${file}\"`),\n );\n\n return savedRequests;\n}\n"],"mappings":"khBAgCA,eAAsB,EAAqC,CACzD,OACA,OACA,aACA,iBAAiB,EAAE,CACnB,cAAc,IACd,eAAe,GAc4B,CAE3C,IAAM,EAAS,EAA4B,EAAc,EAAK,CACxD,EAAS,MAAM,EAAwB,EAAc,EAAM,CAC/D,SACA,aAAc,EACd,UAAW,QAAQ,IAAI,WACxB,CAAC,CAEF,EAAO,KACL,EAAO,QACL,6DAA6D,EAAe,KAAK,IAAI,GACtF,CACF,CAGD,IAAM,EAAc,MAAM,EAAiB,EAAQ,CACjD,QAAS,EACT,SAAU,CAAC,EAAc,UAAU,CACpC,CAAC,CAEF,MAAM,EAAsB,EAAQ,CAAE,SAAQ,CAAC,CAG/C,IAAM,EAAiD,EAAE,CAGzD,MAAM,EACJ,EACA,KAAO,IAAY,CAEjB,IAAM,EAAmB,MAAM,EAAyB,EAAQ,CAC9D,SAAU,CAAE,UAAW,EAAQ,GAAI,CACnC,SACD,CAAC,CAQF,GAL4B,EAAiB,QAC1C,CAAE,YAAa,IAAW,kBAIN,CAAE,CACvB,IAAM,EAAqB,MAAM,EAA2B,EAAQ,EAAQ,CAC1E,SAAU,CAAE,UAAW,EAAQ,GAAI,CACnC,gBAAiB,GACjB,SACD,CAAC,CACF,EAAc,KAAK,CACjB,GAAG,EACH,qBACA,mBACD,CAAC,GAGN,CACE,cACD,CACF,CAED,IAAM,EAAO,EAAc,KACxB,CACC,kBACA,qBACA,mBACA,GAAG,MACE,CACL,GAAG,EAEH,GAAG,OAAO,QAAQ,EAAQ,EAAoB,OAAO,CAAC,CAAC,QACpD,EAAK,CAAC,EAAK,KACV,OAAO,OAAO,EAAK,EAChB,GAAM,EAAO,KAAK,CAAE,WAAY,EAAM,CAAC,KAAK,IAAI,CAClD,CAAC,CACJ,EAAE,CACH,CAED,GAAG,OAAO,QAAQ,EAAQ,EAAiB,oBAAoB,CAAC,CAAC,QAC9D,EAAK,CAAC,EAAK,KACV,OAAO,OAAO,EAAK,EAChB,GAAM,EAAO,KAAK,CAAE,UAAW,EAAK,CAAC,KAAK,IAAI,CAChD,CAAC,CACJ,EAAE,CACH,CACF,EACF,CAUD,OANA,MAAM,EAAS,EAAM,EADL,EAAK,EAAK,IAAK,GAAM,OAAO,KAAK,EAAE,CAAC,CAAC,MAAM,CACzB,CAAC,CAEnC,EAAO,KACL,EAAO,MAAM,sBAAsB,EAAc,OAAO,qBAAqB,EAAK,GAAG,CACtF,CAEM"}
@@ -1,2 +1,2 @@
1
- import{a as e}from"./constants-BCmVqlrM.mjs";import{t}from"./logger-CPu6zbyQ.mjs";import{n}from"./readCsv-DNcJ_UfU.mjs";import{chunk as r}from"lodash-es";import i from"colors";import{map as a,mapSeries as o}from"@transcend-io/utils";import*as s from"io-ts";import{createSombraGotInstance as c}from"@transcend-io/sdk";import l from"cli-progress";const u=s.type({nonce:s.string,identifier:s.string});async function d(e,{nonce:t,identifier:n}){try{return await e.put(`v1/data-silo`,{headers:{"x-transcend-nonce":t},json:{profiles:[{profileId:n}]}}),!0}catch(e){if(e.response?.statusCode===409)return!1;throw Error(`Received an error from server: ${e?.response?.body||e?.message}`)}}async function f({file:s,dataSiloId:f,auth:p,sombraAuth:m,concurrency:h=100,transcendUrl:g=e,sleepSeconds:_=10}){let v=await c(g,p,{logger:t,sombraApiKey:m,sombraUrl:process.env.SOMBRA_URL});t.info(i.magenta(`Reading "${s}" from disk`));let y=n(s,u);t.info(i.magenta(`Notifying Transcend for data silo "${f}" marking "${y.length}" identifiers as completed.`));let b=new Date().getTime(),x=new l.SingleBar({},l.Presets.shades_classic),S=0,C=0,w=0;x.start(y.length,0);let T=r(y,h),E=T.length;await o(T,async(e,n)=>{t.info(i.blue(`Processing chunk ${n+1}/${E} (${r.length} items)`)),await a(e,async e=>{try{await d(v,e)?S+=1:C+=1}catch(n){t.error(i.red(`Error notifying Transcend for identifier "${e.identifier}" - ${n?.message}`)),w+=1}x.update(S+C)}),_>0&&n<E-1&&(t.info(i.yellow(`Sleeping for ${_}s before next chunk...`)),await new Promise(e=>{setTimeout(e,_*1e3)}))}),x.stop();let D=new Date().getTime()-b;if(t.info(i.green(`Successfully notified Transcend for ${S} identifiers in "${D/1e3}" seconds!`)),C&&t.info(i.magenta(`There were ${C} identifiers that were not in a state to be updated.They likely have already been resolved.`)),w)throw t.error(i.red(`There were ${w} identifiers that failed to be updated. Please review the logs for more information.`)),Error(`Failed to update all identifiers`);return y.length}export{u as n,d as r,f as t};
2
- //# sourceMappingURL=pushCronIdentifiersFromCsv-DeS78zyp.mjs.map
1
+ import{a as e}from"./constants-boymUaFC.mjs";import{t}from"./logger-CPu6zbyQ.mjs";import{n}from"./readCsv-DNcJ_UfU.mjs";import{chunk as r}from"lodash-es";import i from"colors";import{map as a,mapSeries as o}from"@transcend-io/utils";import*as s from"io-ts";import{createSombraGotInstance as c}from"@transcend-io/sdk";import l from"cli-progress";const u=s.type({nonce:s.string,identifier:s.string});async function d(e,{nonce:t,identifier:n}){try{return await e.put(`v1/data-silo`,{headers:{"x-transcend-nonce":t},json:{profiles:[{profileId:n}]}}),!0}catch(e){if(e.response?.statusCode===409)return!1;throw Error(`Received an error from server: ${e?.response?.body||e?.message}`)}}async function f({file:s,dataSiloId:f,auth:p,sombraAuth:m,concurrency:h=100,transcendUrl:g=e,sleepSeconds:_=10}){let v=await c(g,p,{logger:t,sombraApiKey:m,sombraUrl:process.env.SOMBRA_URL});t.info(i.magenta(`Reading "${s}" from disk`));let y=n(s,u);t.info(i.magenta(`Notifying Transcend for data silo "${f}" marking "${y.length}" identifiers as completed.`));let b=new Date().getTime(),x=new l.SingleBar({},l.Presets.shades_classic),S=0,C=0,w=0;x.start(y.length,0);let T=r(y,h),E=T.length;await o(T,async(e,n)=>{t.info(i.blue(`Processing chunk ${n+1}/${E} (${r.length} items)`)),await a(e,async e=>{try{await d(v,e)?S+=1:C+=1}catch(n){t.error(i.red(`Error notifying Transcend for identifier "${e.identifier}" - ${n?.message}`)),w+=1}x.update(S+C)}),_>0&&n<E-1&&(t.info(i.yellow(`Sleeping for ${_}s before next chunk...`)),await new Promise(e=>{setTimeout(e,_*1e3)}))}),x.stop();let D=new Date().getTime()-b;if(t.info(i.green(`Successfully notified Transcend for ${S} identifiers in "${D/1e3}" seconds!`)),C&&t.info(i.magenta(`There were ${C} identifiers that were not in a state to be updated.They likely have already been resolved.`)),w)throw t.error(i.red(`There were ${w} identifiers that failed to be updated. Please review the logs for more information.`)),Error(`Failed to update all identifiers`);return y.length}export{u as n,d as r,f as t};
2
+ //# sourceMappingURL=pushCronIdentifiersFromCsv-D-l3erLO.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"pushCronIdentifiersFromCsv-DeS78zyp.mjs","names":[],"sources":["../src/lib/cron/markCronIdentifierCompleted.ts","../src/lib/cron/pushCronIdentifiersFromCsv.ts"],"sourcesContent":["import type { Got } from 'got';\nimport * as t from 'io-ts';\n\n/**\n * Minimal set required to mark as completed\n */\nexport const CronIdentifierPush = t.type({\n nonce: t.string,\n identifier: t.string,\n});\n\n/** Type override */\nexport type CronIdentifierPush = t.TypeOf<typeof CronIdentifierPush>;\n\n/**\n * Mark an identifier output by the cron job as completed.\n *\n * @see https://docs.transcend.io/docs/api-reference/PUT/v1/data-silo\n * @param sombra - Sombra instance configured to make requests\n * @param options - Additional options\n * @returns Successfully submitted request, false if not in a state to update\n */\nexport async function markCronIdentifierCompleted(\n sombra: Got,\n { nonce, identifier }: CronIdentifierPush,\n): Promise<boolean> {\n try {\n // Make the GraphQL request\n await sombra.put('v1/data-silo', {\n headers: {\n 'x-transcend-nonce': nonce,\n },\n json: {\n profiles: [\n {\n profileId: identifier,\n },\n ],\n },\n });\n return true;\n } catch (err) {\n // handle gracefully\n if (err.response?.statusCode === 409) {\n return false;\n }\n throw new Error(`Received an error from server: ${err?.response?.body || err?.message}`);\n }\n}\n","import { createSombraGotInstance } from '@transcend-io/sdk';\nimport { map, mapSeries } from '@transcend-io/utils';\nimport cliProgress from 'cli-progress';\nimport colors from 'colors';\nimport { chunk } from 'lodash-es';\n\nimport { DEFAULT_TRANSCEND_API } from '../../constants.js';\nimport { logger } from '../../logger.js';\nimport { readCsv } from '../requests/index.js';\nimport { markCronIdentifierCompleted, CronIdentifierPush } from './markCronIdentifierCompleted.js';\n\n/**\n * Given a CSV of cron job outputs, mark all requests as completed in Transcend\n *\n * @param options - Options\n * @returns Number of items marked as completed\n */\nexport async function pushCronIdentifiersFromCsv({\n file,\n dataSiloId,\n auth,\n sombraAuth,\n concurrency = 100,\n transcendUrl = DEFAULT_TRANSCEND_API,\n sleepSeconds = 10,\n}: {\n /** CSV file path */\n file: string;\n /** Transcend API key authentication */\n auth: string;\n /** Data Silo ID to pull down jobs for */\n dataSiloId: string;\n /** Upload concurrency */\n concurrency?: number;\n /** API URL for Transcend backend */\n transcendUrl?: string;\n /** Sombra API key authentication */\n sombraAuth?: string;\n /** Sleep time in seconds between chunks of concurrent calls */\n sleepSeconds?: number;\n}): Promise<number> {\n // Create sombra instance to communicate with\n const sombra = await createSombraGotInstance(transcendUrl, auth, {\n logger,\n sombraApiKey: sombraAuth,\n sombraUrl: process.env.SOMBRA_URL,\n });\n\n // Read from CSV\n logger.info(colors.magenta(`Reading \"${file}\" from disk`));\n const activeResults = readCsv(file, CronIdentifierPush);\n\n // Notify Transcend\n logger.info(\n colors.magenta(\n `Notifying Transcend for data silo \"${dataSiloId}\" marking \"${activeResults.length}\" identifiers as completed.`,\n ),\n );\n\n // Time duration\n const t0 = new Date().getTime();\n // create a new progress bar instance and use shades_classic theme\n const progressBar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic);\n\n let successCount = 0;\n let failureCount = 0;\n let errorCount = 0;\n progressBar.start(activeResults.length, 0);\n\n // Process in chunks with sleep intervals\n const chunks = chunk(activeResults, concurrency);\n const totalChunks = chunks.length;\n const processChunk = async (items: CronIdentifierPush[], chunkIndex: number): Promise<void> => {\n logger.info(\n colors.blue(`Processing chunk ${chunkIndex + 1}/${totalChunks} (${chunk.length} items)`),\n );\n\n // Process the items of the chunk concurrently\n await map(items, async (identifier) => {\n try {\n const success = await markCronIdentifierCompleted(sombra, identifier);\n if (success) {\n successCount += 1;\n } else {\n failureCount += 1;\n }\n } catch (e) {\n logger.error(\n colors.red(\n `Error notifying Transcend for identifier \"${identifier.identifier}\" - ${e?.message}`,\n ),\n );\n errorCount += 1;\n }\n progressBar.update(successCount + failureCount);\n });\n\n // Sleep between chunks (except for the last chunk)\n if (sleepSeconds > 0 && chunkIndex < totalChunks - 1) {\n logger.info(colors.yellow(`Sleeping for ${sleepSeconds}s before next chunk...`));\n\n await new Promise((resolve) => {\n setTimeout(resolve, sleepSeconds * 1000);\n });\n }\n };\n\n // Process all chunks sequentially\n await mapSeries(chunks, processChunk);\n\n progressBar.stop();\n const t1 = new Date().getTime();\n const totalTime = t1 - t0;\n\n logger.info(\n colors.green(\n `Successfully notified Transcend for ${successCount} identifiers in \"${\n totalTime / 1000\n }\" seconds!`,\n ),\n );\n if (failureCount) {\n logger.info(\n colors.magenta(\n `There were ${failureCount} identifiers that were not in a state to be updated.` +\n 'They likely have already been resolved.',\n ),\n );\n }\n if (errorCount) {\n logger.error(\n colors.red(\n `There were ${errorCount} identifiers that failed to be updated. Please review the logs for more information.`,\n ),\n );\n throw new Error('Failed to update all identifiers');\n }\n return activeResults.length;\n}\n"],"mappings":"yVAMA,MAAa,EAAqB,EAAE,KAAK,CACvC,MAAO,EAAE,OACT,WAAY,EAAE,OACf,CAAC,CAaF,eAAsB,EACpB,EACA,CAAE,QAAO,cACS,CAClB,GAAI,CAcF,OAZA,MAAM,EAAO,IAAI,eAAgB,CAC/B,QAAS,CACP,oBAAqB,EACtB,CACD,KAAM,CACJ,SAAU,CACR,CACE,UAAW,EACZ,CACF,CACF,CACF,CAAC,CACK,SACA,EAAK,CAEZ,GAAI,EAAI,UAAU,aAAe,IAC/B,MAAO,GAET,MAAU,MAAM,kCAAkC,GAAK,UAAU,MAAQ,GAAK,UAAU,EC7B5F,eAAsB,EAA2B,CAC/C,OACA,aACA,OACA,aACA,cAAc,IACd,eAAe,EACf,eAAe,IAgBG,CAElB,IAAM,EAAS,MAAM,EAAwB,EAAc,EAAM,CAC/D,SACA,aAAc,EACd,UAAW,QAAQ,IAAI,WACxB,CAAC,CAGF,EAAO,KAAK,EAAO,QAAQ,YAAY,EAAK,aAAa,CAAC,CAC1D,IAAM,EAAgB,EAAQ,EAAM,EAAmB,CAGvD,EAAO,KACL,EAAO,QACL,sCAAsC,EAAW,aAAa,EAAc,OAAO,6BACpF,CACF,CAGD,IAAM,EAAK,IAAI,MAAM,CAAC,SAAS,CAEzB,EAAc,IAAI,EAAY,UAAU,EAAE,CAAE,EAAY,QAAQ,eAAe,CAEjF,EAAe,EACf,EAAe,EACf,EAAa,EACjB,EAAY,MAAM,EAAc,OAAQ,EAAE,CAG1C,IAAM,EAAS,EAAM,EAAe,EAAY,CAC1C,EAAc,EAAO,OAqC3B,MAAM,EAAU,EAAQ,MApCI,EAA6B,IAAsC,CAC7F,EAAO,KACL,EAAO,KAAK,oBAAoB,EAAa,EAAE,GAAG,EAAY,IAAI,EAAM,OAAO,SAAS,CACzF,CAGD,MAAM,EAAI,EAAO,KAAO,IAAe,CACrC,GAAI,CAEE,MADkB,EAA4B,EAAQ,EAAW,CAEnE,GAAgB,EAEhB,GAAgB,QAEX,EAAG,CACV,EAAO,MACL,EAAO,IACL,6CAA6C,EAAW,WAAW,MAAM,GAAG,UAC7E,CACF,CACD,GAAc,EAEhB,EAAY,OAAO,EAAe,EAAa,EAC/C,CAGE,EAAe,GAAK,EAAa,EAAc,IACjD,EAAO,KAAK,EAAO,OAAO,gBAAgB,EAAa,wBAAwB,CAAC,CAEhF,MAAM,IAAI,QAAS,GAAY,CAC7B,WAAW,EAAS,EAAe,IAAK,EACxC,GAK+B,CAErC,EAAY,MAAM,CAElB,IAAM,EADK,IAAI,MAAM,CAAC,SACF,CAAG,EAiBvB,GAfA,EAAO,KACL,EAAO,MACL,uCAAuC,EAAa,mBAClD,EAAY,IACb,YACF,CACF,CACG,GACF,EAAO,KACL,EAAO,QACL,cAAc,EAAa,6FAE5B,CACF,CAEC,EAMF,MALA,EAAO,MACL,EAAO,IACL,cAAc,EAAW,sFAC1B,CACF,CACS,MAAM,mCAAmC,CAErD,OAAO,EAAc"}
1
+ {"version":3,"file":"pushCronIdentifiersFromCsv-D-l3erLO.mjs","names":[],"sources":["../src/lib/cron/markCronIdentifierCompleted.ts","../src/lib/cron/pushCronIdentifiersFromCsv.ts"],"sourcesContent":["import type { Got } from 'got';\nimport * as t from 'io-ts';\n\n/**\n * Minimal set required to mark as completed\n */\nexport const CronIdentifierPush = t.type({\n nonce: t.string,\n identifier: t.string,\n});\n\n/** Type override */\nexport type CronIdentifierPush = t.TypeOf<typeof CronIdentifierPush>;\n\n/**\n * Mark an identifier output by the cron job as completed.\n *\n * @see https://docs.transcend.io/docs/api-reference/PUT/v1/data-silo\n * @param sombra - Sombra instance configured to make requests\n * @param options - Additional options\n * @returns Successfully submitted request, false if not in a state to update\n */\nexport async function markCronIdentifierCompleted(\n sombra: Got,\n { nonce, identifier }: CronIdentifierPush,\n): Promise<boolean> {\n try {\n // Make the GraphQL request\n await sombra.put('v1/data-silo', {\n headers: {\n 'x-transcend-nonce': nonce,\n },\n json: {\n profiles: [\n {\n profileId: identifier,\n },\n ],\n },\n });\n return true;\n } catch (err) {\n // handle gracefully\n if (err.response?.statusCode === 409) {\n return false;\n }\n throw new Error(`Received an error from server: ${err?.response?.body || err?.message}`);\n }\n}\n","import { createSombraGotInstance } from '@transcend-io/sdk';\nimport { map, mapSeries } from '@transcend-io/utils';\nimport cliProgress from 'cli-progress';\nimport colors from 'colors';\nimport { chunk } from 'lodash-es';\n\nimport { DEFAULT_TRANSCEND_API } from '../../constants.js';\nimport { logger } from '../../logger.js';\nimport { readCsv } from '../requests/index.js';\nimport { markCronIdentifierCompleted, CronIdentifierPush } from './markCronIdentifierCompleted.js';\n\n/**\n * Given a CSV of cron job outputs, mark all requests as completed in Transcend\n *\n * @param options - Options\n * @returns Number of items marked as completed\n */\nexport async function pushCronIdentifiersFromCsv({\n file,\n dataSiloId,\n auth,\n sombraAuth,\n concurrency = 100,\n transcendUrl = DEFAULT_TRANSCEND_API,\n sleepSeconds = 10,\n}: {\n /** CSV file path */\n file: string;\n /** Transcend API key authentication */\n auth: string;\n /** Data Silo ID to pull down jobs for */\n dataSiloId: string;\n /** Upload concurrency */\n concurrency?: number;\n /** API URL for Transcend backend */\n transcendUrl?: string;\n /** Sombra API key authentication */\n sombraAuth?: string;\n /** Sleep time in seconds between chunks of concurrent calls */\n sleepSeconds?: number;\n}): Promise<number> {\n // Create sombra instance to communicate with\n const sombra = await createSombraGotInstance(transcendUrl, auth, {\n logger,\n sombraApiKey: sombraAuth,\n sombraUrl: process.env.SOMBRA_URL,\n });\n\n // Read from CSV\n logger.info(colors.magenta(`Reading \"${file}\" from disk`));\n const activeResults = readCsv(file, CronIdentifierPush);\n\n // Notify Transcend\n logger.info(\n colors.magenta(\n `Notifying Transcend for data silo \"${dataSiloId}\" marking \"${activeResults.length}\" identifiers as completed.`,\n ),\n );\n\n // Time duration\n const t0 = new Date().getTime();\n // create a new progress bar instance and use shades_classic theme\n const progressBar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic);\n\n let successCount = 0;\n let failureCount = 0;\n let errorCount = 0;\n progressBar.start(activeResults.length, 0);\n\n // Process in chunks with sleep intervals\n const chunks = chunk(activeResults, concurrency);\n const totalChunks = chunks.length;\n const processChunk = async (items: CronIdentifierPush[], chunkIndex: number): Promise<void> => {\n logger.info(\n colors.blue(`Processing chunk ${chunkIndex + 1}/${totalChunks} (${chunk.length} items)`),\n );\n\n // Process the items of the chunk concurrently\n await map(items, async (identifier) => {\n try {\n const success = await markCronIdentifierCompleted(sombra, identifier);\n if (success) {\n successCount += 1;\n } else {\n failureCount += 1;\n }\n } catch (e) {\n logger.error(\n colors.red(\n `Error notifying Transcend for identifier \"${identifier.identifier}\" - ${e?.message}`,\n ),\n );\n errorCount += 1;\n }\n progressBar.update(successCount + failureCount);\n });\n\n // Sleep between chunks (except for the last chunk)\n if (sleepSeconds > 0 && chunkIndex < totalChunks - 1) {\n logger.info(colors.yellow(`Sleeping for ${sleepSeconds}s before next chunk...`));\n\n await new Promise((resolve) => {\n setTimeout(resolve, sleepSeconds * 1000);\n });\n }\n };\n\n // Process all chunks sequentially\n await mapSeries(chunks, processChunk);\n\n progressBar.stop();\n const t1 = new Date().getTime();\n const totalTime = t1 - t0;\n\n logger.info(\n colors.green(\n `Successfully notified Transcend for ${successCount} identifiers in \"${\n totalTime / 1000\n }\" seconds!`,\n ),\n );\n if (failureCount) {\n logger.info(\n colors.magenta(\n `There were ${failureCount} identifiers that were not in a state to be updated.` +\n 'They likely have already been resolved.',\n ),\n );\n }\n if (errorCount) {\n logger.error(\n colors.red(\n `There were ${errorCount} identifiers that failed to be updated. Please review the logs for more information.`,\n ),\n );\n throw new Error('Failed to update all identifiers');\n }\n return activeResults.length;\n}\n"],"mappings":"yVAMA,MAAa,EAAqB,EAAE,KAAK,CACvC,MAAO,EAAE,OACT,WAAY,EAAE,OACf,CAAC,CAaF,eAAsB,EACpB,EACA,CAAE,QAAO,cACS,CAClB,GAAI,CAcF,OAZA,MAAM,EAAO,IAAI,eAAgB,CAC/B,QAAS,CACP,oBAAqB,EACtB,CACD,KAAM,CACJ,SAAU,CACR,CACE,UAAW,EACZ,CACF,CACF,CACF,CAAC,CACK,SACA,EAAK,CAEZ,GAAI,EAAI,UAAU,aAAe,IAC/B,MAAO,GAET,MAAU,MAAM,kCAAkC,GAAK,UAAU,MAAQ,GAAK,UAAU,EC7B5F,eAAsB,EAA2B,CAC/C,OACA,aACA,OACA,aACA,cAAc,IACd,eAAe,EACf,eAAe,IAgBG,CAElB,IAAM,EAAS,MAAM,EAAwB,EAAc,EAAM,CAC/D,SACA,aAAc,EACd,UAAW,QAAQ,IAAI,WACxB,CAAC,CAGF,EAAO,KAAK,EAAO,QAAQ,YAAY,EAAK,aAAa,CAAC,CAC1D,IAAM,EAAgB,EAAQ,EAAM,EAAmB,CAGvD,EAAO,KACL,EAAO,QACL,sCAAsC,EAAW,aAAa,EAAc,OAAO,6BACpF,CACF,CAGD,IAAM,EAAK,IAAI,MAAM,CAAC,SAAS,CAEzB,EAAc,IAAI,EAAY,UAAU,EAAE,CAAE,EAAY,QAAQ,eAAe,CAEjF,EAAe,EACf,EAAe,EACf,EAAa,EACjB,EAAY,MAAM,EAAc,OAAQ,EAAE,CAG1C,IAAM,EAAS,EAAM,EAAe,EAAY,CAC1C,EAAc,EAAO,OAqC3B,MAAM,EAAU,EAAQ,MApCI,EAA6B,IAAsC,CAC7F,EAAO,KACL,EAAO,KAAK,oBAAoB,EAAa,EAAE,GAAG,EAAY,IAAI,EAAM,OAAO,SAAS,CACzF,CAGD,MAAM,EAAI,EAAO,KAAO,IAAe,CACrC,GAAI,CAEE,MADkB,EAA4B,EAAQ,EAAW,CAEnE,GAAgB,EAEhB,GAAgB,QAEX,EAAG,CACV,EAAO,MACL,EAAO,IACL,6CAA6C,EAAW,WAAW,MAAM,GAAG,UAC7E,CACF,CACD,GAAc,EAEhB,EAAY,OAAO,EAAe,EAAa,EAC/C,CAGE,EAAe,GAAK,EAAa,EAAc,IACjD,EAAO,KAAK,EAAO,OAAO,gBAAgB,EAAa,wBAAwB,CAAC,CAEhF,MAAM,IAAI,QAAS,GAAY,CAC7B,WAAW,EAAS,EAAe,IAAK,EACxC,GAK+B,CAErC,EAAY,MAAM,CAElB,IAAM,EADK,IAAI,MAAM,CAAC,SACF,CAAG,EAiBvB,GAfA,EAAO,KACL,EAAO,MACL,uCAAuC,EAAa,mBAClD,EAAY,IACb,YACF,CACF,CACG,GACF,EAAO,KACL,EAAO,QACL,cAAc,EAAa,6FAE5B,CACF,CAEC,EAMF,MALA,EAAO,MACL,EAAO,IACL,cAAc,EAAW,sFAC1B,CACF,CACS,MAAM,mCAAmC,CAErD,OAAO,EAAc"}
@@ -1,2 +1,2 @@
1
- import{a as e}from"./constants-BCmVqlrM.mjs";import{t}from"./logger-CPu6zbyQ.mjs";import{n}from"./readCsv-DNcJ_UfU.mjs";import{o as r}from"./request-BpKyN9Xm.mjs";import{uniq as i}from"lodash-es";import a from"colors";import{map as o,splitCsvToList as s}from"@transcend-io/utils";import*as c from"io-ts";import{buildTranscendGraphQLClient as l,createSombraGotInstance as u,makeGraphQLRequest as d}from"@transcend-io/sdk";const f=`https://app.transcend.io/privacy-requests/incoming-requests/`,p=c.record(c.string,c.string);async function m(e,{id:n,...r},o,c){if(!n){let e=`Request ID must be provided to enricher request.${c?` Found error in row: ${c}`:``}`;throw t.error(a.red(e)),Error(e)}let l=n.toLowerCase(),u=Object.entries(r).reduce((e,[t,n])=>i(s(n)).length===0?e:Object.assign(e,{[t]:i(s(n)).map(e=>({value:t===`email`?e.toLowerCase():e}))}),{});try{return await e.post(`v1/enrich-identifiers`,{headers:{"x-transcend-request-id":l,"x-transcend-enricher-id":o},json:{enrichedIdentifiers:u}}).json(),t.error(a.green(`Successfully enriched request: ${f}${l}`)),!0}catch(e){if(typeof e.response.body==`string`&&e.response.body.includes(`Cannot update a resolved RequestEnricher`))return t.warn(a.magenta(`Skipped enrichment for request: ${f}${l}, request is no longer in the enriching phase.`)),!1;throw t.error(a.red(`Failed to enricher identifiers for request with id: ${f}${l} - ${e.message} - ${e.response.body}`)),e}}async function h({file:i,auth:s,sombraAuth:c,enricherId:f,markSilent:h,concurrency:g=100,transcendUrl:_=e}){let v=await u(_,s,{logger:t,sombraApiKey:c,sombraUrl:process.env.SOMBRA_URL}),y=l(_,s);t.info(a.magenta(`Reading "${i}" from disk`));let b=n(i,p);t.info(a.magenta(`Enriching "${b.length}" privacy requests.`));let x=0,S=0,C=0;if(await o(b,async(e,n)=>{try{h&&(await d(y,r,{variables:{input:{id:e.id,isSilent:!0}},logger:t}),t.info(a.magenta(`Mark request as silent mode - ${e.id}`))),await m(v,e,f,n)?x+=1:S+=1}catch{C+=1}},{concurrency:g}),t.info(a.green(`Successfully notified Transcend! \n Success count: ${x}.`)),S>0&&t.info(a.magenta(`Skipped count: ${S}.`)),C>0)throw t.info(a.red(`Error Count: ${C}.`)),Error(`Failed to enrich: ${C} requests.`);return b.length}export{p as n,m as r,h as t};
2
- //# sourceMappingURL=pushManualEnrichmentIdentifiersFromCsv-B3iVwuvs.mjs.map
1
+ import{a as e}from"./constants-boymUaFC.mjs";import{t}from"./logger-CPu6zbyQ.mjs";import{n}from"./readCsv-DNcJ_UfU.mjs";import{o as r}from"./request-BpKyN9Xm.mjs";import{uniq as i}from"lodash-es";import a from"colors";import{map as o,splitCsvToList as s}from"@transcend-io/utils";import*as c from"io-ts";import{buildTranscendGraphQLClient as l,createSombraGotInstance as u,makeGraphQLRequest as d}from"@transcend-io/sdk";const f=`https://app.transcend.io/privacy-requests/incoming-requests/`,p=c.record(c.string,c.string);async function m(e,{id:n,...r},o,c){if(!n){let e=`Request ID must be provided to enricher request.${c?` Found error in row: ${c}`:``}`;throw t.error(a.red(e)),Error(e)}let l=n.toLowerCase(),u=Object.entries(r).reduce((e,[t,n])=>i(s(n)).length===0?e:Object.assign(e,{[t]:i(s(n)).map(e=>({value:t===`email`?e.toLowerCase():e}))}),{});try{return await e.post(`v1/enrich-identifiers`,{headers:{"x-transcend-request-id":l,"x-transcend-enricher-id":o},json:{enrichedIdentifiers:u}}).json(),t.error(a.green(`Successfully enriched request: ${f}${l}`)),!0}catch(e){if(typeof e.response.body==`string`&&e.response.body.includes(`Cannot update a resolved RequestEnricher`))return t.warn(a.magenta(`Skipped enrichment for request: ${f}${l}, request is no longer in the enriching phase.`)),!1;throw t.error(a.red(`Failed to enricher identifiers for request with id: ${f}${l} - ${e.message} - ${e.response.body}`)),e}}async function h({file:i,auth:s,sombraAuth:c,enricherId:f,markSilent:h,concurrency:g=100,transcendUrl:_=e}){let v=await u(_,s,{logger:t,sombraApiKey:c,sombraUrl:process.env.SOMBRA_URL}),y=l(_,s);t.info(a.magenta(`Reading "${i}" from disk`));let b=n(i,p);t.info(a.magenta(`Enriching "${b.length}" privacy requests.`));let x=0,S=0,C=0;if(await o(b,async(e,n)=>{try{h&&(await d(y,r,{variables:{input:{id:e.id,isSilent:!0}},logger:t}),t.info(a.magenta(`Mark request as silent mode - ${e.id}`))),await m(v,e,f,n)?x+=1:S+=1}catch{C+=1}},{concurrency:g}),t.info(a.green(`Successfully notified Transcend! \n Success count: ${x}.`)),S>0&&t.info(a.magenta(`Skipped count: ${S}.`)),C>0)throw t.info(a.red(`Error Count: ${C}.`)),Error(`Failed to enrich: ${C} requests.`);return b.length}export{p as n,m as r,h as t};
2
+ //# sourceMappingURL=pushManualEnrichmentIdentifiersFromCsv-CqLIw5MV.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"pushManualEnrichmentIdentifiersFromCsv-B3iVwuvs.mjs","names":[],"sources":["../src/lib/manual-enrichment/enrichPrivacyRequest.ts","../src/lib/manual-enrichment/pushManualEnrichmentIdentifiersFromCsv.ts"],"sourcesContent":["import { splitCsvToList } from '@transcend-io/utils';\nimport colors from 'colors';\nimport type { Got } from 'got';\nimport * as t from 'io-ts';\nimport { uniq } from 'lodash-es';\n\nimport { logger } from '../../logger.js';\n\nconst ADMIN_URL = 'https://app.transcend.io/privacy-requests/incoming-requests/';\n/**\n * Minimal set required to mark as completed\n */\nexport const EnrichPrivacyRequest = t.record(t.string, t.string);\n\n/** Type override */\nexport type EnrichPrivacyRequest = t.TypeOf<typeof EnrichPrivacyRequest>;\n\n/**\n * Upload identifiers to a privacy request or mark request as\n *\n * @param sombra - Sombra instance configured to make requests\n * @param request - Request to enricher\n * @param enricherId - The ID of the enricher being uploaded to\n * @param index - Index of request ID\n * @returns True if enriched successfully, false if skipped, throws error if failed\n */\nexport async function enrichPrivacyRequest(\n sombra: Got,\n { id: rawId, ...rest }: EnrichPrivacyRequest,\n enricherId: string,\n index?: number,\n): Promise<boolean> {\n if (!rawId) {\n // error\n const msg = `Request ID must be provided to enricher request.${\n index ? ` Found error in row: ${index}` : ''\n }`;\n logger.error(colors.red(msg));\n throw new Error(msg);\n }\n\n const id = rawId.toLowerCase();\n\n // Pull out the identifiers\n const enrichedIdentifiers = Object.entries(rest).reduce(\n (acc, [key, value]) => {\n const values = uniq(splitCsvToList(value));\n return values.length === 0\n ? acc\n : Object.assign(acc, {\n [key]: uniq(splitCsvToList(value)).map((val) => ({\n value: key === 'email' ? val.toLowerCase() : val,\n })),\n });\n },\n {} as Record<string, string[]>,\n );\n\n // Make the GraphQL request\n try {\n await sombra\n .post('v1/enrich-identifiers', {\n headers: {\n 'x-transcend-request-id': id,\n 'x-transcend-enricher-id': enricherId,\n },\n json: {\n enrichedIdentifiers,\n },\n })\n .json();\n\n logger.error(colors.green(`Successfully enriched request: ${ADMIN_URL}${id}`));\n return true;\n } catch (err) {\n // skip if already enriched\n if (\n typeof err.response.body === 'string' &&\n err.response.body.includes('Cannot update a resolved RequestEnricher')\n ) {\n logger.warn(\n colors.magenta(\n `Skipped enrichment for request: ${ADMIN_URL}${id}, request is no longer in the enriching phase.`,\n ),\n );\n return false;\n }\n\n // error\n logger.error(\n colors.red(\n `Failed to enricher identifiers for request with id: ${ADMIN_URL}${id} - ${err.message} - ${err.response.body}`,\n ),\n );\n throw err;\n }\n}\n","import {\n buildTranscendGraphQLClient,\n createSombraGotInstance,\n makeGraphQLRequest,\n} from '@transcend-io/sdk';\nimport { map } from '@transcend-io/utils';\nimport colors from 'colors';\n\nimport { DEFAULT_TRANSCEND_API } from '../../constants.js';\nimport { logger } from '../../logger.js';\nimport { UPDATE_PRIVACY_REQUEST } from '../graphql/index.js';\nimport { readCsv } from '../requests/index.js';\nimport { enrichPrivacyRequest, EnrichPrivacyRequest } from './enrichPrivacyRequest.js';\n\n/**\n * Push a CSV of enriched requests back into Transcend\n *\n * @param options - Options\n * @returns Number of items processed\n */\nexport async function pushManualEnrichmentIdentifiersFromCsv({\n file,\n auth,\n sombraAuth,\n enricherId,\n markSilent,\n concurrency = 100,\n transcendUrl = DEFAULT_TRANSCEND_API,\n}: {\n /** CSV file path */\n file: string;\n /** Transcend API key authentication */\n auth: string;\n /** ID of enricher being uploaded to */\n enricherId: string;\n /** Sombra API key authentication */\n sombraAuth?: string;\n /** Concurrency */\n concurrency?: number;\n /** API URL for Transcend backend */\n transcendUrl?: string;\n /** Mark requests in silent mode before enriching */\n markSilent?: boolean;\n}): Promise<number> {\n // Create sombra instance to communicate with\n const sombra = await createSombraGotInstance(transcendUrl, auth, {\n logger,\n sombraApiKey: sombraAuth,\n sombraUrl: process.env.SOMBRA_URL,\n });\n const client = buildTranscendGraphQLClient(transcendUrl, auth);\n\n // Read from CSV\n logger.info(colors.magenta(`Reading \"${file}\" from disk`));\n const activeResults = readCsv(file, EnrichPrivacyRequest);\n\n // Notify Transcend\n logger.info(colors.magenta(`Enriching \"${activeResults.length}\" privacy requests.`));\n\n let successCount = 0;\n let skippedCount = 0;\n let errorCount = 0;\n\n await map(\n activeResults,\n async (request, index) => {\n try {\n // Mark requests in silent mode before a certain date\n if (markSilent) {\n await makeGraphQLRequest(client, UPDATE_PRIVACY_REQUEST, {\n variables: {\n input: {\n id: request.id,\n isSilent: true,\n },\n },\n logger,\n });\n\n logger.info(colors.magenta(`Mark request as silent mode - ${request.id}`));\n }\n\n const result = await enrichPrivacyRequest(sombra, request, enricherId, index);\n if (result) {\n successCount += 1;\n } else {\n skippedCount += 1;\n }\n } catch {\n errorCount += 1;\n }\n },\n { concurrency },\n );\n\n logger.info(colors.green(`Successfully notified Transcend! \\n Success count: ${successCount}.`));\n\n if (skippedCount > 0) {\n logger.info(colors.magenta(`Skipped count: ${skippedCount}.`));\n }\n\n if (errorCount > 0) {\n logger.info(colors.red(`Error Count: ${errorCount}.`));\n throw new Error(`Failed to enrich: ${errorCount} requests.`);\n }\n\n return activeResults.length;\n}\n"],"mappings":"qaAQA,MAAM,EAAY,+DAIL,EAAuB,EAAE,OAAO,EAAE,OAAQ,EAAE,OAAO,CAchE,eAAsB,EACpB,EACA,CAAE,GAAI,EAAO,GAAG,GAChB,EACA,EACkB,CAClB,GAAI,CAAC,EAAO,CAEV,IAAM,EAAM,mDACV,EAAQ,wBAAwB,IAAU,KAG5C,MADA,EAAO,MAAM,EAAO,IAAI,EAAI,CAAC,CACnB,MAAM,EAAI,CAGtB,IAAM,EAAK,EAAM,aAAa,CAGxB,EAAsB,OAAO,QAAQ,EAAK,CAAC,QAC9C,EAAK,CAAC,EAAK,KACK,EAAK,EAAe,EAAM,CAC5B,CAAC,SAAW,EACrB,EACA,OAAO,OAAO,EAAK,EAChB,GAAM,EAAK,EAAe,EAAM,CAAC,CAAC,IAAK,IAAS,CAC/C,MAAO,IAAQ,QAAU,EAAI,aAAa,CAAG,EAC9C,EAAE,CACJ,CAAC,CAER,EAAE,CACH,CAGD,GAAI,CAcF,OAbA,MAAM,EACH,KAAK,wBAAyB,CAC7B,QAAS,CACP,yBAA0B,EAC1B,0BAA2B,EAC5B,CACD,KAAM,CACJ,sBACD,CACF,CAAC,CACD,MAAM,CAET,EAAO,MAAM,EAAO,MAAM,kCAAkC,IAAY,IAAK,CAAC,CACvE,SACA,EAAK,CAEZ,GACE,OAAO,EAAI,SAAS,MAAS,UAC7B,EAAI,SAAS,KAAK,SAAS,2CAA2C,CAOtE,OALA,EAAO,KACL,EAAO,QACL,mCAAmC,IAAY,EAAG,gDACnD,CACF,CACM,GAST,MALA,EAAO,MACL,EAAO,IACL,uDAAuD,IAAY,EAAG,KAAK,EAAI,QAAQ,KAAK,EAAI,SAAS,OAC1G,CACF,CACK,GC1EV,eAAsB,EAAuC,CAC3D,OACA,OACA,aACA,aACA,aACA,cAAc,IACd,eAAe,GAgBG,CAElB,IAAM,EAAS,MAAM,EAAwB,EAAc,EAAM,CAC/D,SACA,aAAc,EACd,UAAW,QAAQ,IAAI,WACxB,CAAC,CACI,EAAS,EAA4B,EAAc,EAAK,CAG9D,EAAO,KAAK,EAAO,QAAQ,YAAY,EAAK,aAAa,CAAC,CAC1D,IAAM,EAAgB,EAAQ,EAAM,EAAqB,CAGzD,EAAO,KAAK,EAAO,QAAQ,cAAc,EAAc,OAAO,qBAAqB,CAAC,CAEpF,IAAI,EAAe,EACf,EAAe,EACf,EAAa,EAwCjB,GAtCA,MAAM,EACJ,EACA,MAAO,EAAS,IAAU,CACxB,GAAI,CAEE,IACF,MAAM,EAAmB,EAAQ,EAAwB,CACvD,UAAW,CACT,MAAO,CACL,GAAI,EAAQ,GACZ,SAAU,GACX,CACF,CACD,SACD,CAAC,CAEF,EAAO,KAAK,EAAO,QAAQ,iCAAiC,EAAQ,KAAK,CAAC,EAIxE,MADiB,EAAqB,EAAQ,EAAS,EAAY,EAAM,CAE3E,GAAgB,EAEhB,GAAgB,OAEZ,CACN,GAAc,IAGlB,CAAE,cAAa,CAChB,CAED,EAAO,KAAK,EAAO,MAAM,sDAAsD,EAAa,GAAG,CAAC,CAE5F,EAAe,GACjB,EAAO,KAAK,EAAO,QAAQ,kBAAkB,EAAa,GAAG,CAAC,CAG5D,EAAa,EAEf,MADA,EAAO,KAAK,EAAO,IAAI,gBAAgB,EAAW,GAAG,CAAC,CAC5C,MAAM,qBAAqB,EAAW,YAAY,CAG9D,OAAO,EAAc"}
1
+ {"version":3,"file":"pushManualEnrichmentIdentifiersFromCsv-CqLIw5MV.mjs","names":[],"sources":["../src/lib/manual-enrichment/enrichPrivacyRequest.ts","../src/lib/manual-enrichment/pushManualEnrichmentIdentifiersFromCsv.ts"],"sourcesContent":["import { splitCsvToList } from '@transcend-io/utils';\nimport colors from 'colors';\nimport type { Got } from 'got';\nimport * as t from 'io-ts';\nimport { uniq } from 'lodash-es';\n\nimport { logger } from '../../logger.js';\n\nconst ADMIN_URL = 'https://app.transcend.io/privacy-requests/incoming-requests/';\n/**\n * Minimal set required to mark as completed\n */\nexport const EnrichPrivacyRequest = t.record(t.string, t.string);\n\n/** Type override */\nexport type EnrichPrivacyRequest = t.TypeOf<typeof EnrichPrivacyRequest>;\n\n/**\n * Upload identifiers to a privacy request or mark request as\n *\n * @param sombra - Sombra instance configured to make requests\n * @param request - Request to enricher\n * @param enricherId - The ID of the enricher being uploaded to\n * @param index - Index of request ID\n * @returns True if enriched successfully, false if skipped, throws error if failed\n */\nexport async function enrichPrivacyRequest(\n sombra: Got,\n { id: rawId, ...rest }: EnrichPrivacyRequest,\n enricherId: string,\n index?: number,\n): Promise<boolean> {\n if (!rawId) {\n // error\n const msg = `Request ID must be provided to enricher request.${\n index ? ` Found error in row: ${index}` : ''\n }`;\n logger.error(colors.red(msg));\n throw new Error(msg);\n }\n\n const id = rawId.toLowerCase();\n\n // Pull out the identifiers\n const enrichedIdentifiers = Object.entries(rest).reduce(\n (acc, [key, value]) => {\n const values = uniq(splitCsvToList(value));\n return values.length === 0\n ? acc\n : Object.assign(acc, {\n [key]: uniq(splitCsvToList(value)).map((val) => ({\n value: key === 'email' ? val.toLowerCase() : val,\n })),\n });\n },\n {} as Record<string, string[]>,\n );\n\n // Make the GraphQL request\n try {\n await sombra\n .post('v1/enrich-identifiers', {\n headers: {\n 'x-transcend-request-id': id,\n 'x-transcend-enricher-id': enricherId,\n },\n json: {\n enrichedIdentifiers,\n },\n })\n .json();\n\n logger.error(colors.green(`Successfully enriched request: ${ADMIN_URL}${id}`));\n return true;\n } catch (err) {\n // skip if already enriched\n if (\n typeof err.response.body === 'string' &&\n err.response.body.includes('Cannot update a resolved RequestEnricher')\n ) {\n logger.warn(\n colors.magenta(\n `Skipped enrichment for request: ${ADMIN_URL}${id}, request is no longer in the enriching phase.`,\n ),\n );\n return false;\n }\n\n // error\n logger.error(\n colors.red(\n `Failed to enricher identifiers for request with id: ${ADMIN_URL}${id} - ${err.message} - ${err.response.body}`,\n ),\n );\n throw err;\n }\n}\n","import {\n buildTranscendGraphQLClient,\n createSombraGotInstance,\n makeGraphQLRequest,\n} from '@transcend-io/sdk';\nimport { map } from '@transcend-io/utils';\nimport colors from 'colors';\n\nimport { DEFAULT_TRANSCEND_API } from '../../constants.js';\nimport { logger } from '../../logger.js';\nimport { UPDATE_PRIVACY_REQUEST } from '../graphql/index.js';\nimport { readCsv } from '../requests/index.js';\nimport { enrichPrivacyRequest, EnrichPrivacyRequest } from './enrichPrivacyRequest.js';\n\n/**\n * Push a CSV of enriched requests back into Transcend\n *\n * @param options - Options\n * @returns Number of items processed\n */\nexport async function pushManualEnrichmentIdentifiersFromCsv({\n file,\n auth,\n sombraAuth,\n enricherId,\n markSilent,\n concurrency = 100,\n transcendUrl = DEFAULT_TRANSCEND_API,\n}: {\n /** CSV file path */\n file: string;\n /** Transcend API key authentication */\n auth: string;\n /** ID of enricher being uploaded to */\n enricherId: string;\n /** Sombra API key authentication */\n sombraAuth?: string;\n /** Concurrency */\n concurrency?: number;\n /** API URL for Transcend backend */\n transcendUrl?: string;\n /** Mark requests in silent mode before enriching */\n markSilent?: boolean;\n}): Promise<number> {\n // Create sombra instance to communicate with\n const sombra = await createSombraGotInstance(transcendUrl, auth, {\n logger,\n sombraApiKey: sombraAuth,\n sombraUrl: process.env.SOMBRA_URL,\n });\n const client = buildTranscendGraphQLClient(transcendUrl, auth);\n\n // Read from CSV\n logger.info(colors.magenta(`Reading \"${file}\" from disk`));\n const activeResults = readCsv(file, EnrichPrivacyRequest);\n\n // Notify Transcend\n logger.info(colors.magenta(`Enriching \"${activeResults.length}\" privacy requests.`));\n\n let successCount = 0;\n let skippedCount = 0;\n let errorCount = 0;\n\n await map(\n activeResults,\n async (request, index) => {\n try {\n // Mark requests in silent mode before a certain date\n if (markSilent) {\n await makeGraphQLRequest(client, UPDATE_PRIVACY_REQUEST, {\n variables: {\n input: {\n id: request.id,\n isSilent: true,\n },\n },\n logger,\n });\n\n logger.info(colors.magenta(`Mark request as silent mode - ${request.id}`));\n }\n\n const result = await enrichPrivacyRequest(sombra, request, enricherId, index);\n if (result) {\n successCount += 1;\n } else {\n skippedCount += 1;\n }\n } catch {\n errorCount += 1;\n }\n },\n { concurrency },\n );\n\n logger.info(colors.green(`Successfully notified Transcend! \\n Success count: ${successCount}.`));\n\n if (skippedCount > 0) {\n logger.info(colors.magenta(`Skipped count: ${skippedCount}.`));\n }\n\n if (errorCount > 0) {\n logger.info(colors.red(`Error Count: ${errorCount}.`));\n throw new Error(`Failed to enrich: ${errorCount} requests.`);\n }\n\n return activeResults.length;\n}\n"],"mappings":"qaAQA,MAAM,EAAY,+DAIL,EAAuB,EAAE,OAAO,EAAE,OAAQ,EAAE,OAAO,CAchE,eAAsB,EACpB,EACA,CAAE,GAAI,EAAO,GAAG,GAChB,EACA,EACkB,CAClB,GAAI,CAAC,EAAO,CAEV,IAAM,EAAM,mDACV,EAAQ,wBAAwB,IAAU,KAG5C,MADA,EAAO,MAAM,EAAO,IAAI,EAAI,CAAC,CACnB,MAAM,EAAI,CAGtB,IAAM,EAAK,EAAM,aAAa,CAGxB,EAAsB,OAAO,QAAQ,EAAK,CAAC,QAC9C,EAAK,CAAC,EAAK,KACK,EAAK,EAAe,EAAM,CAC5B,CAAC,SAAW,EACrB,EACA,OAAO,OAAO,EAAK,EAChB,GAAM,EAAK,EAAe,EAAM,CAAC,CAAC,IAAK,IAAS,CAC/C,MAAO,IAAQ,QAAU,EAAI,aAAa,CAAG,EAC9C,EAAE,CACJ,CAAC,CAER,EAAE,CACH,CAGD,GAAI,CAcF,OAbA,MAAM,EACH,KAAK,wBAAyB,CAC7B,QAAS,CACP,yBAA0B,EAC1B,0BAA2B,EAC5B,CACD,KAAM,CACJ,sBACD,CACF,CAAC,CACD,MAAM,CAET,EAAO,MAAM,EAAO,MAAM,kCAAkC,IAAY,IAAK,CAAC,CACvE,SACA,EAAK,CAEZ,GACE,OAAO,EAAI,SAAS,MAAS,UAC7B,EAAI,SAAS,KAAK,SAAS,2CAA2C,CAOtE,OALA,EAAO,KACL,EAAO,QACL,mCAAmC,IAAY,EAAG,gDACnD,CACF,CACM,GAST,MALA,EAAO,MACL,EAAO,IACL,uDAAuD,IAAY,EAAG,KAAK,EAAI,QAAQ,KAAK,EAAI,SAAS,OAC1G,CACF,CACK,GC1EV,eAAsB,EAAuC,CAC3D,OACA,OACA,aACA,aACA,aACA,cAAc,IACd,eAAe,GAgBG,CAElB,IAAM,EAAS,MAAM,EAAwB,EAAc,EAAM,CAC/D,SACA,aAAc,EACd,UAAW,QAAQ,IAAI,WACxB,CAAC,CACI,EAAS,EAA4B,EAAc,EAAK,CAG9D,EAAO,KAAK,EAAO,QAAQ,YAAY,EAAK,aAAa,CAAC,CAC1D,IAAM,EAAgB,EAAQ,EAAM,EAAqB,CAGzD,EAAO,KAAK,EAAO,QAAQ,cAAc,EAAc,OAAO,qBAAqB,CAAC,CAEpF,IAAI,EAAe,EACf,EAAe,EACf,EAAa,EAwCjB,GAtCA,MAAM,EACJ,EACA,MAAO,EAAS,IAAU,CACxB,GAAI,CAEE,IACF,MAAM,EAAmB,EAAQ,EAAwB,CACvD,UAAW,CACT,MAAO,CACL,GAAI,EAAQ,GACZ,SAAU,GACX,CACF,CACD,SACD,CAAC,CAEF,EAAO,KAAK,EAAO,QAAQ,iCAAiC,EAAQ,KAAK,CAAC,EAIxE,MADiB,EAAqB,EAAQ,EAAS,EAAY,EAAM,CAE3E,GAAgB,EAEhB,GAAgB,OAEZ,CACN,GAAc,IAGlB,CAAE,cAAa,CAChB,CAED,EAAO,KAAK,EAAO,MAAM,sDAAsD,EAAa,GAAG,CAAC,CAE5F,EAAe,GACjB,EAAO,KAAK,EAAO,QAAQ,kBAAkB,EAAa,GAAG,CAAC,CAG5D,EAAa,EAEf,MADA,EAAO,KAAK,EAAO,IAAI,gBAAgB,EAAW,GAAG,CAAC,CAC5C,MAAM,qBAAqB,EAAW,YAAY,CAG9D,OAAO,EAAc"}
@@ -1,2 +1,2 @@
1
- import{a as e}from"./constants-BCmVqlrM.mjs";import{t}from"./logger-CPu6zbyQ.mjs";import{r as n}from"./fetchAllRequests-BI0tFl_a.mjs";import{RequestStatus as r}from"@transcend-io/privacy-types";import i from"colors";import{map as a}from"@transcend-io/utils";import{REMOVE_REQUEST_IDENTIFIERS as o,buildTranscendGraphQLClient as s,fetchAllRequestIdentifierMetadata as c,makeGraphQLRequest as l}from"@transcend-io/sdk";import u from"cli-progress";async function d({requestActions:d,identifierNames:f,auth:p,concurrency:m=20,transcendUrl:h=e}){let g=s(h,p),_=new Date().getTime(),v=new u.SingleBar({},u.Presets.shades_classic),y=await n(g,{actions:d,statuses:[r.Enriching]});t.info(i.magenta(`Fetched requests in preflight/enriching state.`));let b=0,x=0;v.start(y.length,0),await a(y,async e=>{let n=(await c(g,{filterBy:{requestId:e.id},logger:t})).filter(({isVerifiedAtLeastOnce:e,name:t})=>e===!1&&f.includes(t)).map(({id:e})=>e);n.length>0&&(await l(g,o,{variables:{input:{requestId:e.id,requestIdentifierIds:n}},logger:t}),x+=n.length),b+=1,v.update(b)},{concurrency:m}),v.stop();let S=new Date().getTime()-_;return t.info(i.green(`Successfully cleared out unverified identifiers "${S/1e3}" seconds for ${b} requests, ${x} identifiers were cleared out!`)),y.length}export{d as t};
2
- //# sourceMappingURL=removeUnverifiedRequestIdentifiers-qS5A_eRZ.mjs.map
1
+ import{a as e}from"./constants-boymUaFC.mjs";import{t}from"./logger-CPu6zbyQ.mjs";import{r as n}from"./fetchAllRequests-BI0tFl_a.mjs";import{RequestStatus as r}from"@transcend-io/privacy-types";import i from"colors";import{map as a}from"@transcend-io/utils";import{REMOVE_REQUEST_IDENTIFIERS as o,buildTranscendGraphQLClient as s,fetchAllRequestIdentifierMetadata as c,makeGraphQLRequest as l}from"@transcend-io/sdk";import u from"cli-progress";async function d({requestActions:d,identifierNames:f,auth:p,concurrency:m=20,transcendUrl:h=e}){let g=s(h,p),_=new Date().getTime(),v=new u.SingleBar({},u.Presets.shades_classic),y=await n(g,{actions:d,statuses:[r.Enriching]});t.info(i.magenta(`Fetched requests in preflight/enriching state.`));let b=0,x=0;v.start(y.length,0),await a(y,async e=>{let n=(await c(g,{filterBy:{requestId:e.id},logger:t})).filter(({isVerifiedAtLeastOnce:e,name:t})=>e===!1&&f.includes(t)).map(({id:e})=>e);n.length>0&&(await l(g,o,{variables:{input:{requestId:e.id,requestIdentifierIds:n}},logger:t}),x+=n.length),b+=1,v.update(b)},{concurrency:m}),v.stop();let S=new Date().getTime()-_;return t.info(i.green(`Successfully cleared out unverified identifiers "${S/1e3}" seconds for ${b} requests, ${x} identifiers were cleared out!`)),y.length}export{d as t};
2
+ //# sourceMappingURL=removeUnverifiedRequestIdentifiers-B-Az2HJ8.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"removeUnverifiedRequestIdentifiers-qS5A_eRZ.mjs","names":[],"sources":["../src/lib/requests/removeUnverifiedRequestIdentifiers.ts"],"sourcesContent":["import { RequestAction, RequestStatus } from '@transcend-io/privacy-types';\nimport {\n buildTranscendGraphQLClient,\n fetchAllRequestIdentifierMetadata,\n makeGraphQLRequest,\n REMOVE_REQUEST_IDENTIFIERS,\n} from '@transcend-io/sdk';\nimport { map } from '@transcend-io/utils';\nimport cliProgress from 'cli-progress';\nimport colors from 'colors';\n\nimport { DEFAULT_TRANSCEND_API } from '../../constants.js';\nimport { logger } from '../../logger.js';\nimport { fetchAllRequests } from '../graphql/index.js';\n\n/**\n * Remove a set of unverified request identifier\n *\n * @param options - Options\n * @returns Number of items marked as completed\n */\nexport async function removeUnverifiedRequestIdentifiers({\n requestActions,\n identifierNames,\n auth,\n concurrency = 20,\n transcendUrl = DEFAULT_TRANSCEND_API,\n}: {\n /** The request actions that should be restarted */\n requestActions: RequestAction[];\n /** Transcend API key authentication */\n auth: string;\n /** The set of identifier names to remove */\n identifierNames: string[];\n /** Concurrency to upload requests in parallel */\n concurrency?: number;\n /** API URL for Transcend backend */\n transcendUrl?: string;\n}): Promise<number> {\n // Find all requests made before createdAt that are in a removing data state\n const client = buildTranscendGraphQLClient(transcendUrl, auth);\n\n // Time duration\n const t0 = new Date().getTime();\n // create a new progress bar instance and use shades_classic theme\n const progressBar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic);\n\n // Pull in the requests\n const allRequests = await fetchAllRequests(client, {\n actions: requestActions,\n statuses: [RequestStatus.Enriching],\n });\n\n // Notify Transcend\n logger.info(colors.magenta('Fetched requests in preflight/enriching state.'));\n\n let total = 0;\n let processed = 0;\n progressBar.start(allRequests.length, 0);\n await map(\n allRequests,\n async (requestToRestart) => {\n const requestIdentifiers = await fetchAllRequestIdentifierMetadata(client, {\n filterBy: { requestId: requestToRestart.id },\n logger,\n });\n const clearOut = requestIdentifiers\n .filter(\n ({ isVerifiedAtLeastOnce, name }) =>\n isVerifiedAtLeastOnce === false && identifierNames.includes(name),\n )\n .map(({ id }) => id);\n\n if (clearOut.length > 0) {\n await makeGraphQLRequest<{\n /** Whether we successfully uploaded the results */\n success: boolean;\n }>(client, REMOVE_REQUEST_IDENTIFIERS, {\n variables: {\n input: {\n requestId: requestToRestart.id,\n requestIdentifierIds: clearOut,\n },\n },\n logger,\n });\n processed += clearOut.length;\n }\n\n total += 1;\n progressBar.update(total);\n },\n { concurrency },\n );\n\n progressBar.stop();\n const t1 = new Date().getTime();\n const totalTime = t1 - t0;\n\n logger.info(\n colors.green(\n `Successfully cleared out unverified identifiers \"${\n totalTime / 1000\n }\" seconds for ${total} requests, ${processed} identifiers were cleared out!`,\n ),\n );\n return allRequests.length;\n}\n"],"mappings":"6bAqBA,eAAsB,EAAmC,CACvD,iBACA,kBACA,OACA,cAAc,GACd,eAAe,GAYG,CAElB,IAAM,EAAS,EAA4B,EAAc,EAAK,CAGxD,EAAK,IAAI,MAAM,CAAC,SAAS,CAEzB,EAAc,IAAI,EAAY,UAAU,EAAE,CAAE,EAAY,QAAQ,eAAe,CAG/E,EAAc,MAAM,EAAiB,EAAQ,CACjD,QAAS,EACT,SAAU,CAAC,EAAc,UAAU,CACpC,CAAC,CAGF,EAAO,KAAK,EAAO,QAAQ,iDAAiD,CAAC,CAE7E,IAAI,EAAQ,EACR,EAAY,EAChB,EAAY,MAAM,EAAY,OAAQ,EAAE,CACxC,MAAM,EACJ,EACA,KAAO,IAAqB,CAK1B,IAAM,GAAW,MAJgB,EAAkC,EAAQ,CACzE,SAAU,CAAE,UAAW,EAAiB,GAAI,CAC5C,SACD,CAAC,EAEC,QACE,CAAE,wBAAuB,UACxB,IAA0B,IAAS,EAAgB,SAAS,EAAK,CACpE,CACA,KAAK,CAAE,QAAS,EAAG,CAElB,EAAS,OAAS,IACpB,MAAM,EAGH,EAAQ,EAA4B,CACrC,UAAW,CACT,MAAO,CACL,UAAW,EAAiB,GAC5B,qBAAsB,EACvB,CACF,CACD,SACD,CAAC,CACF,GAAa,EAAS,QAGxB,GAAS,EACT,EAAY,OAAO,EAAM,EAE3B,CAAE,cAAa,CAChB,CAED,EAAY,MAAM,CAElB,IAAM,EADK,IAAI,MAAM,CAAC,SACF,CAAG,EASvB,OAPA,EAAO,KACL,EAAO,MACL,oDACE,EAAY,IACb,gBAAgB,EAAM,aAAa,EAAU,gCAC/C,CACF,CACM,EAAY"}
1
+ {"version":3,"file":"removeUnverifiedRequestIdentifiers-B-Az2HJ8.mjs","names":[],"sources":["../src/lib/requests/removeUnverifiedRequestIdentifiers.ts"],"sourcesContent":["import { RequestAction, RequestStatus } from '@transcend-io/privacy-types';\nimport {\n buildTranscendGraphQLClient,\n fetchAllRequestIdentifierMetadata,\n makeGraphQLRequest,\n REMOVE_REQUEST_IDENTIFIERS,\n} from '@transcend-io/sdk';\nimport { map } from '@transcend-io/utils';\nimport cliProgress from 'cli-progress';\nimport colors from 'colors';\n\nimport { DEFAULT_TRANSCEND_API } from '../../constants.js';\nimport { logger } from '../../logger.js';\nimport { fetchAllRequests } from '../graphql/index.js';\n\n/**\n * Remove a set of unverified request identifier\n *\n * @param options - Options\n * @returns Number of items marked as completed\n */\nexport async function removeUnverifiedRequestIdentifiers({\n requestActions,\n identifierNames,\n auth,\n concurrency = 20,\n transcendUrl = DEFAULT_TRANSCEND_API,\n}: {\n /** The request actions that should be restarted */\n requestActions: RequestAction[];\n /** Transcend API key authentication */\n auth: string;\n /** The set of identifier names to remove */\n identifierNames: string[];\n /** Concurrency to upload requests in parallel */\n concurrency?: number;\n /** API URL for Transcend backend */\n transcendUrl?: string;\n}): Promise<number> {\n // Find all requests made before createdAt that are in a removing data state\n const client = buildTranscendGraphQLClient(transcendUrl, auth);\n\n // Time duration\n const t0 = new Date().getTime();\n // create a new progress bar instance and use shades_classic theme\n const progressBar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic);\n\n // Pull in the requests\n const allRequests = await fetchAllRequests(client, {\n actions: requestActions,\n statuses: [RequestStatus.Enriching],\n });\n\n // Notify Transcend\n logger.info(colors.magenta('Fetched requests in preflight/enriching state.'));\n\n let total = 0;\n let processed = 0;\n progressBar.start(allRequests.length, 0);\n await map(\n allRequests,\n async (requestToRestart) => {\n const requestIdentifiers = await fetchAllRequestIdentifierMetadata(client, {\n filterBy: { requestId: requestToRestart.id },\n logger,\n });\n const clearOut = requestIdentifiers\n .filter(\n ({ isVerifiedAtLeastOnce, name }) =>\n isVerifiedAtLeastOnce === false && identifierNames.includes(name),\n )\n .map(({ id }) => id);\n\n if (clearOut.length > 0) {\n await makeGraphQLRequest<{\n /** Whether we successfully uploaded the results */\n success: boolean;\n }>(client, REMOVE_REQUEST_IDENTIFIERS, {\n variables: {\n input: {\n requestId: requestToRestart.id,\n requestIdentifierIds: clearOut,\n },\n },\n logger,\n });\n processed += clearOut.length;\n }\n\n total += 1;\n progressBar.update(total);\n },\n { concurrency },\n );\n\n progressBar.stop();\n const t1 = new Date().getTime();\n const totalTime = t1 - t0;\n\n logger.info(\n colors.green(\n `Successfully cleared out unverified identifiers \"${\n totalTime / 1000\n }\" seconds for ${total} requests, ${processed} identifiers were cleared out!`,\n ),\n );\n return allRequests.length;\n}\n"],"mappings":"6bAqBA,eAAsB,EAAmC,CACvD,iBACA,kBACA,OACA,cAAc,GACd,eAAe,GAYG,CAElB,IAAM,EAAS,EAA4B,EAAc,EAAK,CAGxD,EAAK,IAAI,MAAM,CAAC,SAAS,CAEzB,EAAc,IAAI,EAAY,UAAU,EAAE,CAAE,EAAY,QAAQ,eAAe,CAG/E,EAAc,MAAM,EAAiB,EAAQ,CACjD,QAAS,EACT,SAAU,CAAC,EAAc,UAAU,CACpC,CAAC,CAGF,EAAO,KAAK,EAAO,QAAQ,iDAAiD,CAAC,CAE7E,IAAI,EAAQ,EACR,EAAY,EAChB,EAAY,MAAM,EAAY,OAAQ,EAAE,CACxC,MAAM,EACJ,EACA,KAAO,IAAqB,CAK1B,IAAM,GAAW,MAJgB,EAAkC,EAAQ,CACzE,SAAU,CAAE,UAAW,EAAiB,GAAI,CAC5C,SACD,CAAC,EAEC,QACE,CAAE,wBAAuB,UACxB,IAA0B,IAAS,EAAgB,SAAS,EAAK,CACpE,CACA,KAAK,CAAE,QAAS,EAAG,CAElB,EAAS,OAAS,IACpB,MAAM,EAGH,EAAQ,EAA4B,CACrC,UAAW,CACT,MAAO,CACL,UAAW,EAAiB,GAC5B,qBAAsB,EACvB,CACF,CACD,SACD,CAAC,CACF,GAAa,EAAS,QAGxB,GAAS,EACT,EAAY,OAAO,EAAM,EAE3B,CAAE,cAAa,CAChB,CAED,EAAY,MAAM,CAElB,IAAM,EADK,IAAI,MAAM,CAAC,SACF,CAAG,EASvB,OAPA,EAAO,KACL,EAAO,MACL,oDACE,EAAY,IACb,gBAAgB,EAAM,aAAa,EAAU,gCAC/C,CACF,CACM,EAAY"}
@@ -1,2 +1,2 @@
1
- import{a as e}from"./constants-BCmVqlrM.mjs";import{t}from"./logger-CPu6zbyQ.mjs";import{r as n}from"./fetchAllRequests-BI0tFl_a.mjs";import{RequestStatus as r}from"@transcend-io/privacy-types";import i from"colors";import{map as a}from"@transcend-io/utils";import{RETRY_REQUEST_DATA_SILO as o,buildTranscendGraphQLClient as s,fetchRequestDataSilo as c,makeGraphQLRequest as l}from"@transcend-io/sdk";import u from"cli-progress";async function d({requestActions:d,dataSiloId:f,auth:p,concurrency:m=20,transcendUrl:h=e}){let g=s(h,p),_=new Date().getTime(),v=new u.SingleBar({},u.Presets.shades_classic),y=await n(g,{actions:d,statuses:[r.Compiling,r.Approving]});t.info(i.magenta(`Retrying requests for Data Silo: "${f}", restarting "${y.length}" requests.`));let b=0,x=0;v.start(y.length,0),await a(y,async e=>{try{await l(g,o,{variables:{requestDataSiloId:(await c(g,{logger:t,filterBy:{requestId:e.id,dataSiloId:f}})).id},logger:t})}catch(e){if(!e.message.includes(`Failed to find RequestDataSilo`))throw e;x+=1}b+=1,v.update(b)},{concurrency:m}),v.stop();let S=new Date().getTime()-_;return t.info(i.green(`Successfully notified Transcend in "${S/1e3}" seconds for ${b} requests, ${x} requests were skipped because data silo was not attached to the request!`)),y.length}export{d as t};
2
- //# sourceMappingURL=retryRequestDataSilos-Cxaka-kA.mjs.map
1
+ import{a as e}from"./constants-boymUaFC.mjs";import{t}from"./logger-CPu6zbyQ.mjs";import{r as n}from"./fetchAllRequests-BI0tFl_a.mjs";import{RequestStatus as r}from"@transcend-io/privacy-types";import i from"colors";import{map as a}from"@transcend-io/utils";import{RETRY_REQUEST_DATA_SILO as o,buildTranscendGraphQLClient as s,fetchRequestDataSilo as c,makeGraphQLRequest as l}from"@transcend-io/sdk";import u from"cli-progress";async function d({requestActions:d,dataSiloId:f,auth:p,concurrency:m=20,transcendUrl:h=e}){let g=s(h,p),_=new Date().getTime(),v=new u.SingleBar({},u.Presets.shades_classic),y=await n(g,{actions:d,statuses:[r.Compiling,r.Approving]});t.info(i.magenta(`Retrying requests for Data Silo: "${f}", restarting "${y.length}" requests.`));let b=0,x=0;v.start(y.length,0),await a(y,async e=>{try{await l(g,o,{variables:{requestDataSiloId:(await c(g,{logger:t,filterBy:{requestId:e.id,dataSiloId:f}})).id},logger:t})}catch(e){if(!e.message.includes(`Failed to find RequestDataSilo`))throw e;x+=1}b+=1,v.update(b)},{concurrency:m}),v.stop();let S=new Date().getTime()-_;return t.info(i.green(`Successfully notified Transcend in "${S/1e3}" seconds for ${b} requests, ${x} requests were skipped because data silo was not attached to the request!`)),y.length}export{d as t};
2
+ //# sourceMappingURL=retryRequestDataSilos-D70pC9JF.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"retryRequestDataSilos-Cxaka-kA.mjs","names":[],"sources":["../src/lib/requests/retryRequestDataSilos.ts"],"sourcesContent":["import { RequestAction, RequestStatus } from '@transcend-io/privacy-types';\nimport {\n buildTranscendGraphQLClient,\n makeGraphQLRequest,\n RETRY_REQUEST_DATA_SILO,\n fetchRequestDataSilo,\n} from '@transcend-io/sdk';\nimport { map } from '@transcend-io/utils';\nimport cliProgress from 'cli-progress';\nimport colors from 'colors';\n\nimport { DEFAULT_TRANSCEND_API } from '../../constants.js';\nimport { logger } from '../../logger.js';\nimport { fetchAllRequests } from '../graphql/index.js';\n\n/**\n * Retry a set of RequestDataSilos\n *\n * @param options - Options\n * @returns Number of items marked as completed\n */\nexport async function retryRequestDataSilos({\n requestActions,\n dataSiloId,\n auth,\n concurrency = 20,\n transcendUrl = DEFAULT_TRANSCEND_API,\n}: {\n /** The request actions that should be restarted */\n requestActions: RequestAction[];\n /** Transcend API key authentication */\n auth: string;\n /** Data Silo ID to pull down jobs for */\n dataSiloId: string;\n /** Concurrency to upload requests in parallel */\n concurrency?: number;\n /** API URL for Transcend backend */\n transcendUrl?: string;\n}): Promise<number> {\n // Find all requests made before createdAt that are in a removing data state\n const client = buildTranscendGraphQLClient(transcendUrl, auth);\n\n // Time duration\n const t0 = new Date().getTime();\n // create a new progress bar instance and use shades_classic theme\n const progressBar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic);\n\n // Pull in the requests\n const allRequests = await fetchAllRequests(client, {\n actions: requestActions,\n statuses: [RequestStatus.Compiling, RequestStatus.Approving],\n });\n\n // Notify Transcend\n logger.info(\n colors.magenta(\n `Retrying requests for Data Silo: \"${dataSiloId}\", restarting \"${allRequests.length}\" requests.`,\n ),\n );\n\n let total = 0;\n let skipped = 0;\n progressBar.start(allRequests.length, 0);\n await map(\n allRequests,\n async (requestToRestart) => {\n try {\n const requestDataSilo = await fetchRequestDataSilo(client, {\n logger,\n filterBy: { requestId: requestToRestart.id, dataSiloId },\n });\n\n await makeGraphQLRequest<{\n /** Whether we successfully uploaded the results */\n success: boolean;\n }>(client, RETRY_REQUEST_DATA_SILO, {\n variables: { requestDataSiloId: requestDataSilo.id },\n logger,\n });\n } catch (err) {\n // some requests may not have this data silo connected\n if (!err.message.includes('Failed to find RequestDataSilo')) {\n throw err;\n }\n skipped += 1;\n }\n\n total += 1;\n progressBar.update(total);\n },\n { concurrency },\n );\n\n progressBar.stop();\n const t1 = new Date().getTime();\n const totalTime = t1 - t0;\n\n logger.info(\n colors.green(\n `Successfully notified Transcend in \"${\n totalTime / 1000\n }\" seconds for ${total} requests, ${skipped} requests were skipped because data silo was not attached to the request!`,\n ),\n );\n return allRequests.length;\n}\n"],"mappings":"6aAqBA,eAAsB,EAAsB,CAC1C,iBACA,aACA,OACA,cAAc,GACd,eAAe,GAYG,CAElB,IAAM,EAAS,EAA4B,EAAc,EAAK,CAGxD,EAAK,IAAI,MAAM,CAAC,SAAS,CAEzB,EAAc,IAAI,EAAY,UAAU,EAAE,CAAE,EAAY,QAAQ,eAAe,CAG/E,EAAc,MAAM,EAAiB,EAAQ,CACjD,QAAS,EACT,SAAU,CAAC,EAAc,UAAW,EAAc,UAAU,CAC7D,CAAC,CAGF,EAAO,KACL,EAAO,QACL,qCAAqC,EAAW,iBAAiB,EAAY,OAAO,aACrF,CACF,CAED,IAAI,EAAQ,EACR,EAAU,EACd,EAAY,MAAM,EAAY,OAAQ,EAAE,CACxC,MAAM,EACJ,EACA,KAAO,IAAqB,CAC1B,GAAI,CAMF,MAAM,EAGH,EAAQ,EAAyB,CAClC,UAAW,CAAE,mBAAmB,MATJ,EAAqB,EAAQ,CACzD,SACA,SAAU,CAAE,UAAW,EAAiB,GAAI,aAAY,CACzD,CAAC,EAMgD,GAAI,CACpD,SACD,CAAC,OACK,EAAK,CAEZ,GAAI,CAAC,EAAI,QAAQ,SAAS,iCAAiC,CACzD,MAAM,EAER,GAAW,EAGb,GAAS,EACT,EAAY,OAAO,EAAM,EAE3B,CAAE,cAAa,CAChB,CAED,EAAY,MAAM,CAElB,IAAM,EADK,IAAI,MAAM,CAAC,SACF,CAAG,EASvB,OAPA,EAAO,KACL,EAAO,MACL,uCACE,EAAY,IACb,gBAAgB,EAAM,aAAa,EAAQ,2EAC7C,CACF,CACM,EAAY"}
1
+ {"version":3,"file":"retryRequestDataSilos-D70pC9JF.mjs","names":[],"sources":["../src/lib/requests/retryRequestDataSilos.ts"],"sourcesContent":["import { RequestAction, RequestStatus } from '@transcend-io/privacy-types';\nimport {\n buildTranscendGraphQLClient,\n makeGraphQLRequest,\n RETRY_REQUEST_DATA_SILO,\n fetchRequestDataSilo,\n} from '@transcend-io/sdk';\nimport { map } from '@transcend-io/utils';\nimport cliProgress from 'cli-progress';\nimport colors from 'colors';\n\nimport { DEFAULT_TRANSCEND_API } from '../../constants.js';\nimport { logger } from '../../logger.js';\nimport { fetchAllRequests } from '../graphql/index.js';\n\n/**\n * Retry a set of RequestDataSilos\n *\n * @param options - Options\n * @returns Number of items marked as completed\n */\nexport async function retryRequestDataSilos({\n requestActions,\n dataSiloId,\n auth,\n concurrency = 20,\n transcendUrl = DEFAULT_TRANSCEND_API,\n}: {\n /** The request actions that should be restarted */\n requestActions: RequestAction[];\n /** Transcend API key authentication */\n auth: string;\n /** Data Silo ID to pull down jobs for */\n dataSiloId: string;\n /** Concurrency to upload requests in parallel */\n concurrency?: number;\n /** API URL for Transcend backend */\n transcendUrl?: string;\n}): Promise<number> {\n // Find all requests made before createdAt that are in a removing data state\n const client = buildTranscendGraphQLClient(transcendUrl, auth);\n\n // Time duration\n const t0 = new Date().getTime();\n // create a new progress bar instance and use shades_classic theme\n const progressBar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic);\n\n // Pull in the requests\n const allRequests = await fetchAllRequests(client, {\n actions: requestActions,\n statuses: [RequestStatus.Compiling, RequestStatus.Approving],\n });\n\n // Notify Transcend\n logger.info(\n colors.magenta(\n `Retrying requests for Data Silo: \"${dataSiloId}\", restarting \"${allRequests.length}\" requests.`,\n ),\n );\n\n let total = 0;\n let skipped = 0;\n progressBar.start(allRequests.length, 0);\n await map(\n allRequests,\n async (requestToRestart) => {\n try {\n const requestDataSilo = await fetchRequestDataSilo(client, {\n logger,\n filterBy: { requestId: requestToRestart.id, dataSiloId },\n });\n\n await makeGraphQLRequest<{\n /** Whether we successfully uploaded the results */\n success: boolean;\n }>(client, RETRY_REQUEST_DATA_SILO, {\n variables: { requestDataSiloId: requestDataSilo.id },\n logger,\n });\n } catch (err) {\n // some requests may not have this data silo connected\n if (!err.message.includes('Failed to find RequestDataSilo')) {\n throw err;\n }\n skipped += 1;\n }\n\n total += 1;\n progressBar.update(total);\n },\n { concurrency },\n );\n\n progressBar.stop();\n const t1 = new Date().getTime();\n const totalTime = t1 - t0;\n\n logger.info(\n colors.green(\n `Successfully notified Transcend in \"${\n totalTime / 1000\n }\" seconds for ${total} requests, ${skipped} requests were skipped because data silo was not attached to the request!`,\n ),\n );\n return allRequests.length;\n}\n"],"mappings":"6aAqBA,eAAsB,EAAsB,CAC1C,iBACA,aACA,OACA,cAAc,GACd,eAAe,GAYG,CAElB,IAAM,EAAS,EAA4B,EAAc,EAAK,CAGxD,EAAK,IAAI,MAAM,CAAC,SAAS,CAEzB,EAAc,IAAI,EAAY,UAAU,EAAE,CAAE,EAAY,QAAQ,eAAe,CAG/E,EAAc,MAAM,EAAiB,EAAQ,CACjD,QAAS,EACT,SAAU,CAAC,EAAc,UAAW,EAAc,UAAU,CAC7D,CAAC,CAGF,EAAO,KACL,EAAO,QACL,qCAAqC,EAAW,iBAAiB,EAAY,OAAO,aACrF,CACF,CAED,IAAI,EAAQ,EACR,EAAU,EACd,EAAY,MAAM,EAAY,OAAQ,EAAE,CACxC,MAAM,EACJ,EACA,KAAO,IAAqB,CAC1B,GAAI,CAMF,MAAM,EAGH,EAAQ,EAAyB,CAClC,UAAW,CAAE,mBAAmB,MATJ,EAAqB,EAAQ,CACzD,SACA,SAAU,CAAE,UAAW,EAAiB,GAAI,aAAY,CACzD,CAAC,EAMgD,GAAI,CACpD,SACD,CAAC,OACK,EAAK,CAEZ,GAAI,CAAC,EAAI,QAAQ,SAAS,iCAAiC,CACzD,MAAM,EAER,GAAW,EAGb,GAAS,EACT,EAAY,OAAO,EAAM,EAE3B,CAAE,cAAa,CAChB,CAED,EAAY,MAAM,CAElB,IAAM,EADK,IAAI,MAAM,CAAC,SACF,CAAG,EASvB,OAPA,EAAO,KACL,EAAO,MACL,uCACE,EAAY,IACb,gBAAgB,EAAM,aAAa,EAAQ,2EAC7C,CACF,CACM,EAAY"}
@@ -1,2 +1,2 @@
1
- import{a as e}from"./constants-BCmVqlrM.mjs";import{t}from"./logger-CPu6zbyQ.mjs";import{r as n}from"./fetchAllRequests-BI0tFl_a.mjs";import{RequestEnricherStatus as r,RequestStatus as i}from"@transcend-io/privacy-types";import a from"colors";import{map as o,mapSeries as s}from"@transcend-io/utils";import{SKIP_REQUEST_ENRICHER as c,buildTranscendGraphQLClient as l,fetchAllRequestEnrichers as u,makeGraphQLRequest as d}from"@transcend-io/sdk";import f from"cli-progress";async function p({enricherIds:p,auth:m,concurrency:h=100,transcendUrl:g=e}){let _=l(g,m),v=new Date().getTime(),y=await n(_,{statuses:[i.Enriching]});t.info(a.magenta(`Processing enricher: "${p.join(`,`)}" fetched "${y.length}" in enriching status.`));let b=new f.SingleBar({},f.Presets.shades_classic),x=0;b.start(y.length,0);let S=0;await o(y,async e=>{let n=(await u(_,{filterBy:{requestId:e.id},logger:t})).filter(e=>p.includes(e.enricher.id)&&![r.Resolved,r.Skipped].includes(e.status));n.length>0&&await s(n,async e=>{try{await d(_,c,{variables:{requestEnricherId:e.id},logger:t}),S+=1}catch(e){if(!e.message.includes(`Client error: Cannot skip Request enricher because it has already completed`))throw e}}),x+=1,b.update(x)},{concurrency:h}),b.stop();let C=new Date().getTime()-v;return t.info(a.green(`Successfully skipped "${S}" for "${y.length}" requests in "${C/1e3}" seconds!`)),y.length}export{p as t};
2
- //# sourceMappingURL=skipPreflightJobs-FdIm_Imj.mjs.map
1
+ import{a as e}from"./constants-boymUaFC.mjs";import{t}from"./logger-CPu6zbyQ.mjs";import{r as n}from"./fetchAllRequests-BI0tFl_a.mjs";import{RequestEnricherStatus as r,RequestStatus as i}from"@transcend-io/privacy-types";import a from"colors";import{map as o,mapSeries as s}from"@transcend-io/utils";import{SKIP_REQUEST_ENRICHER as c,buildTranscendGraphQLClient as l,fetchAllRequestEnrichers as u,makeGraphQLRequest as d}from"@transcend-io/sdk";import f from"cli-progress";async function p({enricherIds:p,auth:m,concurrency:h=100,transcendUrl:g=e}){let _=l(g,m),v=new Date().getTime(),y=await n(_,{statuses:[i.Enriching]});t.info(a.magenta(`Processing enricher: "${p.join(`,`)}" fetched "${y.length}" in enriching status.`));let b=new f.SingleBar({},f.Presets.shades_classic),x=0;b.start(y.length,0);let S=0;await o(y,async e=>{let n=(await u(_,{filterBy:{requestId:e.id},logger:t})).filter(e=>p.includes(e.enricher.id)&&![r.Resolved,r.Skipped].includes(e.status));n.length>0&&await s(n,async e=>{try{await d(_,c,{variables:{requestEnricherId:e.id},logger:t}),S+=1}catch(e){if(!e.message.includes(`Client error: Cannot skip Request enricher because it has already completed`))throw e}}),x+=1,b.update(x)},{concurrency:h}),b.stop();let C=new Date().getTime()-v;return t.info(a.green(`Successfully skipped "${S}" for "${y.length}" requests in "${C/1e3}" seconds!`)),y.length}export{p as t};
2
+ //# sourceMappingURL=skipPreflightJobs-B0YG7mhP.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"skipPreflightJobs-FdIm_Imj.mjs","names":[],"sources":["../src/lib/requests/skipPreflightJobs.ts"],"sourcesContent":["import { RequestEnricherStatus, RequestStatus } from '@transcend-io/privacy-types';\nimport {\n buildTranscendGraphQLClient,\n fetchAllRequestEnrichers,\n makeGraphQLRequest,\n SKIP_REQUEST_ENRICHER,\n} from '@transcend-io/sdk';\nimport { mapSeries, map } from '@transcend-io/utils';\nimport cliProgress from 'cli-progress';\nimport colors from 'colors';\n\nimport { DEFAULT_TRANSCEND_API } from '../../constants.js';\nimport { logger } from '../../logger.js';\nimport { fetchAllRequests } from '../graphql/index.js';\n\n/**\n * Given an enricher ID, mark all open request enrichers as skipped\n *\n * @param options - Options\n * @returns Number of items skipped\n */\nexport async function skipPreflightJobs({\n enricherIds,\n auth,\n concurrency = 100,\n transcendUrl = DEFAULT_TRANSCEND_API,\n}: {\n /** Transcend API key authentication */\n auth: string;\n /** Enricher IDs to pull down jobs for */\n enricherIds: string[];\n /** Upload concurrency */\n concurrency?: number;\n /** API URL for Transcend backend */\n transcendUrl?: string;\n /** Request statuses to mark as completed */\n requestStatuses?: RequestStatus[];\n}): Promise<number> {\n // Find all requests made before createdAt that are in a removing data state\n const client = buildTranscendGraphQLClient(transcendUrl, auth);\n\n // Time duration\n const t0 = new Date().getTime();\n\n // fetch all RequestDataSilos that are open\n const requests = await fetchAllRequests(client, {\n statuses: [RequestStatus.Enriching],\n });\n\n // Notify Transcend\n logger.info(\n colors.magenta(\n `Processing enricher: \"${enricherIds.join(',')}\" fetched \"${\n requests.length\n }\" in enriching status.`,\n ),\n );\n\n // create a new progress bar instance and use shades_classic theme\n const progressBar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic);\n\n let total = 0;\n progressBar.start(requests.length, 0);\n let totalSkipped = 0;\n await map(\n requests,\n async (request) => {\n // TODO dont pull all in\n const requestEnrichers = await fetchAllRequestEnrichers(client, {\n filterBy: { requestId: request.id },\n logger,\n });\n const requestEnrichersFiltered = requestEnrichers.filter(\n (enricher) =>\n enricherIds.includes(enricher.enricher.id) &&\n ![\n RequestEnricherStatus.Resolved,\n RequestEnricherStatus.Skipped,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ].includes(enricher.status as any),\n );\n\n // TODO\n if (requestEnrichersFiltered.length > 0) {\n await mapSeries(requestEnrichersFiltered, async (requestEnricher) => {\n try {\n await makeGraphQLRequest<{\n /** Whether we successfully uploaded the results */\n success: boolean;\n }>(client, SKIP_REQUEST_ENRICHER, {\n variables: { requestEnricherId: requestEnricher.id },\n logger,\n });\n totalSkipped += 1;\n } catch (err) {\n if (\n !err.message.includes(\n 'Client error: Cannot skip Request enricher because it has already completed',\n )\n ) {\n throw err;\n }\n }\n });\n }\n total += 1;\n progressBar.update(total);\n },\n { concurrency },\n );\n\n progressBar.stop();\n const t1 = new Date().getTime();\n const totalTime = t1 - t0;\n\n logger.info(\n colors.green(\n `Successfully skipped \"${totalSkipped}\" for \"${\n requests.length\n }\" requests in \"${totalTime / 1000}\" seconds!`,\n ),\n );\n return requests.length;\n}\n"],"mappings":"ydAqBA,eAAsB,EAAkB,CACtC,cACA,OACA,cAAc,IACd,eAAe,GAYG,CAElB,IAAM,EAAS,EAA4B,EAAc,EAAK,CAGxD,EAAK,IAAI,MAAM,CAAC,SAAS,CAGzB,EAAW,MAAM,EAAiB,EAAQ,CAC9C,SAAU,CAAC,EAAc,UAAU,CACpC,CAAC,CAGF,EAAO,KACL,EAAO,QACL,yBAAyB,EAAY,KAAK,IAAI,CAAC,aAC7C,EAAS,OACV,wBACF,CACF,CAGD,IAAM,EAAc,IAAI,EAAY,UAAU,EAAE,CAAE,EAAY,QAAQ,eAAe,CAEjF,EAAQ,EACZ,EAAY,MAAM,EAAS,OAAQ,EAAE,CACrC,IAAI,EAAe,EACnB,MAAM,EACJ,EACA,KAAO,IAAY,CAMjB,IAAM,GAA2B,MAJF,EAAyB,EAAQ,CAC9D,SAAU,CAAE,UAAW,EAAQ,GAAI,CACnC,SACD,CAAC,EACgD,OAC/C,GACC,EAAY,SAAS,EAAS,SAAS,GAAG,EAC1C,CAAC,CACC,EAAsB,SACtB,EAAsB,QAEvB,CAAC,SAAS,EAAS,OAAc,CACrC,CAGG,EAAyB,OAAS,GACpC,MAAM,EAAU,EAA0B,KAAO,IAAoB,CACnE,GAAI,CACF,MAAM,EAGH,EAAQ,EAAuB,CAChC,UAAW,CAAE,kBAAmB,EAAgB,GAAI,CACpD,SACD,CAAC,CACF,GAAgB,QACT,EAAK,CACZ,GACE,CAAC,EAAI,QAAQ,SACX,8EACD,CAED,MAAM,IAGV,CAEJ,GAAS,EACT,EAAY,OAAO,EAAM,EAE3B,CAAE,cAAa,CAChB,CAED,EAAY,MAAM,CAElB,IAAM,EADK,IAAI,MAAM,CAAC,SACF,CAAG,EASvB,OAPA,EAAO,KACL,EAAO,MACL,yBAAyB,EAAa,UACpC,EAAS,OACV,iBAAiB,EAAY,IAAK,YACpC,CACF,CACM,EAAS"}
1
+ {"version":3,"file":"skipPreflightJobs-B0YG7mhP.mjs","names":[],"sources":["../src/lib/requests/skipPreflightJobs.ts"],"sourcesContent":["import { RequestEnricherStatus, RequestStatus } from '@transcend-io/privacy-types';\nimport {\n buildTranscendGraphQLClient,\n fetchAllRequestEnrichers,\n makeGraphQLRequest,\n SKIP_REQUEST_ENRICHER,\n} from '@transcend-io/sdk';\nimport { mapSeries, map } from '@transcend-io/utils';\nimport cliProgress from 'cli-progress';\nimport colors from 'colors';\n\nimport { DEFAULT_TRANSCEND_API } from '../../constants.js';\nimport { logger } from '../../logger.js';\nimport { fetchAllRequests } from '../graphql/index.js';\n\n/**\n * Given an enricher ID, mark all open request enrichers as skipped\n *\n * @param options - Options\n * @returns Number of items skipped\n */\nexport async function skipPreflightJobs({\n enricherIds,\n auth,\n concurrency = 100,\n transcendUrl = DEFAULT_TRANSCEND_API,\n}: {\n /** Transcend API key authentication */\n auth: string;\n /** Enricher IDs to pull down jobs for */\n enricherIds: string[];\n /** Upload concurrency */\n concurrency?: number;\n /** API URL for Transcend backend */\n transcendUrl?: string;\n /** Request statuses to mark as completed */\n requestStatuses?: RequestStatus[];\n}): Promise<number> {\n // Find all requests made before createdAt that are in a removing data state\n const client = buildTranscendGraphQLClient(transcendUrl, auth);\n\n // Time duration\n const t0 = new Date().getTime();\n\n // fetch all RequestDataSilos that are open\n const requests = await fetchAllRequests(client, {\n statuses: [RequestStatus.Enriching],\n });\n\n // Notify Transcend\n logger.info(\n colors.magenta(\n `Processing enricher: \"${enricherIds.join(',')}\" fetched \"${\n requests.length\n }\" in enriching status.`,\n ),\n );\n\n // create a new progress bar instance and use shades_classic theme\n const progressBar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic);\n\n let total = 0;\n progressBar.start(requests.length, 0);\n let totalSkipped = 0;\n await map(\n requests,\n async (request) => {\n // TODO dont pull all in\n const requestEnrichers = await fetchAllRequestEnrichers(client, {\n filterBy: { requestId: request.id },\n logger,\n });\n const requestEnrichersFiltered = requestEnrichers.filter(\n (enricher) =>\n enricherIds.includes(enricher.enricher.id) &&\n ![\n RequestEnricherStatus.Resolved,\n RequestEnricherStatus.Skipped,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ].includes(enricher.status as any),\n );\n\n // TODO\n if (requestEnrichersFiltered.length > 0) {\n await mapSeries(requestEnrichersFiltered, async (requestEnricher) => {\n try {\n await makeGraphQLRequest<{\n /** Whether we successfully uploaded the results */\n success: boolean;\n }>(client, SKIP_REQUEST_ENRICHER, {\n variables: { requestEnricherId: requestEnricher.id },\n logger,\n });\n totalSkipped += 1;\n } catch (err) {\n if (\n !err.message.includes(\n 'Client error: Cannot skip Request enricher because it has already completed',\n )\n ) {\n throw err;\n }\n }\n });\n }\n total += 1;\n progressBar.update(total);\n },\n { concurrency },\n );\n\n progressBar.stop();\n const t1 = new Date().getTime();\n const totalTime = t1 - t0;\n\n logger.info(\n colors.green(\n `Successfully skipped \"${totalSkipped}\" for \"${\n requests.length\n }\" requests in \"${totalTime / 1000}\" seconds!`,\n ),\n );\n return requests.length;\n}\n"],"mappings":"ydAqBA,eAAsB,EAAkB,CACtC,cACA,OACA,cAAc,IACd,eAAe,GAYG,CAElB,IAAM,EAAS,EAA4B,EAAc,EAAK,CAGxD,EAAK,IAAI,MAAM,CAAC,SAAS,CAGzB,EAAW,MAAM,EAAiB,EAAQ,CAC9C,SAAU,CAAC,EAAc,UAAU,CACpC,CAAC,CAGF,EAAO,KACL,EAAO,QACL,yBAAyB,EAAY,KAAK,IAAI,CAAC,aAC7C,EAAS,OACV,wBACF,CACF,CAGD,IAAM,EAAc,IAAI,EAAY,UAAU,EAAE,CAAE,EAAY,QAAQ,eAAe,CAEjF,EAAQ,EACZ,EAAY,MAAM,EAAS,OAAQ,EAAE,CACrC,IAAI,EAAe,EACnB,MAAM,EACJ,EACA,KAAO,IAAY,CAMjB,IAAM,GAA2B,MAJF,EAAyB,EAAQ,CAC9D,SAAU,CAAE,UAAW,EAAQ,GAAI,CACnC,SACD,CAAC,EACgD,OAC/C,GACC,EAAY,SAAS,EAAS,SAAS,GAAG,EAC1C,CAAC,CACC,EAAsB,SACtB,EAAsB,QAEvB,CAAC,SAAS,EAAS,OAAc,CACrC,CAGG,EAAyB,OAAS,GACpC,MAAM,EAAU,EAA0B,KAAO,IAAoB,CACnE,GAAI,CACF,MAAM,EAGH,EAAQ,EAAuB,CAChC,UAAW,CAAE,kBAAmB,EAAgB,GAAI,CACpD,SACD,CAAC,CACF,GAAgB,QACT,EAAK,CACZ,GACE,CAAC,EAAI,QAAQ,SACX,8EACD,CAED,MAAM,IAGV,CAEJ,GAAS,EACT,EAAY,OAAO,EAAM,EAE3B,CAAE,cAAa,CAChB,CAED,EAAY,MAAM,CAElB,IAAM,EADK,IAAI,MAAM,CAAC,SACF,CAAG,EASvB,OAPA,EAAO,KACL,EAAO,MACL,yBAAyB,EAAa,UACpC,EAAS,OACV,iBAAiB,EAAY,IAAK,YACpC,CACF,CACM,EAAS"}