@transcend-io/cli 12.1.1 → 12.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{app-BAx2hvcN.mjs → app-DjiX1gVH.mjs} +16 -16
- package/dist/{app-BAx2hvcN.mjs.map → app-DjiX1gVH.mjs.map} +1 -1
- package/dist/{approvePrivacyRequests-B-4o7QMG.mjs → approvePrivacyRequests-lxkQc3j9.mjs} +2 -2
- package/dist/{approvePrivacyRequests-B-4o7QMG.mjs.map → approvePrivacyRequests-lxkQc3j9.mjs.map} +1 -1
- package/dist/bin/bash-complete.mjs +1 -1
- package/dist/bin/cli.mjs +1 -1
- package/dist/bin/deprecated-command.mjs +1 -1
- package/dist/{buildExamples-kR5_7dkU.mjs → buildExamples-BPpsiPem.mjs} +2 -2
- package/dist/{buildExamples-kR5_7dkU.mjs.map → buildExamples-BPpsiPem.mjs.map} +1 -1
- package/dist/{buildXdiSyncEndpoint-BSYwJ2vc.mjs → buildXdiSyncEndpoint-HKlaBsCz.mjs} +2 -2
- package/dist/{buildXdiSyncEndpoint-BSYwJ2vc.mjs.map → buildXdiSyncEndpoint-HKlaBsCz.mjs.map} +1 -1
- package/dist/{bulkRestartRequests-DhOfMz0S.mjs → bulkRestartRequests-BI1lqrPC.mjs} +2 -2
- package/dist/{bulkRestartRequests-DhOfMz0S.mjs.map → bulkRestartRequests-BI1lqrPC.mjs.map} +1 -1
- package/dist/{bulkRetryEnrichers-Cj94ftnY.mjs → bulkRetryEnrichers-i64LOhXy.mjs} +2 -2
- package/dist/{bulkRetryEnrichers-Cj94ftnY.mjs.map → bulkRetryEnrichers-i64LOhXy.mjs.map} +1 -1
- package/dist/{cancelPrivacyRequests-DgAhfcmg.mjs → cancelPrivacyRequests-BzRQOYrf.mjs} +2 -2
- package/dist/{cancelPrivacyRequests-DgAhfcmg.mjs.map → cancelPrivacyRequests-BzRQOYrf.mjs.map} +1 -1
- package/dist/{command-gwG8sQwM.mjs → command-D9Sydip3.mjs} +2 -2
- package/dist/{command-gwG8sQwM.mjs.map → command-D9Sydip3.mjs.map} +1 -1
- package/dist/{constants-BCmVqlrM.mjs → constants-rXUIz6Uy.mjs} +2 -2
- package/dist/{constants-BCmVqlrM.mjs.map → constants-rXUIz6Uy.mjs.map} +1 -1
- package/dist/{createPoolingCommandUi-ByyTeJx5.mjs → createPoolingCommandUi-BsU6e0Ov.mjs} +2 -2
- package/dist/{createPoolingCommandUi-ByyTeJx5.mjs.map → createPoolingCommandUi-BsU6e0Ov.mjs.map} +1 -1
- package/dist/{downloadPrivacyRequestFiles-yiE6NzXz.mjs → downloadPrivacyRequestFiles-B3Yv7zHE.mjs} +2 -2
- package/dist/{downloadPrivacyRequestFiles-yiE6NzXz.mjs.map → downloadPrivacyRequestFiles-B3Yv7zHE.mjs.map} +1 -1
- package/dist/{generateCrossAccountApiKeys-CoSsZwSz.mjs → generateCrossAccountApiKeys-DWDNh4dG.mjs} +2 -2
- package/dist/{generateCrossAccountApiKeys-CoSsZwSz.mjs.map → generateCrossAccountApiKeys-DWDNh4dG.mjs.map} +1 -1
- package/dist/{impl-C1hh_5On.mjs → impl-B1za3AEd.mjs} +2 -2
- package/dist/{impl-C1hh_5On.mjs.map → impl-B1za3AEd.mjs.map} +1 -1
- package/dist/{impl-D0cf-epj.mjs → impl-BGgaogQG.mjs} +2 -2
- package/dist/{impl-D0cf-epj.mjs.map → impl-BGgaogQG.mjs.map} +1 -1
- package/dist/{impl-CY8C-Fvj2.mjs → impl-BHEAKFX02.mjs} +2 -2
- package/dist/{impl-CY8C-Fvj2.mjs.map → impl-BHEAKFX02.mjs.map} +1 -1
- package/dist/{impl-DrlfmH8g.mjs → impl-BTXUJwyb.mjs} +2 -2
- package/dist/{impl-DrlfmH8g.mjs.map → impl-BTXUJwyb.mjs.map} +1 -1
- package/dist/{impl-DqnzZv43.mjs → impl-BU_vn9I6.mjs} +2 -2
- package/dist/{impl-DqnzZv43.mjs.map → impl-BU_vn9I6.mjs.map} +1 -1
- package/dist/{impl-LDY2dgtc.mjs → impl-BlsxbyrR.mjs} +2 -2
- package/dist/{impl-LDY2dgtc.mjs.map → impl-BlsxbyrR.mjs.map} +1 -1
- package/dist/{impl-C1vn0Y7f.mjs → impl-Bnc9QUjN.mjs} +2 -2
- package/dist/{impl-C1vn0Y7f.mjs.map → impl-Bnc9QUjN.mjs.map} +1 -1
- package/dist/{impl-BKKBmXv4.mjs → impl-Boj3WWax.mjs} +2 -2
- package/dist/{impl-BKKBmXv4.mjs.map → impl-Boj3WWax.mjs.map} +1 -1
- package/dist/{impl-BFSZEQ2s.mjs → impl-BtHRf6Jp.mjs} +2 -2
- package/dist/{impl-BFSZEQ2s.mjs.map → impl-BtHRf6Jp.mjs.map} +1 -1
- package/dist/{impl-pA1b-G6B.mjs → impl-BxJ3LBq5.mjs} +2 -2
- package/dist/{impl-pA1b-G6B.mjs.map → impl-BxJ3LBq5.mjs.map} +1 -1
- package/dist/{impl-DSPxdc-T.mjs → impl-C1UEaEKN.mjs} +2 -2
- package/dist/{impl-DSPxdc-T.mjs.map → impl-C1UEaEKN.mjs.map} +1 -1
- package/dist/{impl-BrdCuU0I.mjs → impl-C8RwWS6i.mjs} +2 -2
- package/dist/{impl-BrdCuU0I.mjs.map → impl-C8RwWS6i.mjs.map} +1 -1
- package/dist/{impl-OXGlZyUo.mjs → impl-CAukIeJv.mjs} +2 -2
- package/dist/{impl-OXGlZyUo.mjs.map → impl-CAukIeJv.mjs.map} +1 -1
- package/dist/{impl-BMX6B40F.mjs → impl-CHY55Vge.mjs} +2 -2
- package/dist/{impl-BMX6B40F.mjs.map → impl-CHY55Vge.mjs.map} +1 -1
- package/dist/{impl-C0R2azuF.mjs → impl-CIHHFnIw.mjs} +2 -2
- package/dist/{impl-C0R2azuF.mjs.map → impl-CIHHFnIw.mjs.map} +1 -1
- package/dist/{impl-ClhMRgQZ.mjs → impl-CNU3pgwh.mjs} +2 -2
- package/dist/{impl-ClhMRgQZ.mjs.map → impl-CNU3pgwh.mjs.map} +1 -1
- package/dist/{impl-C54oUOeM2.mjs → impl-CNURWgF12.mjs} +2 -2
- package/dist/{impl-C54oUOeM2.mjs.map → impl-CNURWgF12.mjs.map} +1 -1
- package/dist/{impl-BM2SWSKF.mjs → impl-CYJgNSKH.mjs} +2 -2
- package/dist/{impl-BM2SWSKF.mjs.map → impl-CYJgNSKH.mjs.map} +1 -1
- package/dist/{impl-BaGN4J-w.mjs → impl-CYpcKBww.mjs} +2 -2
- package/dist/{impl-BaGN4J-w.mjs.map → impl-CYpcKBww.mjs.map} +1 -1
- package/dist/{impl-CuDZY6y7.mjs → impl-CbZIfTUK.mjs} +2 -2
- package/dist/{impl-CuDZY6y7.mjs.map → impl-CbZIfTUK.mjs.map} +1 -1
- package/dist/{impl-D8YS5Bge.mjs → impl-CcYyiqaB.mjs} +2 -2
- package/dist/{impl-D8YS5Bge.mjs.map → impl-CcYyiqaB.mjs.map} +1 -1
- package/dist/{impl-DdUe8ClA.mjs → impl-CdFl00EX.mjs} +2 -2
- package/dist/{impl-DdUe8ClA.mjs.map → impl-CdFl00EX.mjs.map} +1 -1
- package/dist/{impl-u1nIo2-E.mjs → impl-Cr_GtYDt.mjs} +2 -2
- package/dist/{impl-u1nIo2-E.mjs.map → impl-Cr_GtYDt.mjs.map} +1 -1
- package/dist/{impl-BJnxahnW.mjs → impl-Crkz6EBl.mjs} +2 -2
- package/dist/{impl-BJnxahnW.mjs.map → impl-Crkz6EBl.mjs.map} +1 -1
- package/dist/{impl-CKg5Xu-r.mjs → impl-DD-ICQ8E.mjs} +2 -2
- package/dist/{impl-CKg5Xu-r.mjs.map → impl-DD-ICQ8E.mjs.map} +1 -1
- package/dist/{impl-CyDdvH5a.mjs → impl-DH_tLaGZ.mjs} +2 -2
- package/dist/{impl-CyDdvH5a.mjs.map → impl-DH_tLaGZ.mjs.map} +1 -1
- package/dist/{impl-DNRA5i0w.mjs → impl-DHhm_-pe.mjs} +2 -2
- package/dist/{impl-DNRA5i0w.mjs.map → impl-DHhm_-pe.mjs.map} +1 -1
- package/dist/{impl-Bf2Qli6c2.mjs → impl-DKhqM6hZ2.mjs} +2 -2
- package/dist/{impl-Bf2Qli6c2.mjs.map → impl-DKhqM6hZ2.mjs.map} +1 -1
- package/dist/{impl-4WoLxjdU.mjs → impl-DP9iMlRn.mjs} +2 -2
- package/dist/{impl-4WoLxjdU.mjs.map → impl-DP9iMlRn.mjs.map} +1 -1
- package/dist/{impl-BCP4X_zk.mjs → impl-DbqbZmAc.mjs} +2 -2
- package/dist/{impl-BCP4X_zk.mjs.map → impl-DbqbZmAc.mjs.map} +1 -1
- package/dist/{impl-tYHONQMu.mjs → impl-DlPwOUAc.mjs} +2 -2
- package/dist/{impl-tYHONQMu.mjs.map → impl-DlPwOUAc.mjs.map} +1 -1
- package/dist/{impl-DbddBTRj2.mjs → impl-DlrJX5Gl2.mjs} +2 -2
- package/dist/{impl-DbddBTRj2.mjs.map → impl-DlrJX5Gl2.mjs.map} +1 -1
- package/dist/{impl-F2lRJEqw.mjs → impl-KukgjmYS.mjs} +2 -2
- package/dist/{impl-F2lRJEqw.mjs.map → impl-KukgjmYS.mjs.map} +1 -1
- package/dist/{impl-DE_cRfz52.mjs → impl-Sx15m6eC2.mjs} +2 -2
- package/dist/{impl-DE_cRfz52.mjs.map → impl-Sx15m6eC2.mjs.map} +1 -1
- package/dist/{impl-f59q7WnK.mjs → impl-Y4CI9YNK.mjs} +2 -2
- package/dist/{impl-f59q7WnK.mjs.map → impl-Y4CI9YNK.mjs.map} +1 -1
- package/dist/{impl-DCF3CF3O.mjs → impl-fsBuXKVJ.mjs} +2 -2
- package/dist/{impl-DCF3CF3O.mjs.map → impl-fsBuXKVJ.mjs.map} +1 -1
- package/dist/{impl-CxpGBeJ0.mjs → impl-hDN-9GZo.mjs} +2 -2
- package/dist/{impl-CxpGBeJ0.mjs.map → impl-hDN-9GZo.mjs.map} +1 -1
- package/dist/{impl-BRCAL4RM.mjs → impl-mM7BgfxL.mjs} +2 -2
- package/dist/{impl-BRCAL4RM.mjs.map → impl-mM7BgfxL.mjs.map} +1 -1
- package/dist/{impl-Dl8xSx8K.mjs → impl-uWnEYxoC.mjs} +2 -2
- package/dist/{impl-Dl8xSx8K.mjs.map → impl-uWnEYxoC.mjs.map} +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{markRequestDataSiloIdsCompleted-B-Dgme2r.mjs → markRequestDataSiloIdsCompleted-Bm61UHII.mjs} +2 -2
- package/dist/{markRequestDataSiloIdsCompleted-B-Dgme2r.mjs.map → markRequestDataSiloIdsCompleted-Bm61UHII.mjs.map} +1 -1
- package/dist/{markSilentPrivacyRequests-CPU4Q2lF.mjs → markSilentPrivacyRequests-BtZHHUVJ.mjs} +2 -2
- package/dist/{markSilentPrivacyRequests-CPU4Q2lF.mjs.map → markSilentPrivacyRequests-BtZHHUVJ.mjs.map} +1 -1
- package/dist/{notifyPrivacyRequestsAdditionalTime-BWsm5Y5K.mjs → notifyPrivacyRequestsAdditionalTime-DLW694xo.mjs} +2 -2
- package/dist/{notifyPrivacyRequestsAdditionalTime-BWsm5Y5K.mjs.map → notifyPrivacyRequestsAdditionalTime-DLW694xo.mjs.map} +1 -1
- package/dist/{policy-bundle-manifest-BDey6EBs.mjs → policy-bundle-manifest-CDbVx8E6.mjs} +2 -2
- package/dist/{policy-bundle-manifest-BDey6EBs.mjs.map → policy-bundle-manifest-CDbVx8E6.mjs.map} +1 -1
- package/dist/{policy-project-discovery-DV5PnFJq.mjs → policy-project-discovery-_6QosI9J.mjs} +2 -2
- package/dist/{policy-project-discovery-DV5PnFJq.mjs.map → policy-project-discovery-_6QosI9J.mjs.map} +1 -1
- package/dist/{policy-scaffold-config-DtRELUZx.mjs → policy-scaffold-config-DwZFigj7.mjs} +4 -2
- package/dist/policy-scaffold-config-DwZFigj7.mjs.map +1 -0
- package/dist/{pullChunkedCustomSiloOutstandingIdentifiers-BaHp-jxs.mjs → pullChunkedCustomSiloOutstandingIdentifiers-DtDLjhzX.mjs} +2 -2
- package/dist/{pullChunkedCustomSiloOutstandingIdentifiers-BaHp-jxs.mjs.map → pullChunkedCustomSiloOutstandingIdentifiers-DtDLjhzX.mjs.map} +1 -1
- package/dist/{pullManualEnrichmentIdentifiersToCsv-CiEFIvl_.mjs → pullManualEnrichmentIdentifiersToCsv-CWVaEx3x.mjs} +2 -2
- package/dist/{pullManualEnrichmentIdentifiersToCsv-CiEFIvl_.mjs.map → pullManualEnrichmentIdentifiersToCsv-CWVaEx3x.mjs.map} +1 -1
- package/dist/{pushCronIdentifiersFromCsv-DeS78zyp.mjs → pushCronIdentifiersFromCsv-J4ByJG4I.mjs} +2 -2
- package/dist/{pushCronIdentifiersFromCsv-DeS78zyp.mjs.map → pushCronIdentifiersFromCsv-J4ByJG4I.mjs.map} +1 -1
- package/dist/{pushManualEnrichmentIdentifiersFromCsv-B3iVwuvs.mjs → pushManualEnrichmentIdentifiersFromCsv-Dyh7-KVs.mjs} +2 -2
- package/dist/{pushManualEnrichmentIdentifiersFromCsv-B3iVwuvs.mjs.map → pushManualEnrichmentIdentifiersFromCsv-Dyh7-KVs.mjs.map} +1 -1
- package/dist/{removeUnverifiedRequestIdentifiers-qS5A_eRZ.mjs → removeUnverifiedRequestIdentifiers-DOXo1yCT.mjs} +2 -2
- package/dist/{removeUnverifiedRequestIdentifiers-qS5A_eRZ.mjs.map → removeUnverifiedRequestIdentifiers-DOXo1yCT.mjs.map} +1 -1
- package/dist/{retryRequestDataSilos-Cxaka-kA.mjs → retryRequestDataSilos-Dehz_PEx.mjs} +2 -2
- package/dist/{retryRequestDataSilos-Cxaka-kA.mjs.map → retryRequestDataSilos-Dehz_PEx.mjs.map} +1 -1
- package/dist/{scaffold-planning-CaetLfv8.mjs → scaffold-planning-BrX-pcz6.mjs} +3 -1
- package/dist/scaffold-planning-BrX-pcz6.mjs.map +1 -0
- package/dist/{skipPreflightJobs-FdIm_Imj.mjs → skipPreflightJobs-BjFQxEwf.mjs} +2 -2
- package/dist/{skipPreflightJobs-FdIm_Imj.mjs.map → skipPreflightJobs-BjFQxEwf.mjs.map} +1 -1
- package/dist/{skipRequestDataSilos-MTbj-OfW.mjs → skipRequestDataSilos-DB1_3m-p.mjs} +2 -2
- package/dist/{skipRequestDataSilos-MTbj-OfW.mjs.map → skipRequestDataSilos-DB1_3m-p.mjs.map} +1 -1
- package/dist/{streamPrivacyRequestsToCsv-BVzYSXNY.mjs → streamPrivacyRequestsToCsv-BWmN5fl6.mjs} +2 -2
- package/dist/{streamPrivacyRequestsToCsv-BVzYSXNY.mjs.map → streamPrivacyRequestsToCsv-BWmN5fl6.mjs.map} +1 -1
- package/dist/{updateConsentManagerVersionToLatest-Udi7Iazb.mjs → updateConsentManagerVersionToLatest-C6jRvQPt.mjs} +2 -2
- package/dist/{updateConsentManagerVersionToLatest-Udi7Iazb.mjs.map → updateConsentManagerVersionToLatest-C6jRvQPt.mjs.map} +1 -1
- package/dist/{uploadConsents-w7FE65Zg.mjs → uploadConsents-08KJPYm_.mjs} +2 -2
- package/dist/{uploadConsents-w7FE65Zg.mjs.map → uploadConsents-08KJPYm_.mjs.map} +1 -1
- package/dist/{uploadPrivacyRequestsFromCsv-B__QsguT.mjs → uploadPrivacyRequestsFromCsv-M3iY9cjb.mjs} +2 -2
- package/dist/{uploadPrivacyRequestsFromCsv-B__QsguT.mjs.map → uploadPrivacyRequestsFromCsv-M3iY9cjb.mjs.map} +1 -1
- package/package.json +5 -5
- package/dist/policy-scaffold-config-DtRELUZx.mjs.map +0 -1
- package/dist/scaffold-planning-CaetLfv8.mjs.map +0 -1
package/dist/{uploadPrivacyRequestsFromCsv-B__QsguT.mjs → uploadPrivacyRequestsFromCsv-M3iY9cjb.mjs}
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as e}from"./constants-BCmVqlrM.mjs";import{t}from"./logger-CPu6zbyQ.mjs";import{a as n,c as r,i,l as a,o,r as s,s as c,t as l}from"./constants-DlH2vWm_.mjs";import{n as u,t as d}from"./fuzzyMatchColumns-D18n9Beu.mjs";import{n as f,t as p}from"./parseAttributesFromString-C5hJMRvt.mjs";import{n as m}from"./readCsv-DNcJ_UfU.mjs";import{r as h,t as g}from"./extractClientError-BQgmnp3e.mjs";import{CompletedRequestStatus as _,IdentifierType as v,IsoCountryCode as y,IsoCountrySubdivisionCode as b,NORMALIZE_PHONE_NUMBER as x,RequestAction as S}from"@transcend-io/privacy-types";import{startCase as C,uniq as w}from"lodash-es";import{join as ee}from"node:path";import T from"colors";import{map as te,splitCsvToList as E}from"@transcend-io/utils";import{LOCALE_KEY as D}from"@transcend-io/internationalization";import{apply as O,getEntries as k,getValues as A,valuesOf as j}from"@transcend-io/type-utils";import*as M from"io-ts";import{DATA_SUBJECTS as N,INITIALIZER as P,buildTranscendGraphQLClient as F,createSombraGotInstance as ne,fetchAllRequestAttributeKeys as re,makeGraphQLRequest as I}from"@transcend-io/sdk";import L from"cli-progress";import R from"inquirer";import z from"inquirer-autocomplete-prompt";import{DateFromISOString as B}from"io-ts-types";import{PersistedState as V}from"@transcend-io/persisted-state";async function H(e,t,n){R.registerPrompt(`autocomplete`,z);let r=e.map(e=>e||`<blank>`).filter(e=>!n[e]);if(r.length===0)return n;let i=await R.prompt(r.map(e=>({name:e,message:`Map value of: ${e}`,type:`autocomplete`,default:t.find(t=>u(e,t)),source:(e,n)=>n?t.filter(e=>typeof e==`string`&&u(n,e)):t})));return{...n,...O(i,e=>typeof e==`string`?e:Object.values(e)[0])}}function U(e,t){return w(e.map(e=>e[t]||``).flat())}async function W(e){let n=w(e.map(e=>Object.keys(e)).flat()),r=e,i=!0;for(;i;){let{filterColumnName:e}=await R.prompt([{name:`filterColumnName`,message:`If you need to filter the list of requests to import, choose the column to filter on. Currently ${r.length} rows.`,type:`list`,default:n,choices:[a,...n]}]);if(i=a!==e,i){let t=U(r,e),{valuesToKeep:i}=await R.prompt([{name:`valuesToKeep`,message:`Keep rows matching this value`,type:`checkbox`,default:n,choices:t}]);r=r.filter(t=>i.includes(t[e]))}}return t.info(T.magenta(`Importing ${r.length} requests`)),r}async function G(e,t){let n=A(o).filter(e=>!t.getValue(`columnNames`,e)),i=n.length===0?{}:await R.prompt(n.map(t=>{let n=C(t.replace(`ColumnName`,``)),i=d(e,n,r[t],!!s[t]);return{name:t,message:`Choose the column that will be used to map in the field: ${n}`,type:`list`,default:i[0],choices:i}}));return await Promise.all(k(i).map(([e,n])=>t.setValue(n,`columnNames`,e))),i}async function K(e,n,{state:r,columnNameMap:i}){let o=e=>r.getValue(`columnNames`,e)||i[e],{internalSubjects:s}=await I(e,N,{logger:t});t.info(T.magenta(`Determining mapping of columns for request action`));let c=await H(U(n,o(`requestType`)),Object.values(S),r.getValue(`requestTypeToRequestAction`));await r.setValue(c,`requestTypeToRequestAction`),t.info(T.magenta(`Determining mapping of columns for subject`));let l=await H(U(n,o(`subjectType`)),s.map(({type:e})=>e),r.getValue(`subjectTypeToSubjectName`));await r.setValue(l,`subjectTypeToSubjectName`),t.info(T.magenta(`Determining mapping of columns for locale`));let u=await H(U(n,o(`locale`)),Object.values(D),r.getValue(`languageToLocale`));await r.setValue(u,`languageToLocale`),t.info(T.magenta(`Determining mapping of columns for request status`)),t.info(T.magenta(`Determining mapping of columns for request status`));let d=o(`requestStatus`),f=d===`[NONE]`?{}:await H(U(n,d),[...Object.values(_),a],r.getValue(`statusToRequestStatus`));await r.setValue(f,`statusToRequestStatus`),t.info(T.magenta(`Determining mapping of columns for country`));let p=o(`country`),m=p===`[NONE]`?{}:await H(U(n,p),[...Object.values(y),a],r.getValue(`regionToCountry`));await r.setValue(m,`regionToCountry`),t.info(T.magenta(`Determining mapping of columns for country sub division`));let h=o(`countrySubDivision`),g=h===`[NONE]`?{}:await H(U(n,h),[...Object.values(b),a],r.getValue(`regionToCountrySubDivision`));await r.setValue(g,`regionToCountrySubDivision`)}const q=M.record(M.string,M.array(M.intersection([M.type({value:M.string}),M.partial({name:M.string})]))),J=M.intersection([M.type({email:M.string,attestedExtraIdentifiers:q,coreIdentifier:M.string,requestType:j(S),subjectType:M.string}),M.partial({country:j(y),countrySubDivision:j(b),attributes:M.array(p),status:j(_),createdAt:B,dataSiloIds:M.array(M.string),locale:j(D),workflowConfigId:M.string})]);function Y(e,t,n){if(t===v.Email)return e.toLowerCase();if(t===v.Phone){let t=e.replace(x,``).replace(/[()]/g,``).replace(/[–]/g,``).replace(/[:]/g,``).replace(/[]/g,``).replace(/[A-Za-z]/g,``);return t?t.startsWith(`+`)?t:`+${n}${t}`:``}return e}function X(e,t,{columnNameMap:n,identifierNameMap:r,attributeNameMap:i,requestAttributeKeys:o,defaultPhoneCountryCode:s=`1`}){let c=e=>t.getValue(`columnNames`,e)||n[e];return e.map(e=>{let n={};Object.entries(r).filter(([,e])=>e!==a).forEach(([t,r])=>{let i=Object.values(v).includes(t)?t:v.Custom,a=e[r];if(a){let e=Y(a,i,s);e&&(n[i]||(n[i]=[]),n[i].push({value:e,name:t}))}});let u=[];Object.entries(i).filter(([,e])=>e!==a).forEach(([t,n])=>{let r=e[n];if(r){let e=o.find(e=>e.name===t)?.type===`MULTI_SELECT`;u.push({values:e?E(r):r,key:t})}});let d=c(`requestType`),f=c(`subjectType`);return[e,{email:e[c(`email`)],attestedExtraIdentifiers:n,attributes:u,coreIdentifier:e[c(`coreIdentifier`)],requestType:d===`[APPLY VALUE TO ALL ROWS]`?t.getValue(`requestTypeToRequestAction`,l):t.getValue(`requestTypeToRequestAction`,e[d]),subjectType:f===`[APPLY VALUE TO ALL ROWS]`?t.getValue(`subjectTypeToSubjectName`,l):t.getValue(`subjectTypeToSubjectName`,e[f]),...c(`locale`)!==`[NONE]`&&e[c(`locale`)]?{locale:t.getValue(`languageToLocale`,e[c(`locale`)])}:{},...c(`country`)!==`[NONE]`&&e[c(`country`)]?{country:t.getValue(`regionToCountry`,e[c(`country`)])}:{},...c(`countrySubDivision`)!==`[NONE]`&&e[c(`countrySubDivision`)]?{countrySubDivision:t.getValue(`regionToCountrySubDivision`,e[c(`countrySubDivision`)])}:{},...c(`requestStatus`)!==`[NONE]`&&t.getValue(`statusToRequestStatus`,e[c(`requestStatus`)])!==`[NONE]`&&e[c(`requestStatus`)]?{status:t.getValue(`statusToRequestStatus`,e[c(`requestStatus`)])}:{},...c(`createdAt`)!==`[NONE]`&&e[c(`createdAt`)]?{createdAt:new Date(e[c(`createdAt`)])}:{},...c(`dataSiloIds`)!==`[NONE]`&&e[c(`dataSiloIds`)]?{dataSiloIds:E(e[c(`dataSiloIds`)])}:{},...c(`workflowConfigId`)!==`[NONE]`&&e[c(`workflowConfigId`)]?{workflowConfigId:e[c(`workflowConfigId`)]}:{}}]})}async function Z(e,n,r){let{initializer:i}=await I(e,P,{logger:t}),a=i.identifiers.filter(({name:e})=>!r.getValue(`identifierNames`,e)&&!c.includes(e)),o=a.length===0?{}:await R.prompt(a.map(({name:e})=>{let t=d(n,e,!1);return{name:e,message:`Choose the column that will be used to map in the identifier: ${e}`,type:`list`,default:t[0],choices:t}}));return await Promise.all(Object.entries(o).map(([e,t])=>r.setValue(t,`identifierNames`,e))),{...r.getValue(`identifierNames`),...o}}async function Q(e,t,n,r){let i=r.filter(({name:e})=>!n.getValue(`attributeNames`,e)),a=i.length===0?{}:await R.prompt(i.map(({name:e})=>{let n=d(t,e,!1);return{name:e,message:`Choose the column that will be used to map in the attribute: ${e}`,type:`list`,default:n[0],choices:n}}));return await Promise.all(Object.entries(a).map(([e,t])=>n.setValue(t,`attributeNames`,e))),{...n.getValue(`attributeNames`),...a}}async function $({cacheFilepath:r,requestReceiptFolder:a,file:o,auth:s,sombraAuth:c,concurrency:l=100,defaultPhoneCountryCode:u=`1`,transcendUrl:d=e,attributes:p=[],emailIsVerified:_=!0,skipFilterStep:v=!1,skipSendingReceipt:y=!0,isTest:b=!1,isSilent:x=!0,debug:S=!1,dryRun:C=!1}){let E=new Date().getTime(),D=new L.SingleBar({},L.Presets.shades_classic),O=f(p),k=new V(r,i,{columnNames:{},requestTypeToRequestAction:{},subjectTypeToSubjectName:{},languageToLocale:{},statusToRequestStatus:{},identifierNames:{},attributeNames:{},regionToCountrySubDivision:{},regionToCountry:{}}),A=ee(a,`tr-request-upload-${new Date().toISOString().replace(/:/g,`-`)}-${o.split(`/`).pop()}`.replace(`.csv`,`.json`)),j=new V(A,n,{successfulRequests:[],duplicateRequests:[],failingRequests:[]}),N=await ne(d,s,{logger:t,sombraApiKey:c,sombraUrl:process.env.SOMBRA_URL}),P=m(o,M.record(M.string,M.string)),I=w(P.map(e=>Object.keys(e)).flat());if(P.length===0)throw Error(`No Requests found in list! Ensure the first row of the CSV is a header and the rest are requests.`);if(S){let e=P[0];t.info(T.magenta(`First request: ${JSON.stringify(e,null,2)}`))}let R=v?P:await W(P),z=F(d,s),B=await re(z,{logger:t}),H=await G(I,k),U=await Z(z,I,k),q=await Q(z,I,k,B);await K(z,R,{state:k,columnNameMap:H});let J=X(R,k,{defaultPhoneCountryCode:u,columnNameMap:H,identifierNameMap:U,attributeNameMap:q,requestAttributeKeys:B});S||D.start(J.length,0);let Y=0;await te(J,async([e,n],r)=>{let i=S?`email:${n.email} | coreIdentifier:${n.coreIdentifier}`:`row:${r.toString()}`;if(S&&t.info(T.magenta(`[${r+1}/${J.length}] Importing: ${JSON.stringify(n,null,2)}`)),C){t.info(T.magenta(`Bailing out on dry run because dryRun is set`));return}try{let a=await h(N,n,{details:`Uploaded by Transcend Cli: "tr-request-upload" : ${JSON.stringify(e,null,2)}`,isTest:b,emailIsVerified:_,skipSendingReceipt:y,isSilent:x,additionalAttributes:O});S&&(t.info(T.green(`[${r+1}/${J.length}] Successfully submitted the test data subject request: "${i}"`)),t.info(T.green(`[${r+1}/${J.length}] View it at: "${a.link}"`)));let o=j.getValue(`successfulRequests`);o.push({id:a.id,link:a.link,rowIndex:r,coreIdentifier:a.coreIdentifier,attemptedAt:new Date().toISOString()}),await j.setValue(o,`successfulRequests`)}catch(e){let a=`${e.message} - ${JSON.stringify(e.response?.body,null,2)}`,o=g(a);if(o===`Client error: You have already made this request.`){S&&t.info(T.yellow(`[${r+1}/${J.length}] Skipping request as it is a duplicate`));let e=j.getValue(`duplicateRequests`);e.push({coreIdentifier:n.coreIdentifier,rowIndex:r,attemptedAt:new Date().toISOString()}),await j.setValue(e,`duplicateRequests`)}else{let e=j.getValue(`failingRequests`);e.push({...n,rowIndex:r,error:o||a,attemptedAt:new Date().toISOString()}),await j.setValue(e,`failingRequests`),S&&(t.error(T.red(o||a)),t.error(T.red(`[${r+1}/${J.length}] Failed to submit request for: "${i}"`)))}}Y+=1,S||D.update(Y)},{concurrency:l}),D.stop();let $=new Date().getTime()-E;t.info(T.green(`Completed upload in "${$/1e3}" seconds.`)),j.getValue(`duplicateRequests`).length>0&&t.info(T.yellow(`Encountered "${j.getValue(`duplicateRequests`).length}" duplicate requests. See "${A}" to review the core identifiers for these requests.`)),j.getValue(`failingRequests`).length>0&&(t.error(T.red(`Encountered "${j.getValue(`failingRequests`).length}" errors. See "${A}" to review the error messages and inputs.`)),process.exit(1))}export{J as a,K as c,U as d,H as f,q as i,G as l,Q as n,X as o,Z as r,Y as s,$ as t,W as u};
|
|
2
|
-
//# sourceMappingURL=uploadPrivacyRequestsFromCsv-
|
|
1
|
+
import{a as e}from"./constants-rXUIz6Uy.mjs";import{t}from"./logger-CPu6zbyQ.mjs";import{a as n,c as r,i,l as a,o,r as s,s as c,t as l}from"./constants-DlH2vWm_.mjs";import{n as u,t as d}from"./fuzzyMatchColumns-D18n9Beu.mjs";import{n as f,t as p}from"./parseAttributesFromString-C5hJMRvt.mjs";import{n as m}from"./readCsv-DNcJ_UfU.mjs";import{r as h,t as g}from"./extractClientError-BQgmnp3e.mjs";import{CompletedRequestStatus as _,IdentifierType as v,IsoCountryCode as y,IsoCountrySubdivisionCode as b,NORMALIZE_PHONE_NUMBER as x,RequestAction as S}from"@transcend-io/privacy-types";import{startCase as C,uniq as w}from"lodash-es";import{join as ee}from"node:path";import T from"colors";import{map as te,splitCsvToList as E}from"@transcend-io/utils";import{LOCALE_KEY as D}from"@transcend-io/internationalization";import{apply as O,getEntries as k,getValues as A,valuesOf as j}from"@transcend-io/type-utils";import*as M from"io-ts";import{DATA_SUBJECTS as N,INITIALIZER as P,buildTranscendGraphQLClient as F,createSombraGotInstance as ne,fetchAllRequestAttributeKeys as re,makeGraphQLRequest as I}from"@transcend-io/sdk";import L from"cli-progress";import R from"inquirer";import z from"inquirer-autocomplete-prompt";import{DateFromISOString as B}from"io-ts-types";import{PersistedState as V}from"@transcend-io/persisted-state";async function H(e,t,n){R.registerPrompt(`autocomplete`,z);let r=e.map(e=>e||`<blank>`).filter(e=>!n[e]);if(r.length===0)return n;let i=await R.prompt(r.map(e=>({name:e,message:`Map value of: ${e}`,type:`autocomplete`,default:t.find(t=>u(e,t)),source:(e,n)=>n?t.filter(e=>typeof e==`string`&&u(n,e)):t})));return{...n,...O(i,e=>typeof e==`string`?e:Object.values(e)[0])}}function U(e,t){return w(e.map(e=>e[t]||``).flat())}async function W(e){let n=w(e.map(e=>Object.keys(e)).flat()),r=e,i=!0;for(;i;){let{filterColumnName:e}=await R.prompt([{name:`filterColumnName`,message:`If you need to filter the list of requests to import, choose the column to filter on. Currently ${r.length} rows.`,type:`list`,default:n,choices:[a,...n]}]);if(i=a!==e,i){let t=U(r,e),{valuesToKeep:i}=await R.prompt([{name:`valuesToKeep`,message:`Keep rows matching this value`,type:`checkbox`,default:n,choices:t}]);r=r.filter(t=>i.includes(t[e]))}}return t.info(T.magenta(`Importing ${r.length} requests`)),r}async function G(e,t){let n=A(o).filter(e=>!t.getValue(`columnNames`,e)),i=n.length===0?{}:await R.prompt(n.map(t=>{let n=C(t.replace(`ColumnName`,``)),i=d(e,n,r[t],!!s[t]);return{name:t,message:`Choose the column that will be used to map in the field: ${n}`,type:`list`,default:i[0],choices:i}}));return await Promise.all(k(i).map(([e,n])=>t.setValue(n,`columnNames`,e))),i}async function K(e,n,{state:r,columnNameMap:i}){let o=e=>r.getValue(`columnNames`,e)||i[e],{internalSubjects:s}=await I(e,N,{logger:t});t.info(T.magenta(`Determining mapping of columns for request action`));let c=await H(U(n,o(`requestType`)),Object.values(S),r.getValue(`requestTypeToRequestAction`));await r.setValue(c,`requestTypeToRequestAction`),t.info(T.magenta(`Determining mapping of columns for subject`));let l=await H(U(n,o(`subjectType`)),s.map(({type:e})=>e),r.getValue(`subjectTypeToSubjectName`));await r.setValue(l,`subjectTypeToSubjectName`),t.info(T.magenta(`Determining mapping of columns for locale`));let u=await H(U(n,o(`locale`)),Object.values(D),r.getValue(`languageToLocale`));await r.setValue(u,`languageToLocale`),t.info(T.magenta(`Determining mapping of columns for request status`)),t.info(T.magenta(`Determining mapping of columns for request status`));let d=o(`requestStatus`),f=d===`[NONE]`?{}:await H(U(n,d),[...Object.values(_),a],r.getValue(`statusToRequestStatus`));await r.setValue(f,`statusToRequestStatus`),t.info(T.magenta(`Determining mapping of columns for country`));let p=o(`country`),m=p===`[NONE]`?{}:await H(U(n,p),[...Object.values(y),a],r.getValue(`regionToCountry`));await r.setValue(m,`regionToCountry`),t.info(T.magenta(`Determining mapping of columns for country sub division`));let h=o(`countrySubDivision`),g=h===`[NONE]`?{}:await H(U(n,h),[...Object.values(b),a],r.getValue(`regionToCountrySubDivision`));await r.setValue(g,`regionToCountrySubDivision`)}const q=M.record(M.string,M.array(M.intersection([M.type({value:M.string}),M.partial({name:M.string})]))),J=M.intersection([M.type({email:M.string,attestedExtraIdentifiers:q,coreIdentifier:M.string,requestType:j(S),subjectType:M.string}),M.partial({country:j(y),countrySubDivision:j(b),attributes:M.array(p),status:j(_),createdAt:B,dataSiloIds:M.array(M.string),locale:j(D),workflowConfigId:M.string})]);function Y(e,t,n){if(t===v.Email)return e.toLowerCase();if(t===v.Phone){let t=e.replace(x,``).replace(/[()]/g,``).replace(/[–]/g,``).replace(/[:]/g,``).replace(/[]/g,``).replace(/[A-Za-z]/g,``);return t?t.startsWith(`+`)?t:`+${n}${t}`:``}return e}function X(e,t,{columnNameMap:n,identifierNameMap:r,attributeNameMap:i,requestAttributeKeys:o,defaultPhoneCountryCode:s=`1`}){let c=e=>t.getValue(`columnNames`,e)||n[e];return e.map(e=>{let n={};Object.entries(r).filter(([,e])=>e!==a).forEach(([t,r])=>{let i=Object.values(v).includes(t)?t:v.Custom,a=e[r];if(a){let e=Y(a,i,s);e&&(n[i]||(n[i]=[]),n[i].push({value:e,name:t}))}});let u=[];Object.entries(i).filter(([,e])=>e!==a).forEach(([t,n])=>{let r=e[n];if(r){let e=o.find(e=>e.name===t)?.type===`MULTI_SELECT`;u.push({values:e?E(r):r,key:t})}});let d=c(`requestType`),f=c(`subjectType`);return[e,{email:e[c(`email`)],attestedExtraIdentifiers:n,attributes:u,coreIdentifier:e[c(`coreIdentifier`)],requestType:d===`[APPLY VALUE TO ALL ROWS]`?t.getValue(`requestTypeToRequestAction`,l):t.getValue(`requestTypeToRequestAction`,e[d]),subjectType:f===`[APPLY VALUE TO ALL ROWS]`?t.getValue(`subjectTypeToSubjectName`,l):t.getValue(`subjectTypeToSubjectName`,e[f]),...c(`locale`)!==`[NONE]`&&e[c(`locale`)]?{locale:t.getValue(`languageToLocale`,e[c(`locale`)])}:{},...c(`country`)!==`[NONE]`&&e[c(`country`)]?{country:t.getValue(`regionToCountry`,e[c(`country`)])}:{},...c(`countrySubDivision`)!==`[NONE]`&&e[c(`countrySubDivision`)]?{countrySubDivision:t.getValue(`regionToCountrySubDivision`,e[c(`countrySubDivision`)])}:{},...c(`requestStatus`)!==`[NONE]`&&t.getValue(`statusToRequestStatus`,e[c(`requestStatus`)])!==`[NONE]`&&e[c(`requestStatus`)]?{status:t.getValue(`statusToRequestStatus`,e[c(`requestStatus`)])}:{},...c(`createdAt`)!==`[NONE]`&&e[c(`createdAt`)]?{createdAt:new Date(e[c(`createdAt`)])}:{},...c(`dataSiloIds`)!==`[NONE]`&&e[c(`dataSiloIds`)]?{dataSiloIds:E(e[c(`dataSiloIds`)])}:{},...c(`workflowConfigId`)!==`[NONE]`&&e[c(`workflowConfigId`)]?{workflowConfigId:e[c(`workflowConfigId`)]}:{}}]})}async function Z(e,n,r){let{initializer:i}=await I(e,P,{logger:t}),a=i.identifiers.filter(({name:e})=>!r.getValue(`identifierNames`,e)&&!c.includes(e)),o=a.length===0?{}:await R.prompt(a.map(({name:e})=>{let t=d(n,e,!1);return{name:e,message:`Choose the column that will be used to map in the identifier: ${e}`,type:`list`,default:t[0],choices:t}}));return await Promise.all(Object.entries(o).map(([e,t])=>r.setValue(t,`identifierNames`,e))),{...r.getValue(`identifierNames`),...o}}async function Q(e,t,n,r){let i=r.filter(({name:e})=>!n.getValue(`attributeNames`,e)),a=i.length===0?{}:await R.prompt(i.map(({name:e})=>{let n=d(t,e,!1);return{name:e,message:`Choose the column that will be used to map in the attribute: ${e}`,type:`list`,default:n[0],choices:n}}));return await Promise.all(Object.entries(a).map(([e,t])=>n.setValue(t,`attributeNames`,e))),{...n.getValue(`attributeNames`),...a}}async function $({cacheFilepath:r,requestReceiptFolder:a,file:o,auth:s,sombraAuth:c,concurrency:l=100,defaultPhoneCountryCode:u=`1`,transcendUrl:d=e,attributes:p=[],emailIsVerified:_=!0,skipFilterStep:v=!1,skipSendingReceipt:y=!0,isTest:b=!1,isSilent:x=!0,debug:S=!1,dryRun:C=!1}){let E=new Date().getTime(),D=new L.SingleBar({},L.Presets.shades_classic),O=f(p),k=new V(r,i,{columnNames:{},requestTypeToRequestAction:{},subjectTypeToSubjectName:{},languageToLocale:{},statusToRequestStatus:{},identifierNames:{},attributeNames:{},regionToCountrySubDivision:{},regionToCountry:{}}),A=ee(a,`tr-request-upload-${new Date().toISOString().replace(/:/g,`-`)}-${o.split(`/`).pop()}`.replace(`.csv`,`.json`)),j=new V(A,n,{successfulRequests:[],duplicateRequests:[],failingRequests:[]}),N=await ne(d,s,{logger:t,sombraApiKey:c,sombraUrl:process.env.SOMBRA_URL}),P=m(o,M.record(M.string,M.string)),I=w(P.map(e=>Object.keys(e)).flat());if(P.length===0)throw Error(`No Requests found in list! Ensure the first row of the CSV is a header and the rest are requests.`);if(S){let e=P[0];t.info(T.magenta(`First request: ${JSON.stringify(e,null,2)}`))}let R=v?P:await W(P),z=F(d,s),B=await re(z,{logger:t}),H=await G(I,k),U=await Z(z,I,k),q=await Q(z,I,k,B);await K(z,R,{state:k,columnNameMap:H});let J=X(R,k,{defaultPhoneCountryCode:u,columnNameMap:H,identifierNameMap:U,attributeNameMap:q,requestAttributeKeys:B});S||D.start(J.length,0);let Y=0;await te(J,async([e,n],r)=>{let i=S?`email:${n.email} | coreIdentifier:${n.coreIdentifier}`:`row:${r.toString()}`;if(S&&t.info(T.magenta(`[${r+1}/${J.length}] Importing: ${JSON.stringify(n,null,2)}`)),C){t.info(T.magenta(`Bailing out on dry run because dryRun is set`));return}try{let a=await h(N,n,{details:`Uploaded by Transcend Cli: "tr-request-upload" : ${JSON.stringify(e,null,2)}`,isTest:b,emailIsVerified:_,skipSendingReceipt:y,isSilent:x,additionalAttributes:O});S&&(t.info(T.green(`[${r+1}/${J.length}] Successfully submitted the test data subject request: "${i}"`)),t.info(T.green(`[${r+1}/${J.length}] View it at: "${a.link}"`)));let o=j.getValue(`successfulRequests`);o.push({id:a.id,link:a.link,rowIndex:r,coreIdentifier:a.coreIdentifier,attemptedAt:new Date().toISOString()}),await j.setValue(o,`successfulRequests`)}catch(e){let a=`${e.message} - ${JSON.stringify(e.response?.body,null,2)}`,o=g(a);if(o===`Client error: You have already made this request.`){S&&t.info(T.yellow(`[${r+1}/${J.length}] Skipping request as it is a duplicate`));let e=j.getValue(`duplicateRequests`);e.push({coreIdentifier:n.coreIdentifier,rowIndex:r,attemptedAt:new Date().toISOString()}),await j.setValue(e,`duplicateRequests`)}else{let e=j.getValue(`failingRequests`);e.push({...n,rowIndex:r,error:o||a,attemptedAt:new Date().toISOString()}),await j.setValue(e,`failingRequests`),S&&(t.error(T.red(o||a)),t.error(T.red(`[${r+1}/${J.length}] Failed to submit request for: "${i}"`)))}}Y+=1,S||D.update(Y)},{concurrency:l}),D.stop();let $=new Date().getTime()-E;t.info(T.green(`Completed upload in "${$/1e3}" seconds.`)),j.getValue(`duplicateRequests`).length>0&&t.info(T.yellow(`Encountered "${j.getValue(`duplicateRequests`).length}" duplicate requests. See "${A}" to review the core identifiers for these requests.`)),j.getValue(`failingRequests`).length>0&&(t.error(T.red(`Encountered "${j.getValue(`failingRequests`).length}" errors. See "${A}" to review the error messages and inputs.`)),process.exit(1))}export{J as a,K as c,U as d,H as f,q as i,G as l,Q as n,X as o,Z as r,Y as s,$ as t,W as u};
|
|
2
|
+
//# sourceMappingURL=uploadPrivacyRequestsFromCsv-M3iY9cjb.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uploadPrivacyRequestsFromCsv-B__QsguT.mjs","names":[],"sources":["../src/lib/requests/mapEnumValues.ts","../src/lib/requests/getUniqueValuesForColumn.ts","../src/lib/requests/filterRows.ts","../src/lib/requests/mapCsvColumnsToApi.ts","../src/lib/requests/mapRequestEnumValues.ts","../src/lib/requests/mapCsvRowsToRequestInputs.ts","../src/lib/requests/mapColumnsToIdentifiers.ts","../src/lib/requests/mapColumnsToAttributes.ts","../src/lib/requests/uploadPrivacyRequestsFromCsv.ts"],"sourcesContent":["import { apply, ObjByString } from '@transcend-io/type-utils';\nimport inquirer from 'inquirer';\nimport autoCompletePrompt from 'inquirer-autocomplete-prompt';\n\nimport { fuzzySearch } from './fuzzyMatchColumns.js';\n\n/**\n * Map a set of inputs to a set of outputs\n *\n * @param csvInputs - Input list\n * @param expectedOutputs - Output list\n * @param cache - Cache\n * @returns Mapping from row to enum value\n */\nexport async function mapEnumValues<TValue extends string>(\n csvInputs: string[],\n expectedOutputs: TValue[],\n cache: { [k in string]: TValue },\n): Promise<{ [k in string]: TValue }> {\n inquirer.registerPrompt('autocomplete', autoCompletePrompt);\n\n const inputs = csvInputs.map((item) => item || '<blank>').filter((value) => !cache[value]);\n if (inputs.length === 0) {\n return cache;\n }\n const result = await inquirer.prompt<{ [k in string]: TValue }>(\n inputs.map((value) => ({\n name: value,\n message: `Map value of: ${value}`,\n type: 'autocomplete',\n default: expectedOutputs.find((x) => fuzzySearch(value, x)),\n source: (answersSoFar: ObjByString, input: string) =>\n !input\n ? expectedOutputs\n : expectedOutputs.filter((x) => typeof x === 'string' && fuzzySearch(input, x)),\n })),\n );\n return {\n ...cache,\n ...apply(result, (r) =>\n typeof r === 'string' ? (r as TValue) : (Object.values(r)[0] as TValue),\n ),\n };\n}\n","import { ObjByString } from '@transcend-io/type-utils';\nimport { uniq } from 'lodash-es';\n\n/**\n * Return the unique set of values for a column in a CSV\n *\n * @param rows - Rows to look up\n * @param columnName - Name of column to grab values for\n * @returns Unique set of values in that column\n */\nexport function getUniqueValuesForColumn(rows: ObjByString[], columnName: string): string[] {\n return uniq(rows.map((row) => row[columnName] || '').flat());\n}\n","import { ObjByString } from '@transcend-io/type-utils';\nimport colors from 'colors';\nimport inquirer from 'inquirer';\nimport { uniq } from 'lodash-es';\n\nimport { logger } from '../../logger.js';\nimport { NONE } from './constants.js';\nimport { getUniqueValuesForColumn } from './getUniqueValuesForColumn.js';\n\n/**\n * Filter a list of CSV rows by column values\n * Choose columns that contain metadata to filter the requests\n *\n * @param rows - Rows to filter\n * @returns Filtered rows\n */\nexport async function filterRows(rows: ObjByString[]): Promise<ObjByString[]> {\n // Determine set of column names\n const columnNames = uniq(rows.map((x) => Object.keys(x)).flat());\n\n // update these variables recursively\n let filteredRows = rows;\n let keepFiltering = true;\n\n // loop over\n while (keepFiltering) {\n // Prompt user for column to filter on\n\n const { filterColumnName } = await inquirer.prompt<{\n /** Name of column to filter on */\n filterColumnName: string;\n }>([\n {\n name: 'filterColumnName',\n // eslint-disable-next-line max-len\n message: `If you need to filter the list of requests to import, choose the column to filter on. Currently ${filteredRows.length} rows.`,\n type: 'list',\n default: columnNames,\n choices: [NONE, ...columnNames],\n },\n ]);\n\n // Determine if filtering should continue, or loop should be exited\n keepFiltering = NONE !== filterColumnName;\n if (keepFiltering) {\n const options = getUniqueValuesForColumn(filteredRows, filterColumnName);\n\n const { valuesToKeep } = await inquirer.prompt<{\n /** Values to keep */\n valuesToKeep: string[];\n }>([\n {\n name: 'valuesToKeep',\n message: 'Keep rows matching this value',\n type: 'checkbox',\n default: columnNames,\n choices: options,\n },\n ]);\n\n filteredRows = filteredRows.filter((request) =>\n valuesToKeep.includes(request[filterColumnName]),\n );\n }\n }\n\n logger.info(colors.magenta(`Importing ${filteredRows.length} requests`));\n return filteredRows;\n}\n","import type { PersistedState } from '@transcend-io/persisted-state';\nimport { getValues, getEntries } from '@transcend-io/type-utils';\nimport inquirer from 'inquirer';\nimport { startCase } from 'lodash-es';\n\nimport { ColumnName, CachedFileState, IS_REQUIRED, CAN_APPLY_IN_BULK } from './constants.js';\nimport { fuzzyMatchColumns } from './fuzzyMatchColumns.js';\n\n/**\n * Mapping from column name to request input parameter\n */\nexport type ColumnNameMap = {\n [k in ColumnName]?: string;\n};\n\n/**\n * Determine the mapping between columns in CSV\n *\n * @param columnNames - The set of column names\n * @param state - The cached file state used to map DSR inputs\n * @returns The column name mapping\n */\nexport async function mapCsvColumnsToApi(\n columnNames: string[],\n state: PersistedState<typeof CachedFileState>,\n): Promise<ColumnNameMap> {\n // Determine the columns that should be mapped\n const columnQuestions = getValues(ColumnName).filter(\n (name) => !state.getValue('columnNames', name),\n );\n\n // Skip mapping when everything is mapped\n const columnNameMap =\n columnQuestions.length === 0\n ? {}\n : // prompt questions to map columns\n await inquirer.prompt<{\n [k in ColumnName]?: string;\n }>(\n columnQuestions.map((name) => {\n const field = startCase(name.replace('ColumnName', ''));\n const matches = fuzzyMatchColumns(\n columnNames,\n field,\n IS_REQUIRED[name],\n !!CAN_APPLY_IN_BULK[name],\n );\n return {\n name,\n message: `Choose the column that will be used to map in the field: ${field}`,\n type: 'list',\n default: matches[0],\n choices: matches,\n };\n }),\n );\n\n await Promise.all(getEntries(columnNameMap).map(([k, v]) => state.setValue(v, 'columnNames', k)));\n return columnNameMap;\n}\n","import { LOCALE_KEY, type LocaleValue } from '@transcend-io/internationalization';\nimport type { PersistedState } from '@transcend-io/persisted-state';\nimport {\n CompletedRequestStatus,\n RequestAction,\n IsoCountryCode,\n IsoCountrySubdivisionCode,\n} from '@transcend-io/privacy-types';\nimport { makeGraphQLRequest, DATA_SUBJECTS, type DataSubject } from '@transcend-io/sdk';\nimport { ObjByString } from '@transcend-io/type-utils';\nimport colors from 'colors';\nimport { GraphQLClient } from 'graphql-request';\n\nimport { logger } from '../../logger.js';\nimport { CachedFileState, NONE, ColumnName } from './constants.js';\nimport { getUniqueValuesForColumn } from './getUniqueValuesForColumn.js';\nimport { ColumnNameMap } from './mapCsvColumnsToApi.js';\nimport { mapEnumValues } from './mapEnumValues.js';\n\n/**\n * Map the values in a CSV to the enum values in Transcend\n *\n * @param client - GraphQL client\n * @param requests - Set of privacy requests\n * @param options - Options\n */\nexport async function mapRequestEnumValues(\n client: GraphQLClient,\n requests: ObjByString[],\n {\n state,\n columnNameMap,\n }: {\n /** State value to write cache to */\n state: PersistedState<typeof CachedFileState>;\n /** Mapping of column names */\n columnNameMap: ColumnNameMap;\n },\n): Promise<void> {\n // Get mapped value\n const getMappedName = (attribute: ColumnName): string =>\n state.getValue('columnNames', attribute) || columnNameMap[attribute]!;\n\n // Fetch all data subjects in the organization\n const { internalSubjects } = await makeGraphQLRequest<{\n /** Query response */\n internalSubjects: DataSubject[];\n }>(client, DATA_SUBJECTS, { logger });\n\n // Map RequestAction\n logger.info(colors.magenta('Determining mapping of columns for request action'));\n const requestTypeToRequestAction: { [k in string]: RequestAction } = await mapEnumValues(\n getUniqueValuesForColumn(requests, getMappedName(ColumnName.RequestType)),\n Object.values(RequestAction),\n state.getValue('requestTypeToRequestAction'),\n );\n await state.setValue(requestTypeToRequestAction, 'requestTypeToRequestAction');\n\n // Map data subject type\n logger.info(colors.magenta('Determining mapping of columns for subject'));\n const subjectTypeToSubjectName: { [k in string]: string } = await mapEnumValues(\n getUniqueValuesForColumn(requests, getMappedName(ColumnName.SubjectType)),\n internalSubjects.map(({ type }) => type),\n state.getValue('subjectTypeToSubjectName'),\n );\n await state.setValue(subjectTypeToSubjectName, 'subjectTypeToSubjectName');\n\n // Map locale\n logger.info(colors.magenta('Determining mapping of columns for locale'));\n const languageToLocale: { [k in string]: LocaleValue } = await mapEnumValues(\n getUniqueValuesForColumn(requests, getMappedName(ColumnName.Locale)),\n Object.values(LOCALE_KEY),\n state.getValue('languageToLocale'),\n );\n await state.setValue(languageToLocale, 'languageToLocale');\n logger.info(colors.magenta('Determining mapping of columns for request status'));\n\n // Map request status\n logger.info(colors.magenta('Determining mapping of columns for request status'));\n const requestStatusColumn = getMappedName(ColumnName.RequestStatus);\n const statusToRequestStatus: {\n [k in string]: CompletedRequestStatus | typeof NONE;\n } =\n requestStatusColumn === NONE\n ? {}\n : await mapEnumValues(\n getUniqueValuesForColumn(requests, requestStatusColumn),\n [...Object.values(CompletedRequestStatus), NONE],\n state.getValue('statusToRequestStatus'),\n );\n await state.setValue(statusToRequestStatus, 'statusToRequestStatus');\n\n // Map country\n logger.info(colors.magenta('Determining mapping of columns for country'));\n const countryColumn = getMappedName(ColumnName.Country);\n const regionToCountry: {\n [k in string]: IsoCountryCode | typeof NONE;\n } =\n countryColumn === NONE\n ? {}\n : await mapEnumValues(\n getUniqueValuesForColumn(requests, countryColumn),\n [...Object.values(IsoCountryCode), NONE],\n state.getValue('regionToCountry'),\n );\n await state.setValue(regionToCountry, 'regionToCountry');\n\n // Map country sub division\n logger.info(colors.magenta('Determining mapping of columns for country sub division'));\n const countrySubDivisionColumn = getMappedName(ColumnName.CountrySubDivision);\n const regionToCountrySubDivision: {\n [k in string]: IsoCountrySubdivisionCode | typeof NONE;\n } =\n countrySubDivisionColumn === NONE\n ? {}\n : await mapEnumValues(\n getUniqueValuesForColumn(requests, countrySubDivisionColumn),\n [...Object.values(IsoCountrySubdivisionCode), NONE],\n state.getValue('regionToCountrySubDivision'),\n );\n await state.setValue(regionToCountrySubDivision, 'regionToCountrySubDivision');\n}\n","import { LOCALE_KEY } from '@transcend-io/internationalization';\nimport type { PersistedState } from '@transcend-io/persisted-state';\nimport {\n NORMALIZE_PHONE_NUMBER,\n CompletedRequestStatus,\n RequestAction,\n IdentifierType,\n IsoCountryCode,\n IsoCountrySubdivisionCode,\n} from '@transcend-io/privacy-types';\nimport type { AttributeKey } from '@transcend-io/sdk';\nimport { ObjByString, valuesOf } from '@transcend-io/type-utils';\nimport { splitCsvToList } from '@transcend-io/utils';\nimport * as t from 'io-ts';\nimport { DateFromISOString } from 'io-ts-types';\n\nimport { CachedFileState, BLANK, BULK_APPLY, ColumnName, NONE } from './constants.js';\nimport { AttributeNameMap } from './mapColumnsToAttributes.js';\nimport { IdentifierNameMap } from './mapColumnsToIdentifiers.js';\nimport { ColumnNameMap } from './mapCsvColumnsToApi.js';\nimport { ParsedAttributeInput } from './parseAttributesFromString.js';\n\n/**\n * Shape of additional identifiers\n *\n * key of object is IdentifierType\n */\nexport const AttestedExtraIdentifiers = t.record(\n t.string,\n t.array(\n t.intersection([\n t.type({\n /** Value of identifier */\n value: t.string,\n }),\n t.partial({\n /** Name of identifier - option for non-custom identifier types */\n name: t.string,\n }),\n ]),\n ),\n);\n\n/** Type override */\nexport type AttestedExtraIdentifiers = t.TypeOf<typeof AttestedExtraIdentifiers>;\n\nexport const PrivacyRequestInput = t.intersection([\n t.type({\n /** Email of user */\n email: t.string,\n /** Extra identifiers */\n attestedExtraIdentifiers: AttestedExtraIdentifiers,\n /** Core identifier for user */\n coreIdentifier: t.string,\n /** Action type being submitted */\n requestType: valuesOf(RequestAction),\n /** Type of data subject */\n subjectType: t.string,\n }),\n t.partial({\n /** Country */\n country: valuesOf(IsoCountryCode),\n /** Country sub division */\n countrySubDivision: valuesOf(IsoCountrySubdivisionCode),\n /** Attribute inputs */\n attributes: t.array(ParsedAttributeInput),\n /** The status that the request should be created as */\n status: valuesOf(CompletedRequestStatus),\n /** The time that the request was created */\n createdAt: DateFromISOString,\n /** Data silo IDs to submit for */\n dataSiloIds: t.array(t.string),\n /** Language key to map to */\n locale: valuesOf(LOCALE_KEY),\n /** Workflow config ID to target explicitly */\n workflowConfigId: t.string,\n }),\n]);\n\n/** Type override */\nexport type PrivacyRequestInput = t.TypeOf<typeof PrivacyRequestInput>;\n\n/**\n * Transform the identifier value based on type\n *\n * @param identifierValue - Value of identifier\n * @param identifierType - Type of identifier\n * @param defaultPhoneCountryCode - Default country code for phone numbers\n * @returns Post-processed identifier\n */\nexport function normalizeIdentifierValue(\n identifierValue: string,\n identifierType: IdentifierType,\n defaultPhoneCountryCode: string,\n): string {\n // Lowercase email\n if (identifierType === IdentifierType.Email) {\n return identifierValue.toLowerCase();\n }\n\n // Normalize phone number\n if (identifierType === IdentifierType.Phone) {\n const normalized = identifierValue\n .replace(NORMALIZE_PHONE_NUMBER, '')\n .replace(/[()]/g, '')\n .replace(/[–]/g, '')\n .replace(/[:]/g, '')\n .replace(/[]/g, '')\n .replace(/[A-Za-z]/g, '');\n return !normalized\n ? ''\n : normalized.startsWith('+')\n ? normalized\n : `+${defaultPhoneCountryCode}${normalized}`;\n }\n return identifierValue;\n}\n\n/**\n * Take the raw rows in a CSV upload, and map those rows to the request\n * input shape that can be passed to the Transcend API to submit a privacy\n * request.\n *\n * @param requestInputs - CSV of requests to be uploaded\n * @param state - The cached set of mapping values\n * @param options - Options\n * @returns [raw input, request input] list\n */\nexport function mapCsvRowsToRequestInputs(\n requestInputs: ObjByString[],\n state: PersistedState<typeof CachedFileState>,\n {\n columnNameMap,\n identifierNameMap,\n attributeNameMap,\n requestAttributeKeys,\n defaultPhoneCountryCode = '1', // US\n }: {\n /** Default country code */\n defaultPhoneCountryCode?: string;\n /** Mapping of column names */\n columnNameMap: ColumnNameMap;\n /** Mapping of identifier names */\n identifierNameMap: IdentifierNameMap;\n /** Mapping of attribute names */\n attributeNameMap: AttributeNameMap;\n /** Request attribute keys */\n requestAttributeKeys: AttributeKey[];\n },\n): [Record<string, string>, PrivacyRequestInput][] {\n // map the CSV to request input\n const getMappedName = (attribute: ColumnName): string =>\n state.getValue('columnNames', attribute) || columnNameMap[attribute]!;\n return requestInputs.map((input): [Record<string, string>, PrivacyRequestInput] => {\n // The extra identifiers to upload for this request\n const attestedExtraIdentifiers: AttestedExtraIdentifiers = {};\n Object.entries(identifierNameMap)\n // filter out skipped identifiers\n .filter(([, columnName]) => columnName !== NONE)\n .forEach(([identifierName, columnName]) => {\n // Determine the identifier type being specified\n const identifierType = Object.values(IdentifierType).includes(\n identifierName as any, // eslint-disable-line @typescript-eslint/no-explicit-any\n )\n ? (identifierName as IdentifierType)\n : IdentifierType.Custom;\n\n // Only add the identifier if the value exists\n const identifierValue = input[columnName];\n if (identifierValue) {\n const normalized = normalizeIdentifierValue(\n identifierValue,\n identifierType,\n defaultPhoneCountryCode,\n );\n if (normalized) {\n // Initialize\n if (!attestedExtraIdentifiers[identifierType]) {\n attestedExtraIdentifiers[identifierType] = [];\n }\n\n // Add the identifier\n attestedExtraIdentifiers[identifierType]!.push({\n value: normalized,\n name: identifierName,\n });\n }\n }\n });\n\n // The extra attributes to upload for this request\n const attributes: ParsedAttributeInput[] = [];\n Object.entries(attributeNameMap)\n // filter out skipped attributes\n .filter(([, columnName]) => columnName !== NONE)\n .forEach(([attributeName, columnName]) => {\n // Only add the identifier if the value exists\n const attributeValueString = input[columnName];\n if (attributeValueString) {\n // Add the attribute\n const isMulti =\n requestAttributeKeys.find((attr) => attr.name === attributeName)?.type ===\n 'MULTI_SELECT';\n attributes.push({\n values: isMulti ? splitCsvToList(attributeValueString) : attributeValueString,\n key: attributeName,\n });\n }\n });\n\n const requestTypeColumn = getMappedName(ColumnName.RequestType);\n const dataSubjectTypeColumn = getMappedName(ColumnName.SubjectType);\n return [\n input,\n {\n email: input[getMappedName(ColumnName.Email)],\n attestedExtraIdentifiers,\n attributes,\n coreIdentifier: input[getMappedName(ColumnName.CoreIdentifier)],\n requestType:\n requestTypeColumn === BULK_APPLY\n ? state.getValue('requestTypeToRequestAction', BLANK)\n : state.getValue('requestTypeToRequestAction', input[requestTypeColumn]),\n subjectType:\n dataSubjectTypeColumn === BULK_APPLY\n ? state.getValue('subjectTypeToSubjectName', BLANK)\n : state.getValue('subjectTypeToSubjectName', input[dataSubjectTypeColumn]),\n ...(getMappedName(ColumnName.Locale) !== NONE && input[getMappedName(ColumnName.Locale)]\n ? {\n locale: state.getValue('languageToLocale', input[getMappedName(ColumnName.Locale)]),\n }\n : {}),\n ...(getMappedName(ColumnName.Country) !== NONE && input[getMappedName(ColumnName.Country)]\n ? {\n country: state.getValue(\n 'regionToCountry',\n input[getMappedName(ColumnName.Country)],\n ) as IsoCountryCode,\n }\n : {}),\n ...(getMappedName(ColumnName.CountrySubDivision) !== NONE &&\n input[getMappedName(ColumnName.CountrySubDivision)]\n ? {\n countrySubDivision: state.getValue(\n 'regionToCountrySubDivision',\n input[getMappedName(ColumnName.CountrySubDivision)],\n ) as IsoCountrySubdivisionCode,\n }\n : {}),\n ...(getMappedName(ColumnName.RequestStatus) !== NONE &&\n state.getValue('statusToRequestStatus', input[getMappedName(ColumnName.RequestStatus)]) !==\n NONE &&\n input[getMappedName(ColumnName.RequestStatus)]\n ? {\n status: state.getValue(\n 'statusToRequestStatus',\n input[getMappedName(ColumnName.RequestStatus)],\n ) as CompletedRequestStatus,\n }\n : {}),\n ...(getMappedName(ColumnName.CreatedAt) !== NONE &&\n input[getMappedName(ColumnName.CreatedAt)]\n ? {\n createdAt: new Date(input[getMappedName(ColumnName.CreatedAt)]),\n }\n : {}),\n ...(getMappedName(ColumnName.DataSiloIds) !== NONE &&\n input[getMappedName(ColumnName.DataSiloIds)]\n ? {\n dataSiloIds: splitCsvToList(input[getMappedName(ColumnName.DataSiloIds)]),\n }\n : {}),\n ...(getMappedName(ColumnName.WorkflowConfigId) !== NONE &&\n input[getMappedName(ColumnName.WorkflowConfigId)]\n ? {\n workflowConfigId: input[getMappedName(ColumnName.WorkflowConfigId)],\n }\n : {}),\n },\n ];\n });\n}\n","import type { PersistedState } from '@transcend-io/persisted-state';\nimport { INITIALIZER, type Initializer, makeGraphQLRequest } from '@transcend-io/sdk';\nimport type { GraphQLClient } from 'graphql-request';\nimport inquirer from 'inquirer';\n\nimport { logger } from '../../logger.js';\nimport { CachedFileState, IDENTIFIER_BLOCK_LIST } from './constants.js';\nimport { fuzzyMatchColumns } from './fuzzyMatchColumns.js';\n\n/**\n * Mapping from identifier name to request input parameter\n */\nexport type IdentifierNameMap = {\n [k in string]: string;\n};\n\n/**\n * Create a mapping from the identifier names that can be included\n * at request submission, to the names of the columns that map to those\n * identifiers.\n *\n * @param client - GraphQL client\n * @param columnNames - The set of all column names\n * @param state - Cached state of this mapping\n * @returns Mapping from identifier name to column name\n */\nexport async function mapColumnsToIdentifiers(\n client: GraphQLClient,\n columnNames: string[],\n state: PersistedState<typeof CachedFileState>,\n): Promise<IdentifierNameMap> {\n // Grab the initializer\n const { initializer } = await makeGraphQLRequest<{\n /** Query response */\n initializer: Initializer;\n }>(client, INITIALIZER, { logger });\n\n // Determine the columns that should be mapped\n const columnQuestions = initializer.identifiers.filter(\n ({ name }) => !state.getValue('identifierNames', name) && !IDENTIFIER_BLOCK_LIST.includes(name),\n );\n\n // Skip mapping when everything is mapped\n const identifierNameMap =\n columnQuestions.length === 0\n ? {}\n : // prompt questions to map columns\n await inquirer.prompt<{\n [k in string]: string;\n }>(\n columnQuestions.map(({ name }) => {\n const matches = fuzzyMatchColumns(columnNames, name, false);\n return {\n name,\n message: `Choose the column that will be used to map in the identifier: ${name}`,\n type: 'list',\n default: matches[0],\n choices: matches,\n };\n }),\n );\n await Promise.all(\n Object.entries(identifierNameMap).map(([k, v]) => state.setValue(v, 'identifierNames', k)),\n );\n\n return {\n ...state.getValue('identifierNames'),\n ...identifierNameMap,\n };\n}\n","import type { PersistedState } from '@transcend-io/persisted-state';\nimport type { AttributeKey } from '@transcend-io/sdk';\nimport type { GraphQLClient } from 'graphql-request';\nimport inquirer from 'inquirer';\n\nimport { CachedFileState } from './constants.js';\nimport { fuzzyMatchColumns } from './fuzzyMatchColumns.js';\n\n/**\n * Mapping from attribute name to request input parameter\n */\nexport type AttributeNameMap = {\n [k in string]: string;\n};\n\n/**\n * Create a mapping from the attributes names that can be included\n * at request submission, to the names of the columns that map to those\n * attributes.\n *\n * @param client - GraphQL client\n * @param columnNames - The set of all column names\n * @param state - Cached state of this mapping\n * @param requestAttributeKeys - Attribute keys to map\n * @returns Mapping from attributes name to column name\n */\nexport async function mapColumnsToAttributes(\n client: GraphQLClient,\n columnNames: string[],\n state: PersistedState<typeof CachedFileState>,\n requestAttributeKeys: AttributeKey[],\n): Promise<AttributeNameMap> {\n // Determine the columns that should be mapped\n const columnQuestions = requestAttributeKeys.filter(\n ({ name }) => !state.getValue('attributeNames', name),\n );\n\n // Skip mapping when everything is mapped\n const attributeNameMap =\n columnQuestions.length === 0\n ? {}\n : // prompt questions to map columns\n await inquirer.prompt<{\n [k in string]: string;\n }>(\n columnQuestions.map(({ name }) => {\n const matches = fuzzyMatchColumns(columnNames, name, false);\n return {\n name,\n message: `Choose the column that will be used to map in the attribute: ${name}`,\n type: 'list',\n default: matches[0],\n choices: matches,\n };\n }),\n );\n await Promise.all(\n Object.entries(attributeNameMap).map(([k, v]) => state.setValue(v, 'attributeNames', k)),\n );\n\n return {\n ...state.getValue('attributeNames'),\n ...attributeNameMap,\n };\n}\n","import { join } from 'node:path';\n\nimport { PersistedState } from '@transcend-io/persisted-state';\nimport {\n buildTranscendGraphQLClient,\n createSombraGotInstance,\n fetchAllRequestAttributeKeys,\n} from '@transcend-io/sdk';\nimport { map } from '@transcend-io/utils';\nimport cliProgress from 'cli-progress';\n/* eslint-disable max-lines */\nimport colors from 'colors';\nimport * as t from 'io-ts';\nimport { uniq } from 'lodash-es';\n\nimport { DEFAULT_TRANSCEND_API } from '../../constants.js';\nimport { logger } from '../../logger.js';\nimport { CachedRequestState, CachedFileState } from './constants.js';\nimport { extractClientError } from './extractClientError.js';\nimport { filterRows } from './filterRows.js';\nimport { mapColumnsToAttributes } from './mapColumnsToAttributes.js';\nimport { mapColumnsToIdentifiers } from './mapColumnsToIdentifiers.js';\nimport { mapCsvColumnsToApi } from './mapCsvColumnsToApi.js';\nimport { mapCsvRowsToRequestInputs } from './mapCsvRowsToRequestInputs.js';\nimport { mapRequestEnumValues } from './mapRequestEnumValues.js';\nimport { parseAttributesFromString } from './parseAttributesFromString.js';\nimport { readCsv } from './readCsv.js';\nimport { submitPrivacyRequest } from './submitPrivacyRequest.js';\n\n/**\n * Upload a set of privacy requests from CSV\n *\n * @param options - Options\n */\nexport async function uploadPrivacyRequestsFromCsv({\n cacheFilepath,\n requestReceiptFolder,\n file,\n auth,\n sombraAuth,\n concurrency = 100,\n defaultPhoneCountryCode = '1', // USA\n transcendUrl = DEFAULT_TRANSCEND_API,\n attributes = [],\n emailIsVerified = true,\n skipFilterStep = false,\n skipSendingReceipt = true,\n isTest = false,\n isSilent = true,\n debug = false,\n dryRun = false,\n}: {\n /** File to cache metadata about mapping of CSV shape to script */\n cacheFilepath: string;\n /** File where request receipts are stored */\n requestReceiptFolder: string;\n /** CSV file path */\n file: string;\n /** Transcend API key authentication */\n auth: string;\n /** Default country code for phone numbers */\n defaultPhoneCountryCode?: string;\n /** Concurrency to upload in */\n concurrency?: number;\n /** API URL for Transcend backend */\n transcendUrl?: string;\n /** Sombra API key authentication */\n sombraAuth?: string;\n /** Include debug logs */\n debug?: boolean;\n /** Skip the step where requests are filtered */\n skipFilterStep?: boolean;\n /** Whether test requests are being uploaded */\n isTest?: boolean;\n /** Whether requests are uploaded in silent mode */\n isSilent?: boolean;\n /** Whether to send the email receipt */\n skipSendingReceipt?: boolean;\n /** Whether the email was verified up front */\n emailIsVerified?: boolean;\n /** Attributes string pre-parse */\n attributes?: string[];\n /** Whether a dry run is happening */\n dryRun?: boolean;\n}): Promise<void> {\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 // Parse out the extra attributes to apply to all requests uploaded\n const parsedAttributes = parseAttributesFromString(attributes);\n\n // Create a new state to persist the metadata that\n // maps the request inputs to the Transcend API shape\n const state = new PersistedState(cacheFilepath, CachedFileState, {\n columnNames: {},\n requestTypeToRequestAction: {},\n subjectTypeToSubjectName: {},\n languageToLocale: {},\n statusToRequestStatus: {},\n identifierNames: {},\n attributeNames: {},\n regionToCountrySubDivision: {},\n regionToCountry: {},\n });\n\n // Create a new state file to store the requests from this run.\n // `toISOString()` contains colons (e.g. 2026-07-06T04:33:12.345Z) which are\n // illegal characters in Windows filenames, so strip them out to keep the\n // auto-generated receipt filename cross-platform.\n const requestCacheFile = join(\n requestReceiptFolder,\n `tr-request-upload-${new Date()\n .toISOString()\n .replace(/:/g, '-')}-${file.split('/').pop()}`.replace('.csv', '.json'),\n );\n const requestState = new PersistedState(requestCacheFile, CachedRequestState, {\n successfulRequests: [],\n duplicateRequests: [],\n failingRequests: [],\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 // Read in the list of integration requests\n const requestsList = readCsv(file, t.record(t.string, t.string));\n const columnNames = uniq(requestsList.map((x) => Object.keys(x)).flat());\n\n // Log out an example request\n if (requestsList.length === 0) {\n throw new Error(\n 'No Requests found in list! Ensure the first row of the CSV is a header and the rest are requests.',\n );\n }\n if (debug) {\n const firstRequest = requestsList[0];\n logger.info(colors.magenta(`First request: ${JSON.stringify(firstRequest, null, 2)}`));\n }\n // Determine what rows in the CSV should be imported\n // Choose columns that contain metadata to filter the requests\n const filteredRequestList = skipFilterStep ? requestsList : await filterRows(requestsList);\n\n // Build a GraphQL client\n const client = buildTranscendGraphQLClient(transcendUrl, auth);\n // Grab the request attributes\n const requestAttributeKeys = await fetchAllRequestAttributeKeys(client, { logger });\n // Determine the columns that should be mapped\n const columnNameMap = await mapCsvColumnsToApi(columnNames, state);\n const identifierNameMap = await mapColumnsToIdentifiers(client, columnNames, state);\n const attributeNameMap = await mapColumnsToAttributes(\n client,\n columnNames,\n state,\n requestAttributeKeys,\n );\n await mapRequestEnumValues(client, filteredRequestList, {\n state,\n columnNameMap,\n });\n\n // map the CSV to request input\n const requestInputs = mapCsvRowsToRequestInputs(filteredRequestList, state, {\n defaultPhoneCountryCode,\n columnNameMap,\n identifierNameMap,\n attributeNameMap,\n requestAttributeKeys,\n });\n\n // start the progress bar with a total value of 200 and start value of 0\n if (!debug) {\n progressBar.start(requestInputs.length, 0);\n }\n let total = 0;\n // Submit each request\n await map(\n requestInputs,\n async ([rawRow, requestInput], ind) => {\n // The identifier to log, only include personal data if debug mode is on\n const requestLogId = debug\n ? `email:${requestInput.email} | coreIdentifier:${requestInput.coreIdentifier}`\n : `row:${ind.toString()}`;\n\n if (debug) {\n logger.info(\n colors.magenta(\n `[${ind + 1}/${requestInputs.length}] Importing: ${JSON.stringify(\n requestInput,\n null,\n 2,\n )}`,\n ),\n );\n }\n\n // Skip on dry run\n if (dryRun) {\n logger.info(colors.magenta('Bailing out on dry run because dryRun is set'));\n return;\n }\n\n try {\n // Make the GraphQL request to submit the privacy request\n const requestResponse = await submitPrivacyRequest(sombra, requestInput, {\n details: `Uploaded by Transcend Cli: \"tr-request-upload\" : ${JSON.stringify(\n rawRow,\n null,\n 2,\n )}`,\n isTest,\n emailIsVerified,\n skipSendingReceipt,\n isSilent,\n additionalAttributes: parsedAttributes,\n });\n\n // Log success\n if (debug) {\n logger.info(\n colors.green(\n `[${ind + 1}/${\n requestInputs.length\n }] Successfully submitted the test data subject request: \"${requestLogId}\"`,\n ),\n );\n logger.info(\n colors.green(\n `[${ind + 1}/${requestInputs.length}] View it at: \"${requestResponse.link}\"`,\n ),\n );\n }\n\n // Cache successful upload\n const successfulRequests = requestState.getValue('successfulRequests');\n successfulRequests.push({\n id: requestResponse.id,\n link: requestResponse.link,\n rowIndex: ind,\n coreIdentifier: requestResponse.coreIdentifier,\n attemptedAt: new Date().toISOString(),\n });\n await requestState.setValue(successfulRequests, 'successfulRequests');\n } catch (err) {\n const msg = `${err.message} - ${JSON.stringify(err.response?.body, null, 2)}`;\n const clientError = extractClientError(msg);\n\n if (clientError === 'Client error: You have already made this request.') {\n if (debug) {\n logger.info(\n colors.yellow(\n `[${ind + 1}/${requestInputs.length}] Skipping request as it is a duplicate`,\n ),\n );\n }\n const duplicateRequests = requestState.getValue('duplicateRequests');\n duplicateRequests.push({\n coreIdentifier: requestInput.coreIdentifier,\n rowIndex: ind,\n attemptedAt: new Date().toISOString(),\n });\n await requestState.setValue(duplicateRequests, 'duplicateRequests');\n } else {\n const failingRequests = requestState.getValue('failingRequests');\n failingRequests.push({\n ...requestInput,\n rowIndex: ind,\n error: clientError || msg,\n attemptedAt: new Date().toISOString(),\n });\n await requestState.setValue(failingRequests, 'failingRequests');\n if (debug) {\n logger.error(colors.red(clientError || msg));\n logger.error(\n colors.red(\n `[${ind + 1}/${\n requestInputs.length\n }] Failed to submit request for: \"${requestLogId}\"`,\n ),\n );\n }\n }\n }\n\n total += 1;\n if (!debug) {\n progressBar.update(total);\n }\n },\n {\n concurrency,\n },\n );\n\n progressBar.stop();\n const t1 = new Date().getTime();\n const totalTime = t1 - t0;\n\n // Log completion time\n logger.info(colors.green(`Completed upload in \"${totalTime / 1000}\" seconds.`));\n\n // Log duplicates\n if (requestState.getValue('duplicateRequests').length > 0) {\n logger.info(\n colors.yellow(\n `Encountered \"${requestState.getValue('duplicateRequests').length}\" duplicate requests. ` +\n `See \"${requestCacheFile}\" to review the core identifiers for these requests.`,\n ),\n );\n }\n\n // Log errors\n if (requestState.getValue('failingRequests').length > 0) {\n logger.error(\n colors.red(\n `Encountered \"${requestState.getValue('failingRequests').length}\" errors. ` +\n `See \"${requestCacheFile}\" to review the error messages and inputs.`,\n ),\n );\n process.exit(1);\n }\n}\n/* eslint-enable max-lines */\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"uploadPrivacyRequestsFromCsv-M3iY9cjb.mjs","names":[],"sources":["../src/lib/requests/mapEnumValues.ts","../src/lib/requests/getUniqueValuesForColumn.ts","../src/lib/requests/filterRows.ts","../src/lib/requests/mapCsvColumnsToApi.ts","../src/lib/requests/mapRequestEnumValues.ts","../src/lib/requests/mapCsvRowsToRequestInputs.ts","../src/lib/requests/mapColumnsToIdentifiers.ts","../src/lib/requests/mapColumnsToAttributes.ts","../src/lib/requests/uploadPrivacyRequestsFromCsv.ts"],"sourcesContent":["import { apply, ObjByString } from '@transcend-io/type-utils';\nimport inquirer from 'inquirer';\nimport autoCompletePrompt from 'inquirer-autocomplete-prompt';\n\nimport { fuzzySearch } from './fuzzyMatchColumns.js';\n\n/**\n * Map a set of inputs to a set of outputs\n *\n * @param csvInputs - Input list\n * @param expectedOutputs - Output list\n * @param cache - Cache\n * @returns Mapping from row to enum value\n */\nexport async function mapEnumValues<TValue extends string>(\n csvInputs: string[],\n expectedOutputs: TValue[],\n cache: { [k in string]: TValue },\n): Promise<{ [k in string]: TValue }> {\n inquirer.registerPrompt('autocomplete', autoCompletePrompt);\n\n const inputs = csvInputs.map((item) => item || '<blank>').filter((value) => !cache[value]);\n if (inputs.length === 0) {\n return cache;\n }\n const result = await inquirer.prompt<{ [k in string]: TValue }>(\n inputs.map((value) => ({\n name: value,\n message: `Map value of: ${value}`,\n type: 'autocomplete',\n default: expectedOutputs.find((x) => fuzzySearch(value, x)),\n source: (answersSoFar: ObjByString, input: string) =>\n !input\n ? expectedOutputs\n : expectedOutputs.filter((x) => typeof x === 'string' && fuzzySearch(input, x)),\n })),\n );\n return {\n ...cache,\n ...apply(result, (r) =>\n typeof r === 'string' ? (r as TValue) : (Object.values(r)[0] as TValue),\n ),\n };\n}\n","import { ObjByString } from '@transcend-io/type-utils';\nimport { uniq } from 'lodash-es';\n\n/**\n * Return the unique set of values for a column in a CSV\n *\n * @param rows - Rows to look up\n * @param columnName - Name of column to grab values for\n * @returns Unique set of values in that column\n */\nexport function getUniqueValuesForColumn(rows: ObjByString[], columnName: string): string[] {\n return uniq(rows.map((row) => row[columnName] || '').flat());\n}\n","import { ObjByString } from '@transcend-io/type-utils';\nimport colors from 'colors';\nimport inquirer from 'inquirer';\nimport { uniq } from 'lodash-es';\n\nimport { logger } from '../../logger.js';\nimport { NONE } from './constants.js';\nimport { getUniqueValuesForColumn } from './getUniqueValuesForColumn.js';\n\n/**\n * Filter a list of CSV rows by column values\n * Choose columns that contain metadata to filter the requests\n *\n * @param rows - Rows to filter\n * @returns Filtered rows\n */\nexport async function filterRows(rows: ObjByString[]): Promise<ObjByString[]> {\n // Determine set of column names\n const columnNames = uniq(rows.map((x) => Object.keys(x)).flat());\n\n // update these variables recursively\n let filteredRows = rows;\n let keepFiltering = true;\n\n // loop over\n while (keepFiltering) {\n // Prompt user for column to filter on\n\n const { filterColumnName } = await inquirer.prompt<{\n /** Name of column to filter on */\n filterColumnName: string;\n }>([\n {\n name: 'filterColumnName',\n // eslint-disable-next-line max-len\n message: `If you need to filter the list of requests to import, choose the column to filter on. Currently ${filteredRows.length} rows.`,\n type: 'list',\n default: columnNames,\n choices: [NONE, ...columnNames],\n },\n ]);\n\n // Determine if filtering should continue, or loop should be exited\n keepFiltering = NONE !== filterColumnName;\n if (keepFiltering) {\n const options = getUniqueValuesForColumn(filteredRows, filterColumnName);\n\n const { valuesToKeep } = await inquirer.prompt<{\n /** Values to keep */\n valuesToKeep: string[];\n }>([\n {\n name: 'valuesToKeep',\n message: 'Keep rows matching this value',\n type: 'checkbox',\n default: columnNames,\n choices: options,\n },\n ]);\n\n filteredRows = filteredRows.filter((request) =>\n valuesToKeep.includes(request[filterColumnName]),\n );\n }\n }\n\n logger.info(colors.magenta(`Importing ${filteredRows.length} requests`));\n return filteredRows;\n}\n","import type { PersistedState } from '@transcend-io/persisted-state';\nimport { getValues, getEntries } from '@transcend-io/type-utils';\nimport inquirer from 'inquirer';\nimport { startCase } from 'lodash-es';\n\nimport { ColumnName, CachedFileState, IS_REQUIRED, CAN_APPLY_IN_BULK } from './constants.js';\nimport { fuzzyMatchColumns } from './fuzzyMatchColumns.js';\n\n/**\n * Mapping from column name to request input parameter\n */\nexport type ColumnNameMap = {\n [k in ColumnName]?: string;\n};\n\n/**\n * Determine the mapping between columns in CSV\n *\n * @param columnNames - The set of column names\n * @param state - The cached file state used to map DSR inputs\n * @returns The column name mapping\n */\nexport async function mapCsvColumnsToApi(\n columnNames: string[],\n state: PersistedState<typeof CachedFileState>,\n): Promise<ColumnNameMap> {\n // Determine the columns that should be mapped\n const columnQuestions = getValues(ColumnName).filter(\n (name) => !state.getValue('columnNames', name),\n );\n\n // Skip mapping when everything is mapped\n const columnNameMap =\n columnQuestions.length === 0\n ? {}\n : // prompt questions to map columns\n await inquirer.prompt<{\n [k in ColumnName]?: string;\n }>(\n columnQuestions.map((name) => {\n const field = startCase(name.replace('ColumnName', ''));\n const matches = fuzzyMatchColumns(\n columnNames,\n field,\n IS_REQUIRED[name],\n !!CAN_APPLY_IN_BULK[name],\n );\n return {\n name,\n message: `Choose the column that will be used to map in the field: ${field}`,\n type: 'list',\n default: matches[0],\n choices: matches,\n };\n }),\n );\n\n await Promise.all(getEntries(columnNameMap).map(([k, v]) => state.setValue(v, 'columnNames', k)));\n return columnNameMap;\n}\n","import { LOCALE_KEY, type LocaleValue } from '@transcend-io/internationalization';\nimport type { PersistedState } from '@transcend-io/persisted-state';\nimport {\n CompletedRequestStatus,\n RequestAction,\n IsoCountryCode,\n IsoCountrySubdivisionCode,\n} from '@transcend-io/privacy-types';\nimport { makeGraphQLRequest, DATA_SUBJECTS, type DataSubject } from '@transcend-io/sdk';\nimport { ObjByString } from '@transcend-io/type-utils';\nimport colors from 'colors';\nimport { GraphQLClient } from 'graphql-request';\n\nimport { logger } from '../../logger.js';\nimport { CachedFileState, NONE, ColumnName } from './constants.js';\nimport { getUniqueValuesForColumn } from './getUniqueValuesForColumn.js';\nimport { ColumnNameMap } from './mapCsvColumnsToApi.js';\nimport { mapEnumValues } from './mapEnumValues.js';\n\n/**\n * Map the values in a CSV to the enum values in Transcend\n *\n * @param client - GraphQL client\n * @param requests - Set of privacy requests\n * @param options - Options\n */\nexport async function mapRequestEnumValues(\n client: GraphQLClient,\n requests: ObjByString[],\n {\n state,\n columnNameMap,\n }: {\n /** State value to write cache to */\n state: PersistedState<typeof CachedFileState>;\n /** Mapping of column names */\n columnNameMap: ColumnNameMap;\n },\n): Promise<void> {\n // Get mapped value\n const getMappedName = (attribute: ColumnName): string =>\n state.getValue('columnNames', attribute) || columnNameMap[attribute]!;\n\n // Fetch all data subjects in the organization\n const { internalSubjects } = await makeGraphQLRequest<{\n /** Query response */\n internalSubjects: DataSubject[];\n }>(client, DATA_SUBJECTS, { logger });\n\n // Map RequestAction\n logger.info(colors.magenta('Determining mapping of columns for request action'));\n const requestTypeToRequestAction: { [k in string]: RequestAction } = await mapEnumValues(\n getUniqueValuesForColumn(requests, getMappedName(ColumnName.RequestType)),\n Object.values(RequestAction),\n state.getValue('requestTypeToRequestAction'),\n );\n await state.setValue(requestTypeToRequestAction, 'requestTypeToRequestAction');\n\n // Map data subject type\n logger.info(colors.magenta('Determining mapping of columns for subject'));\n const subjectTypeToSubjectName: { [k in string]: string } = await mapEnumValues(\n getUniqueValuesForColumn(requests, getMappedName(ColumnName.SubjectType)),\n internalSubjects.map(({ type }) => type),\n state.getValue('subjectTypeToSubjectName'),\n );\n await state.setValue(subjectTypeToSubjectName, 'subjectTypeToSubjectName');\n\n // Map locale\n logger.info(colors.magenta('Determining mapping of columns for locale'));\n const languageToLocale: { [k in string]: LocaleValue } = await mapEnumValues(\n getUniqueValuesForColumn(requests, getMappedName(ColumnName.Locale)),\n Object.values(LOCALE_KEY),\n state.getValue('languageToLocale'),\n );\n await state.setValue(languageToLocale, 'languageToLocale');\n logger.info(colors.magenta('Determining mapping of columns for request status'));\n\n // Map request status\n logger.info(colors.magenta('Determining mapping of columns for request status'));\n const requestStatusColumn = getMappedName(ColumnName.RequestStatus);\n const statusToRequestStatus: {\n [k in string]: CompletedRequestStatus | typeof NONE;\n } =\n requestStatusColumn === NONE\n ? {}\n : await mapEnumValues(\n getUniqueValuesForColumn(requests, requestStatusColumn),\n [...Object.values(CompletedRequestStatus), NONE],\n state.getValue('statusToRequestStatus'),\n );\n await state.setValue(statusToRequestStatus, 'statusToRequestStatus');\n\n // Map country\n logger.info(colors.magenta('Determining mapping of columns for country'));\n const countryColumn = getMappedName(ColumnName.Country);\n const regionToCountry: {\n [k in string]: IsoCountryCode | typeof NONE;\n } =\n countryColumn === NONE\n ? {}\n : await mapEnumValues(\n getUniqueValuesForColumn(requests, countryColumn),\n [...Object.values(IsoCountryCode), NONE],\n state.getValue('regionToCountry'),\n );\n await state.setValue(regionToCountry, 'regionToCountry');\n\n // Map country sub division\n logger.info(colors.magenta('Determining mapping of columns for country sub division'));\n const countrySubDivisionColumn = getMappedName(ColumnName.CountrySubDivision);\n const regionToCountrySubDivision: {\n [k in string]: IsoCountrySubdivisionCode | typeof NONE;\n } =\n countrySubDivisionColumn === NONE\n ? {}\n : await mapEnumValues(\n getUniqueValuesForColumn(requests, countrySubDivisionColumn),\n [...Object.values(IsoCountrySubdivisionCode), NONE],\n state.getValue('regionToCountrySubDivision'),\n );\n await state.setValue(regionToCountrySubDivision, 'regionToCountrySubDivision');\n}\n","import { LOCALE_KEY } from '@transcend-io/internationalization';\nimport type { PersistedState } from '@transcend-io/persisted-state';\nimport {\n NORMALIZE_PHONE_NUMBER,\n CompletedRequestStatus,\n RequestAction,\n IdentifierType,\n IsoCountryCode,\n IsoCountrySubdivisionCode,\n} from '@transcend-io/privacy-types';\nimport type { AttributeKey } from '@transcend-io/sdk';\nimport { ObjByString, valuesOf } from '@transcend-io/type-utils';\nimport { splitCsvToList } from '@transcend-io/utils';\nimport * as t from 'io-ts';\nimport { DateFromISOString } from 'io-ts-types';\n\nimport { CachedFileState, BLANK, BULK_APPLY, ColumnName, NONE } from './constants.js';\nimport { AttributeNameMap } from './mapColumnsToAttributes.js';\nimport { IdentifierNameMap } from './mapColumnsToIdentifiers.js';\nimport { ColumnNameMap } from './mapCsvColumnsToApi.js';\nimport { ParsedAttributeInput } from './parseAttributesFromString.js';\n\n/**\n * Shape of additional identifiers\n *\n * key of object is IdentifierType\n */\nexport const AttestedExtraIdentifiers = t.record(\n t.string,\n t.array(\n t.intersection([\n t.type({\n /** Value of identifier */\n value: t.string,\n }),\n t.partial({\n /** Name of identifier - option for non-custom identifier types */\n name: t.string,\n }),\n ]),\n ),\n);\n\n/** Type override */\nexport type AttestedExtraIdentifiers = t.TypeOf<typeof AttestedExtraIdentifiers>;\n\nexport const PrivacyRequestInput = t.intersection([\n t.type({\n /** Email of user */\n email: t.string,\n /** Extra identifiers */\n attestedExtraIdentifiers: AttestedExtraIdentifiers,\n /** Core identifier for user */\n coreIdentifier: t.string,\n /** Action type being submitted */\n requestType: valuesOf(RequestAction),\n /** Type of data subject */\n subjectType: t.string,\n }),\n t.partial({\n /** Country */\n country: valuesOf(IsoCountryCode),\n /** Country sub division */\n countrySubDivision: valuesOf(IsoCountrySubdivisionCode),\n /** Attribute inputs */\n attributes: t.array(ParsedAttributeInput),\n /** The status that the request should be created as */\n status: valuesOf(CompletedRequestStatus),\n /** The time that the request was created */\n createdAt: DateFromISOString,\n /** Data silo IDs to submit for */\n dataSiloIds: t.array(t.string),\n /** Language key to map to */\n locale: valuesOf(LOCALE_KEY),\n /** Workflow config ID to target explicitly */\n workflowConfigId: t.string,\n }),\n]);\n\n/** Type override */\nexport type PrivacyRequestInput = t.TypeOf<typeof PrivacyRequestInput>;\n\n/**\n * Transform the identifier value based on type\n *\n * @param identifierValue - Value of identifier\n * @param identifierType - Type of identifier\n * @param defaultPhoneCountryCode - Default country code for phone numbers\n * @returns Post-processed identifier\n */\nexport function normalizeIdentifierValue(\n identifierValue: string,\n identifierType: IdentifierType,\n defaultPhoneCountryCode: string,\n): string {\n // Lowercase email\n if (identifierType === IdentifierType.Email) {\n return identifierValue.toLowerCase();\n }\n\n // Normalize phone number\n if (identifierType === IdentifierType.Phone) {\n const normalized = identifierValue\n .replace(NORMALIZE_PHONE_NUMBER, '')\n .replace(/[()]/g, '')\n .replace(/[–]/g, '')\n .replace(/[:]/g, '')\n .replace(/[]/g, '')\n .replace(/[A-Za-z]/g, '');\n return !normalized\n ? ''\n : normalized.startsWith('+')\n ? normalized\n : `+${defaultPhoneCountryCode}${normalized}`;\n }\n return identifierValue;\n}\n\n/**\n * Take the raw rows in a CSV upload, and map those rows to the request\n * input shape that can be passed to the Transcend API to submit a privacy\n * request.\n *\n * @param requestInputs - CSV of requests to be uploaded\n * @param state - The cached set of mapping values\n * @param options - Options\n * @returns [raw input, request input] list\n */\nexport function mapCsvRowsToRequestInputs(\n requestInputs: ObjByString[],\n state: PersistedState<typeof CachedFileState>,\n {\n columnNameMap,\n identifierNameMap,\n attributeNameMap,\n requestAttributeKeys,\n defaultPhoneCountryCode = '1', // US\n }: {\n /** Default country code */\n defaultPhoneCountryCode?: string;\n /** Mapping of column names */\n columnNameMap: ColumnNameMap;\n /** Mapping of identifier names */\n identifierNameMap: IdentifierNameMap;\n /** Mapping of attribute names */\n attributeNameMap: AttributeNameMap;\n /** Request attribute keys */\n requestAttributeKeys: AttributeKey[];\n },\n): [Record<string, string>, PrivacyRequestInput][] {\n // map the CSV to request input\n const getMappedName = (attribute: ColumnName): string =>\n state.getValue('columnNames', attribute) || columnNameMap[attribute]!;\n return requestInputs.map((input): [Record<string, string>, PrivacyRequestInput] => {\n // The extra identifiers to upload for this request\n const attestedExtraIdentifiers: AttestedExtraIdentifiers = {};\n Object.entries(identifierNameMap)\n // filter out skipped identifiers\n .filter(([, columnName]) => columnName !== NONE)\n .forEach(([identifierName, columnName]) => {\n // Determine the identifier type being specified\n const identifierType = Object.values(IdentifierType).includes(\n identifierName as any, // eslint-disable-line @typescript-eslint/no-explicit-any\n )\n ? (identifierName as IdentifierType)\n : IdentifierType.Custom;\n\n // Only add the identifier if the value exists\n const identifierValue = input[columnName];\n if (identifierValue) {\n const normalized = normalizeIdentifierValue(\n identifierValue,\n identifierType,\n defaultPhoneCountryCode,\n );\n if (normalized) {\n // Initialize\n if (!attestedExtraIdentifiers[identifierType]) {\n attestedExtraIdentifiers[identifierType] = [];\n }\n\n // Add the identifier\n attestedExtraIdentifiers[identifierType]!.push({\n value: normalized,\n name: identifierName,\n });\n }\n }\n });\n\n // The extra attributes to upload for this request\n const attributes: ParsedAttributeInput[] = [];\n Object.entries(attributeNameMap)\n // filter out skipped attributes\n .filter(([, columnName]) => columnName !== NONE)\n .forEach(([attributeName, columnName]) => {\n // Only add the identifier if the value exists\n const attributeValueString = input[columnName];\n if (attributeValueString) {\n // Add the attribute\n const isMulti =\n requestAttributeKeys.find((attr) => attr.name === attributeName)?.type ===\n 'MULTI_SELECT';\n attributes.push({\n values: isMulti ? splitCsvToList(attributeValueString) : attributeValueString,\n key: attributeName,\n });\n }\n });\n\n const requestTypeColumn = getMappedName(ColumnName.RequestType);\n const dataSubjectTypeColumn = getMappedName(ColumnName.SubjectType);\n return [\n input,\n {\n email: input[getMappedName(ColumnName.Email)],\n attestedExtraIdentifiers,\n attributes,\n coreIdentifier: input[getMappedName(ColumnName.CoreIdentifier)],\n requestType:\n requestTypeColumn === BULK_APPLY\n ? state.getValue('requestTypeToRequestAction', BLANK)\n : state.getValue('requestTypeToRequestAction', input[requestTypeColumn]),\n subjectType:\n dataSubjectTypeColumn === BULK_APPLY\n ? state.getValue('subjectTypeToSubjectName', BLANK)\n : state.getValue('subjectTypeToSubjectName', input[dataSubjectTypeColumn]),\n ...(getMappedName(ColumnName.Locale) !== NONE && input[getMappedName(ColumnName.Locale)]\n ? {\n locale: state.getValue('languageToLocale', input[getMappedName(ColumnName.Locale)]),\n }\n : {}),\n ...(getMappedName(ColumnName.Country) !== NONE && input[getMappedName(ColumnName.Country)]\n ? {\n country: state.getValue(\n 'regionToCountry',\n input[getMappedName(ColumnName.Country)],\n ) as IsoCountryCode,\n }\n : {}),\n ...(getMappedName(ColumnName.CountrySubDivision) !== NONE &&\n input[getMappedName(ColumnName.CountrySubDivision)]\n ? {\n countrySubDivision: state.getValue(\n 'regionToCountrySubDivision',\n input[getMappedName(ColumnName.CountrySubDivision)],\n ) as IsoCountrySubdivisionCode,\n }\n : {}),\n ...(getMappedName(ColumnName.RequestStatus) !== NONE &&\n state.getValue('statusToRequestStatus', input[getMappedName(ColumnName.RequestStatus)]) !==\n NONE &&\n input[getMappedName(ColumnName.RequestStatus)]\n ? {\n status: state.getValue(\n 'statusToRequestStatus',\n input[getMappedName(ColumnName.RequestStatus)],\n ) as CompletedRequestStatus,\n }\n : {}),\n ...(getMappedName(ColumnName.CreatedAt) !== NONE &&\n input[getMappedName(ColumnName.CreatedAt)]\n ? {\n createdAt: new Date(input[getMappedName(ColumnName.CreatedAt)]),\n }\n : {}),\n ...(getMappedName(ColumnName.DataSiloIds) !== NONE &&\n input[getMappedName(ColumnName.DataSiloIds)]\n ? {\n dataSiloIds: splitCsvToList(input[getMappedName(ColumnName.DataSiloIds)]),\n }\n : {}),\n ...(getMappedName(ColumnName.WorkflowConfigId) !== NONE &&\n input[getMappedName(ColumnName.WorkflowConfigId)]\n ? {\n workflowConfigId: input[getMappedName(ColumnName.WorkflowConfigId)],\n }\n : {}),\n },\n ];\n });\n}\n","import type { PersistedState } from '@transcend-io/persisted-state';\nimport { INITIALIZER, type Initializer, makeGraphQLRequest } from '@transcend-io/sdk';\nimport type { GraphQLClient } from 'graphql-request';\nimport inquirer from 'inquirer';\n\nimport { logger } from '../../logger.js';\nimport { CachedFileState, IDENTIFIER_BLOCK_LIST } from './constants.js';\nimport { fuzzyMatchColumns } from './fuzzyMatchColumns.js';\n\n/**\n * Mapping from identifier name to request input parameter\n */\nexport type IdentifierNameMap = {\n [k in string]: string;\n};\n\n/**\n * Create a mapping from the identifier names that can be included\n * at request submission, to the names of the columns that map to those\n * identifiers.\n *\n * @param client - GraphQL client\n * @param columnNames - The set of all column names\n * @param state - Cached state of this mapping\n * @returns Mapping from identifier name to column name\n */\nexport async function mapColumnsToIdentifiers(\n client: GraphQLClient,\n columnNames: string[],\n state: PersistedState<typeof CachedFileState>,\n): Promise<IdentifierNameMap> {\n // Grab the initializer\n const { initializer } = await makeGraphQLRequest<{\n /** Query response */\n initializer: Initializer;\n }>(client, INITIALIZER, { logger });\n\n // Determine the columns that should be mapped\n const columnQuestions = initializer.identifiers.filter(\n ({ name }) => !state.getValue('identifierNames', name) && !IDENTIFIER_BLOCK_LIST.includes(name),\n );\n\n // Skip mapping when everything is mapped\n const identifierNameMap =\n columnQuestions.length === 0\n ? {}\n : // prompt questions to map columns\n await inquirer.prompt<{\n [k in string]: string;\n }>(\n columnQuestions.map(({ name }) => {\n const matches = fuzzyMatchColumns(columnNames, name, false);\n return {\n name,\n message: `Choose the column that will be used to map in the identifier: ${name}`,\n type: 'list',\n default: matches[0],\n choices: matches,\n };\n }),\n );\n await Promise.all(\n Object.entries(identifierNameMap).map(([k, v]) => state.setValue(v, 'identifierNames', k)),\n );\n\n return {\n ...state.getValue('identifierNames'),\n ...identifierNameMap,\n };\n}\n","import type { PersistedState } from '@transcend-io/persisted-state';\nimport type { AttributeKey } from '@transcend-io/sdk';\nimport type { GraphQLClient } from 'graphql-request';\nimport inquirer from 'inquirer';\n\nimport { CachedFileState } from './constants.js';\nimport { fuzzyMatchColumns } from './fuzzyMatchColumns.js';\n\n/**\n * Mapping from attribute name to request input parameter\n */\nexport type AttributeNameMap = {\n [k in string]: string;\n};\n\n/**\n * Create a mapping from the attributes names that can be included\n * at request submission, to the names of the columns that map to those\n * attributes.\n *\n * @param client - GraphQL client\n * @param columnNames - The set of all column names\n * @param state - Cached state of this mapping\n * @param requestAttributeKeys - Attribute keys to map\n * @returns Mapping from attributes name to column name\n */\nexport async function mapColumnsToAttributes(\n client: GraphQLClient,\n columnNames: string[],\n state: PersistedState<typeof CachedFileState>,\n requestAttributeKeys: AttributeKey[],\n): Promise<AttributeNameMap> {\n // Determine the columns that should be mapped\n const columnQuestions = requestAttributeKeys.filter(\n ({ name }) => !state.getValue('attributeNames', name),\n );\n\n // Skip mapping when everything is mapped\n const attributeNameMap =\n columnQuestions.length === 0\n ? {}\n : // prompt questions to map columns\n await inquirer.prompt<{\n [k in string]: string;\n }>(\n columnQuestions.map(({ name }) => {\n const matches = fuzzyMatchColumns(columnNames, name, false);\n return {\n name,\n message: `Choose the column that will be used to map in the attribute: ${name}`,\n type: 'list',\n default: matches[0],\n choices: matches,\n };\n }),\n );\n await Promise.all(\n Object.entries(attributeNameMap).map(([k, v]) => state.setValue(v, 'attributeNames', k)),\n );\n\n return {\n ...state.getValue('attributeNames'),\n ...attributeNameMap,\n };\n}\n","import { join } from 'node:path';\n\nimport { PersistedState } from '@transcend-io/persisted-state';\nimport {\n buildTranscendGraphQLClient,\n createSombraGotInstance,\n fetchAllRequestAttributeKeys,\n} from '@transcend-io/sdk';\nimport { map } from '@transcend-io/utils';\nimport cliProgress from 'cli-progress';\n/* eslint-disable max-lines */\nimport colors from 'colors';\nimport * as t from 'io-ts';\nimport { uniq } from 'lodash-es';\n\nimport { DEFAULT_TRANSCEND_API } from '../../constants.js';\nimport { logger } from '../../logger.js';\nimport { CachedRequestState, CachedFileState } from './constants.js';\nimport { extractClientError } from './extractClientError.js';\nimport { filterRows } from './filterRows.js';\nimport { mapColumnsToAttributes } from './mapColumnsToAttributes.js';\nimport { mapColumnsToIdentifiers } from './mapColumnsToIdentifiers.js';\nimport { mapCsvColumnsToApi } from './mapCsvColumnsToApi.js';\nimport { mapCsvRowsToRequestInputs } from './mapCsvRowsToRequestInputs.js';\nimport { mapRequestEnumValues } from './mapRequestEnumValues.js';\nimport { parseAttributesFromString } from './parseAttributesFromString.js';\nimport { readCsv } from './readCsv.js';\nimport { submitPrivacyRequest } from './submitPrivacyRequest.js';\n\n/**\n * Upload a set of privacy requests from CSV\n *\n * @param options - Options\n */\nexport async function uploadPrivacyRequestsFromCsv({\n cacheFilepath,\n requestReceiptFolder,\n file,\n auth,\n sombraAuth,\n concurrency = 100,\n defaultPhoneCountryCode = '1', // USA\n transcendUrl = DEFAULT_TRANSCEND_API,\n attributes = [],\n emailIsVerified = true,\n skipFilterStep = false,\n skipSendingReceipt = true,\n isTest = false,\n isSilent = true,\n debug = false,\n dryRun = false,\n}: {\n /** File to cache metadata about mapping of CSV shape to script */\n cacheFilepath: string;\n /** File where request receipts are stored */\n requestReceiptFolder: string;\n /** CSV file path */\n file: string;\n /** Transcend API key authentication */\n auth: string;\n /** Default country code for phone numbers */\n defaultPhoneCountryCode?: string;\n /** Concurrency to upload in */\n concurrency?: number;\n /** API URL for Transcend backend */\n transcendUrl?: string;\n /** Sombra API key authentication */\n sombraAuth?: string;\n /** Include debug logs */\n debug?: boolean;\n /** Skip the step where requests are filtered */\n skipFilterStep?: boolean;\n /** Whether test requests are being uploaded */\n isTest?: boolean;\n /** Whether requests are uploaded in silent mode */\n isSilent?: boolean;\n /** Whether to send the email receipt */\n skipSendingReceipt?: boolean;\n /** Whether the email was verified up front */\n emailIsVerified?: boolean;\n /** Attributes string pre-parse */\n attributes?: string[];\n /** Whether a dry run is happening */\n dryRun?: boolean;\n}): Promise<void> {\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 // Parse out the extra attributes to apply to all requests uploaded\n const parsedAttributes = parseAttributesFromString(attributes);\n\n // Create a new state to persist the metadata that\n // maps the request inputs to the Transcend API shape\n const state = new PersistedState(cacheFilepath, CachedFileState, {\n columnNames: {},\n requestTypeToRequestAction: {},\n subjectTypeToSubjectName: {},\n languageToLocale: {},\n statusToRequestStatus: {},\n identifierNames: {},\n attributeNames: {},\n regionToCountrySubDivision: {},\n regionToCountry: {},\n });\n\n // Create a new state file to store the requests from this run.\n // `toISOString()` contains colons (e.g. 2026-07-06T04:33:12.345Z) which are\n // illegal characters in Windows filenames, so strip them out to keep the\n // auto-generated receipt filename cross-platform.\n const requestCacheFile = join(\n requestReceiptFolder,\n `tr-request-upload-${new Date()\n .toISOString()\n .replace(/:/g, '-')}-${file.split('/').pop()}`.replace('.csv', '.json'),\n );\n const requestState = new PersistedState(requestCacheFile, CachedRequestState, {\n successfulRequests: [],\n duplicateRequests: [],\n failingRequests: [],\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 // Read in the list of integration requests\n const requestsList = readCsv(file, t.record(t.string, t.string));\n const columnNames = uniq(requestsList.map((x) => Object.keys(x)).flat());\n\n // Log out an example request\n if (requestsList.length === 0) {\n throw new Error(\n 'No Requests found in list! Ensure the first row of the CSV is a header and the rest are requests.',\n );\n }\n if (debug) {\n const firstRequest = requestsList[0];\n logger.info(colors.magenta(`First request: ${JSON.stringify(firstRequest, null, 2)}`));\n }\n // Determine what rows in the CSV should be imported\n // Choose columns that contain metadata to filter the requests\n const filteredRequestList = skipFilterStep ? requestsList : await filterRows(requestsList);\n\n // Build a GraphQL client\n const client = buildTranscendGraphQLClient(transcendUrl, auth);\n // Grab the request attributes\n const requestAttributeKeys = await fetchAllRequestAttributeKeys(client, { logger });\n // Determine the columns that should be mapped\n const columnNameMap = await mapCsvColumnsToApi(columnNames, state);\n const identifierNameMap = await mapColumnsToIdentifiers(client, columnNames, state);\n const attributeNameMap = await mapColumnsToAttributes(\n client,\n columnNames,\n state,\n requestAttributeKeys,\n );\n await mapRequestEnumValues(client, filteredRequestList, {\n state,\n columnNameMap,\n });\n\n // map the CSV to request input\n const requestInputs = mapCsvRowsToRequestInputs(filteredRequestList, state, {\n defaultPhoneCountryCode,\n columnNameMap,\n identifierNameMap,\n attributeNameMap,\n requestAttributeKeys,\n });\n\n // start the progress bar with a total value of 200 and start value of 0\n if (!debug) {\n progressBar.start(requestInputs.length, 0);\n }\n let total = 0;\n // Submit each request\n await map(\n requestInputs,\n async ([rawRow, requestInput], ind) => {\n // The identifier to log, only include personal data if debug mode is on\n const requestLogId = debug\n ? `email:${requestInput.email} | coreIdentifier:${requestInput.coreIdentifier}`\n : `row:${ind.toString()}`;\n\n if (debug) {\n logger.info(\n colors.magenta(\n `[${ind + 1}/${requestInputs.length}] Importing: ${JSON.stringify(\n requestInput,\n null,\n 2,\n )}`,\n ),\n );\n }\n\n // Skip on dry run\n if (dryRun) {\n logger.info(colors.magenta('Bailing out on dry run because dryRun is set'));\n return;\n }\n\n try {\n // Make the GraphQL request to submit the privacy request\n const requestResponse = await submitPrivacyRequest(sombra, requestInput, {\n details: `Uploaded by Transcend Cli: \"tr-request-upload\" : ${JSON.stringify(\n rawRow,\n null,\n 2,\n )}`,\n isTest,\n emailIsVerified,\n skipSendingReceipt,\n isSilent,\n additionalAttributes: parsedAttributes,\n });\n\n // Log success\n if (debug) {\n logger.info(\n colors.green(\n `[${ind + 1}/${\n requestInputs.length\n }] Successfully submitted the test data subject request: \"${requestLogId}\"`,\n ),\n );\n logger.info(\n colors.green(\n `[${ind + 1}/${requestInputs.length}] View it at: \"${requestResponse.link}\"`,\n ),\n );\n }\n\n // Cache successful upload\n const successfulRequests = requestState.getValue('successfulRequests');\n successfulRequests.push({\n id: requestResponse.id,\n link: requestResponse.link,\n rowIndex: ind,\n coreIdentifier: requestResponse.coreIdentifier,\n attemptedAt: new Date().toISOString(),\n });\n await requestState.setValue(successfulRequests, 'successfulRequests');\n } catch (err) {\n const msg = `${err.message} - ${JSON.stringify(err.response?.body, null, 2)}`;\n const clientError = extractClientError(msg);\n\n if (clientError === 'Client error: You have already made this request.') {\n if (debug) {\n logger.info(\n colors.yellow(\n `[${ind + 1}/${requestInputs.length}] Skipping request as it is a duplicate`,\n ),\n );\n }\n const duplicateRequests = requestState.getValue('duplicateRequests');\n duplicateRequests.push({\n coreIdentifier: requestInput.coreIdentifier,\n rowIndex: ind,\n attemptedAt: new Date().toISOString(),\n });\n await requestState.setValue(duplicateRequests, 'duplicateRequests');\n } else {\n const failingRequests = requestState.getValue('failingRequests');\n failingRequests.push({\n ...requestInput,\n rowIndex: ind,\n error: clientError || msg,\n attemptedAt: new Date().toISOString(),\n });\n await requestState.setValue(failingRequests, 'failingRequests');\n if (debug) {\n logger.error(colors.red(clientError || msg));\n logger.error(\n colors.red(\n `[${ind + 1}/${\n requestInputs.length\n }] Failed to submit request for: \"${requestLogId}\"`,\n ),\n );\n }\n }\n }\n\n total += 1;\n if (!debug) {\n progressBar.update(total);\n }\n },\n {\n concurrency,\n },\n );\n\n progressBar.stop();\n const t1 = new Date().getTime();\n const totalTime = t1 - t0;\n\n // Log completion time\n logger.info(colors.green(`Completed upload in \"${totalTime / 1000}\" seconds.`));\n\n // Log duplicates\n if (requestState.getValue('duplicateRequests').length > 0) {\n logger.info(\n colors.yellow(\n `Encountered \"${requestState.getValue('duplicateRequests').length}\" duplicate requests. ` +\n `See \"${requestCacheFile}\" to review the core identifiers for these requests.`,\n ),\n );\n }\n\n // Log errors\n if (requestState.getValue('failingRequests').length > 0) {\n logger.error(\n colors.red(\n `Encountered \"${requestState.getValue('failingRequests').length}\" errors. ` +\n `See \"${requestCacheFile}\" to review the error messages and inputs.`,\n ),\n );\n process.exit(1);\n }\n}\n/* eslint-enable max-lines */\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@transcend-io/cli",
|
|
3
|
-
"version": "12.1.
|
|
3
|
+
"version": "12.1.3",
|
|
4
4
|
"description": "A command line interface for programmatic operations across Transcend.",
|
|
5
5
|
"homepage": "https://github.com/transcend-io/tools/tree/main/packages/cli",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -106,11 +106,11 @@
|
|
|
106
106
|
"undici": "^6.28.1",
|
|
107
107
|
"yaml": "^2.9.0",
|
|
108
108
|
"yargs-parser": "^21.1.1",
|
|
109
|
-
"@transcend-io/airgap.js-types": "14.2.
|
|
110
|
-
"@transcend-io/internationalization": "4.1.1",
|
|
109
|
+
"@transcend-io/airgap.js-types": "14.2.45",
|
|
111
110
|
"@transcend-io/custom-function-types": "0.2.0",
|
|
112
|
-
"@transcend-io/
|
|
113
|
-
"@transcend-io/
|
|
111
|
+
"@transcend-io/internationalization": "4.1.1",
|
|
112
|
+
"@transcend-io/privacy-types": "6.2.0",
|
|
113
|
+
"@transcend-io/sdk": "2.1.13",
|
|
114
114
|
"@transcend-io/type-utils": "3.0.1",
|
|
115
115
|
"@transcend-io/utils": "0.3.0"
|
|
116
116
|
},
|
|
@@ -1 +0,0 @@
|
|
|
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 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"scaffold-planning-CaetLfv8.mjs","names":["CUSTOM_FUNCTION_SKILL_MD","CUSTOM_FUNCTION_SETUP_REFERENCE_MD","CUSTOM_FUNCTION_WRITING_REFERENCE_MD"],"sources":["../../../skills/transcend-custom-functions/references/setup.md","../../../skills/transcend-custom-functions/references/writing-custom-functions.md","../../../skills/transcend-custom-functions/SKILL.md","../src/lib/custom-functions/custom-function-skill.ts","../src/lib/custom-functions/ai-handoff.ts","../src/lib/custom-functions/scaffold-output.ts","../src/lib/custom-functions/scaffold-artifacts.ts","../src/lib/custom-functions/scaffold-config.ts","../src/lib/custom-functions/scaffold-planning.ts"],"sourcesContent":[null,null,null,"import CUSTOM_FUNCTION_SETUP_REFERENCE_MD from '../../../../../skills/transcend-custom-functions/references/setup.md';\nimport CUSTOM_FUNCTION_WRITING_REFERENCE_MD from '../../../../../skills/transcend-custom-functions/references/writing-custom-functions.md';\nimport CUSTOM_FUNCTION_SKILL_MD from '../../../../../skills/transcend-custom-functions/SKILL.md';\nimport type { AgentSkillFile } from '../scaffolding/agent-skill.js';\n\n/** Namespaced directory and frontmatter name for the installed Agent Skill. */\nexport const CUSTOM_FUNCTION_SKILL_NAME = 'transcend-custom-functions';\n\nexport {\n CUSTOM_FUNCTION_SETUP_REFERENCE_MD,\n CUSTOM_FUNCTION_SKILL_MD,\n CUSTOM_FUNCTION_WRITING_REFERENCE_MD,\n};\n\n/** Complete portable Agent Skill file set. */\nexport const CUSTOM_FUNCTION_SKILL_FILES: readonly AgentSkillFile[] = [\n { path: 'SKILL.md', contents: CUSTOM_FUNCTION_SKILL_MD },\n { path: 'references/setup.md', contents: CUSTOM_FUNCTION_SETUP_REFERENCE_MD },\n {\n path: 'references/writing-custom-functions.md',\n contents: CUSTOM_FUNCTION_WRITING_REFERENCE_MD,\n },\n];\n","import { quoteShellArgument } from '../scaffolding/project-plan-output.js';\nimport { CUSTOM_FUNCTION_SKILL_NAME } from './custom-function-skill.js';\nimport { SUPPORTED_DENO_MAJOR_VERSION } from './deno-runtime.js';\nimport { buildCustomFunctionProjectArguments, buildPlaceholderVariablesArgument } from './paths.js';\n\n/**\n * Build a compact AI handoff after project initialization.\n *\n * @param options - Generated setup context\n * @returns Copyable agent instruction\n */\nexport function buildInitAiHandoff(options: {\n /** Display path to the Custom Function project. */\n targetDirectory: string;\n /** Display path to the manifest. */\n manifestPath: string;\n /** Exact CLI version used by generated CI. */\n cliVersion: string;\n /** Whether the authoring skill was installed. */\n hasSkill: boolean;\n /** Whether a GitHub Actions workflow was generated. */\n hasGithubWorkflow: boolean;\n}): string {\n const prefix = options.hasSkill\n ? `Use the \\`${CUSTOM_FUNCTION_SKILL_NAME}\\` skill to`\n : 'Ask your coding agent to';\n const checkCommand = `transcend custom-functions check ${buildCustomFunctionProjectArguments(\n options.targetDirectory,\n options.manifestPath,\n )} --noInteractive`;\n if (options.hasGithubWorkflow) {\n return `${prefix} review the Custom Function setup in \\`${options.targetDirectory}\\`, adapt the generated GitHub Actions workflow to this repository's conventions, and run \\`${checkCommand}\\`.`;\n }\n return `${prefix} review the Custom Function setup in \\`${options.targetDirectory}\\` and add equivalent CI for this repository: install Deno ${SUPPORTED_DENO_MAJOR_VERSION} and \\`@transcend-io/cli@${options.cliVersion}\\`, then run \\`${checkCommand}\\`.`;\n}\n\n/**\n * Build a compact AI handoff after adding one function.\n *\n * @param options - Generated function context\n * @returns Copyable agent instruction\n */\nexport function buildNewFunctionAiHandoff(options: {\n /** Customer-visible function name. */\n displayName: string;\n /** Display path to the generated source. */\n sourcePath: string;\n /** Display path to the Custom Function project. */\n targetDirectory: string;\n /** Display path to the selected manifest. */\n manifestPath: string;\n /** Whether the authoring skill is expected in this project. */\n hasSkill: boolean;\n /** Manifest variables required by this function. */\n variableNames: readonly string[];\n}): string {\n const prefix = options.hasSkill\n ? `Use the \\`${CUSTOM_FUNCTION_SKILL_NAME}\\` skill to`\n : 'Ask your coding agent to';\n const variablesArgument = buildPlaceholderVariablesArgument(options.variableNames);\n const checkCommand = `transcend custom-functions check ${buildCustomFunctionProjectArguments(\n options.targetDirectory,\n options.manifestPath,\n )}${variablesArgument}`;\n const runCommand =\n `transcend custom-functions run ` +\n `${buildCustomFunctionProjectArguments(options.targetDirectory, options.manifestPath)} ` +\n `--function=${quoteShellArgument(options.displayName)}${variablesArgument}`;\n return `${prefix} implement \\`${options.displayName}\\` in \\`${options.sourcePath}\\`, replace the example fixtures with realistic cases, run \\`${runCommand}\\` to exercise it locally, and finish with \\`${checkCommand}\\`.`;\n}\n","import {\n toPublicPlannedChange,\n type PublicPlannedChange,\n} from '../scaffolding/project-plan-output.js';\nimport type { CustomFunctionProjectPlan } from './scaffold-model.js';\n\n/** Stable JSON scaffold result. */\nexport interface CustomFunctionPlanResult {\n /** JSON contract version. */\n version: number;\n /** Command that produced the plan. */\n command: 'init' | 'new';\n /** Whether the plan was applied. */\n applied: boolean;\n /** Whether this was a dry run. */\n dryRun: boolean;\n /** Absolute target directory. */\n targetDirectory: string;\n /** Absolute manifest path. */\n manifestPath: string;\n /** Planned mutations without file contents. */\n changes: PublicPlannedChange[];\n /** Non-fatal warnings. */\n warnings: string[];\n /** Suggested next commands. */\n nextSteps: string[];\n /** Compact prompt for continuing with a coding agent. */\n aiHandoff: string;\n}\n\n/**\n * Build the stable machine-readable scaffold result.\n *\n * @param plan - Project plan\n * @param options - Apply state\n * @returns JSON result\n */\nexport function buildPlanResult(\n plan: CustomFunctionProjectPlan,\n options: {\n /** Whether the plan was applied. */\n applied: boolean;\n /** Whether the invocation was a dry run. */\n dryRun: boolean;\n /** Working directory for portable paths. */\n cwd: string;\n /** Compact prompt for continuing with a coding agent. */\n aiHandoff: string;\n },\n): CustomFunctionPlanResult {\n return {\n version: plan.version,\n command: plan.command,\n applied: options.applied,\n dryRun: options.dryRun,\n targetDirectory: plan.targetDirectory,\n manifestPath: plan.manifestPath,\n changes: plan.changes.map((change) => toPublicPlannedChange(options.cwd, change)),\n warnings: plan.warnings,\n nextSteps: plan.nextSteps,\n aiHandoff: options.aiHandoff,\n };\n}\n","import { createHash } from 'node:crypto';\n\nimport { RECOMMENDED_DENO_VERSION } from './deno-runtime.js';\n\n/** Immutable actions/checkout v6 release commit. */\nconst ACTIONS_CHECKOUT_SHA = 'd23441a48e516b6c34aea4fa41551a30e30af803';\n\n/** Immutable denoland/setup-deno v2.0.5 release commit. */\nconst SETUP_DENO_SHA = '22d081ff2d3a40755e97629de92e3bcbfa7cf2ed';\n\n/**\n * Escape a repository path before embedding intentional GitHub glob syntax.\n *\n * @param path - Literal repository-relative path\n * @returns Path with GitHub glob metacharacters escaped\n */\nfunction escapeGithubPath(path: string): string {\n return path.replace(/([!*?[\\]\\\\])/gu, '\\\\$1');\n}\n\n/**\n * Check whether a generated workflow still matches its ownership digest.\n *\n * @param contents - Existing workflow\n * @returns Whether automatic replacement is safe\n */\nexport function isUnmodifiedCustomFunctionWorkflow(contents: string): boolean {\n const marker = contents.match(\n /# managed-by: @transcend-io\\/cli; content-sha256: ([a-f0-9]{64})\\s*$/u,\n );\n if (!marker || marker.index === undefined) {\n return false;\n }\n const body = contents.slice(0, marker.index).trimEnd();\n return createHash('sha256').update(body).digest('hex') === marker[1];\n}\n\n/**\n * Generate the conservative Custom Function GitHub Actions workflow.\n *\n * @param options - Workflow paths and pinned CLI version\n * @returns Workflow YAML\n */\nexport function generateGithubActionsWorkflow(options: {\n /** Exact released CLI version. */\n cliVersion: string;\n /** Target directory relative to the repository root. */\n targetDirectory: string;\n /** Manifest directory where generated source, payload, and Deno files live. */\n contentDirectory?: string;\n /** Manifest path relative to the repository root. */\n manifestPath: string;\n /** Additional manifest-referenced files that should trigger validation. */\n watchedPaths?: readonly string[];\n}): string {\n const { cliVersion, targetDirectory, manifestPath } = options;\n const contentDirectory = options.contentDirectory ?? targetDirectory;\n const pathPrefix =\n contentDirectory === '.' ? '' : `${escapeGithubPath(contentDirectory.replace(/\\/+$/u, ''))}/`;\n const watchedPaths = [\n `${pathPrefix}functions/**`,\n `${pathPrefix}test-payloads/**`,\n escapeGithubPath(manifestPath),\n `${pathPrefix}deno.json*`,\n ...(options.watchedPaths ?? []).map(escapeGithubPath),\n ].filter((path, index, paths) => paths.indexOf(path) === index);\n const pathFilters = watchedPaths.map((path) => ` - ${JSON.stringify(path)}`).join('\\n');\n const body = `# Generated by transcend custom-functions init.\nname: Transcend Custom Functions\n\non:\n pull_request:\n paths:\n${pathFilters}\n push:\n paths:\n${pathFilters}\n\npermissions:\n contents: read\n\njobs:\n check:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@${ACTIONS_CHECKOUT_SHA} # v6\n - uses: denoland/setup-deno@${SETUP_DENO_SHA} # v2.0.5\n with:\n deno-version: ${RECOMMENDED_DENO_VERSION}\n - name: Install the Transcend CLI\n run: npm install --global @transcend-io/cli@${cliVersion}\n - name: Validate Custom Functions\n env:\n CUSTOM_FUNCTION_DIRECTORY: ${JSON.stringify(targetDirectory)}\n CUSTOM_FUNCTION_MANIFEST: ${JSON.stringify(manifestPath)}\n run: >-\n transcend custom-functions check \"$CUSTOM_FUNCTION_DIRECTORY\"\n --manifest=\"$CUSTOM_FUNCTION_MANIFEST\"\n --variables=TRANSCEND_API_KEY:placeholder\n --noInteractive\n --json\n`;\n const digest = createHash('sha256').update(body.trimEnd()).digest('hex');\n return `${body.trimEnd()}\\n\\n# managed-by: @transcend-io/cli; content-sha256: ${digest}\\n`;\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 { buildCustomFunctionProjectArguments } from './paths.js';\n\n/**\n * Build target-scoped Deno authoring configuration.\n *\n * @param contents - Existing deno.json/jsonc contents\n * @param contractVersion - Exact authoring-contract version\n * @param manifestFileName - Manifest path relative to this Deno configuration\n * @param referencedPaths - Existing manifest-relative source and payload paths\n * @returns Merged configuration\n */\nexport function mergeDenoConfiguration(\n contents: string | null,\n contractVersion: string,\n manifestFileName = 'transcend-functions.yml',\n referencedPaths: {\n /** Existing source paths. */\n sources?: readonly string[];\n /** Existing payload paths. */\n payloads?: readonly string[];\n } = {},\n): string {\n const current = parseJsoncObject(contents ?? '{}\\n', 'Deno configuration');\n const compilerOptions =\n current.compilerOptions &&\n typeof current.compilerOptions === 'object' &&\n !Array.isArray(current.compilerOptions)\n ? (current.compilerOptions as Record<string, unknown>)\n : {};\n const lint =\n current.lint && typeof current.lint === 'object' && !Array.isArray(current.lint)\n ? (current.lint as Record<string, unknown>)\n : {};\n const fmt =\n current.fmt && typeof current.fmt === 'object' && !Array.isArray(current.fmt)\n ? (current.fmt as Record<string, unknown>)\n : {};\n const tasks =\n current.tasks && typeof current.tasks === 'object' && !Array.isArray(current.tasks)\n ? (current.tasks as Record<string, unknown>)\n : {};\n const taskName = 'custom-functions:check';\n const legacyTaskCommand =\n 'deno check functions/**/*.ts && deno lint functions/ && deno fmt --check functions/ test-payloads/';\n const previousTaskCommand = `transcend custom-functions check ${buildCustomFunctionProjectArguments(\n '.',\n manifestFileName,\n )} --noInteractive`;\n const taskCommand = `transcend custom-functions check ${buildCustomFunctionProjectArguments(\n '.',\n manifestFileName,\n )} --variables=TRANSCEND_API_KEY:placeholder --noInteractive`;\n if (\n tasks[taskName] !== undefined &&\n tasks[taskName] !== legacyTaskCommand &&\n tasks[taskName] !== previousTaskCommand &&\n tasks[taskName] !== taskCommand\n ) {\n throw new Error(\n `Deno task \"${taskName}\" already has a different command; apply the patch manually.`,\n );\n }\n const updates: JsoncUpdate[] = [\n {\n path: ['imports', '@transcend-io/custom-function-types'],\n value: `npm:@transcend-io/custom-function-types@${contractVersion}`,\n },\n { path: ['compilerOptions', 'strict'], value: true },\n {\n path: ['compilerOptions', 'lib'],\n value: mergeStringArray(compilerOptions.lib, ['deno.ns', 'dom', 'dom.iterable', 'esnext']),\n },\n {\n path: ['lint', 'include'],\n value: mergeStringArray(lint.include, ['functions/', ...(referencedPaths.sources ?? [])]),\n },\n {\n path: ['fmt', 'include'],\n value: mergeStringArray(fmt.include, [\n 'functions/',\n 'test-payloads/',\n manifestFileName,\n ...(referencedPaths.sources ?? []),\n ...(referencedPaths.payloads ?? []),\n ]),\n },\n { path: ['fmt', 'singleQuote'], value: true },\n { path: ['fmt', 'lineWidth'], value: 100 },\n { path: ['tasks', taskName], value: taskCommand },\n ];\n return mergeJsonc(contents, updates, 'Deno configuration');\n}\n\n/**\n * Merge VS Code-compatible Deno settings scoped to the target.\n *\n * @param contents - Existing settings JSONC\n * @param repositoryRoot - Repository root\n * @param targetDirectory - Custom Function project directory\n * @returns Merged settings\n */\nexport function mergeEditorSettings(\n contents: string | null,\n repositoryRoot: string,\n targetDirectory: string,\n): string {\n const current = parseJsoncObject(contents ?? '{}\\n', 'editor settings');\n const target = relative(repositoryRoot, targetDirectory).split(sep).join('/') || '.';\n return mergeJsonc(\n contents,\n [\n { path: ['deno.enable'], value: true },\n {\n path: ['deno.enablePaths'],\n value: mergeStringArray(current['deno.enablePaths'], [target]),\n },\n ],\n 'editor settings',\n );\n}\n\n/**\n * Merge the recommended Deno editor extension.\n *\n * @param contents - Existing extensions JSONC\n * @returns Merged extensions\n */\nexport function mergeEditorExtensions(contents: string | null): string {\n const current = parseJsoncObject(contents ?? '{}\\n', 'editor extension recommendations');\n return mergeJsonc(\n contents,\n [\n {\n path: ['recommendations'],\n value: mergeStringArray(current.recommendations, ['denoland.vscode-deno']),\n },\n ],\n 'editor extension recommendations',\n );\n}\n","import { basename, isAbsolute, join, relative, resolve, sep } from 'node:path';\n\nimport {\n getManagedAgentSkillCandidatePaths,\n planManagedAgentSkill,\n type ManagedAgentSkillDefinition,\n} from '../scaffolding/agent-skill.js';\nimport { displayProjectPath, quoteShellArgument } from '../scaffolding/project-plan-output.js';\nimport {\n getPlanningFileSnapshot,\n planFileChange,\n type PlanningPathSnapshot,\n} from '../scaffolding/project-plan.js';\nimport {\n CUSTOM_FUNCTION_SKILL_FILES,\n CUSTOM_FUNCTION_SKILL_NAME,\n} from './custom-function-skill.js';\nimport { insertCustomFunctionManifestEntry, parseCustomFunctionsManifest } from './manifest.js';\nimport { buildCustomFunctionProjectArguments, buildPlaceholderVariablesArgument } from './paths.js';\nimport {\n generateGithubActionsWorkflow,\n isUnmodifiedCustomFunctionWorkflow,\n} from './scaffold-artifacts.js';\nimport {\n mergeDenoConfiguration,\n mergeEditorExtensions,\n mergeEditorSettings,\n} from './scaffold-config.js';\nimport {\n CUSTOM_FUNCTION_RESULT_VERSION,\n CustomFunctionSetupFeature,\n type CustomFunctionProjectPlan,\n type CustomFunctionProjectState,\n type CustomFunctionSetupFeature as CustomFunctionSetupFeatureType,\n} from './scaffold-model.js';\nimport {\n generateCustomFunctionTemplate,\n type GeneratedCustomFunctionTemplate,\n} from './scaffold-templates.js';\n\n/** Empty manifest used for first initialization. */\nexport const EMPTY_CUSTOM_FUNCTION_MANIFEST = `# Custom Functions managed as code.\nfunctions: []\n`;\n\n/** Managed Custom Function Agent Skill definition. */\nconst CUSTOM_FUNCTION_MANAGED_SKILL: ManagedAgentSkillDefinition = {\n name: CUSTOM_FUNCTION_SKILL_NAME,\n displayName: 'Custom Function',\n owner: '@transcend-io/cli',\n files: CUSTOM_FUNCTION_SKILL_FILES,\n};\n\n/** In-memory input consumed by pure project planners. */\nexport interface CustomFunctionPlanningInput {\n /** Project discovery state. */\n state: CustomFunctionProjectState;\n /** Potential mutation paths keyed by absolute path. */\n snapshots: Readonly<Record<string, PlanningPathSnapshot>>;\n}\n\n/**\n * Find parameter placeholders in a generated manifest value.\n *\n * @param value - Generated manifest entry\n * @returns Unique parameter names in encounter order\n */\nexport function parameterNamesInManifestValue(value: unknown): string[] {\n const serialized = JSON.stringify(value) ?? '';\n return [\n ...new Set(Array.from(serialized.matchAll(/<<parameters\\.([^>]+)>>/gu), (match) => match[1]!)),\n ];\n}\n\n/** In-memory input required only by project initialization. */\nexport interface CustomFunctionInitPlanningInput extends CustomFunctionPlanningInput {\n /** Exact authoring contract version. */\n contractVersion: string;\n /** Exact CLI version. */\n cliVersion: string;\n}\n\n/** Options shared by init and implicit init. */\nexport interface InitPlanOptions {\n /** Selected setup features. */\n features: readonly CustomFunctionSetupFeatureType[];\n}\n\n/**\n * Determine the repository-owned base for optional setup.\n *\n * @param state - Discovery state\n * @returns Repository root or target\n */\nfunction setupRoot(state: CustomFunctionProjectState): string {\n return state.projectRoot;\n}\n\n/**\n * Enumerate all paths that could be included in a plan.\n *\n * @param state - Discovery state\n * @param options - Selected setup and optional scaffold\n * @returns Absolute candidate paths\n */\nexport function getInitPlanningCandidatePaths(\n state: CustomFunctionProjectState,\n options: {\n /** Selected setup features. */\n features: readonly CustomFunctionSetupFeatureType[];\n },\n): string[] {\n const root = setupRoot(state);\n const paths = new Set<string>([state.manifestPath]);\n const selected = new Set(options.features);\n if (selected.has(CustomFunctionSetupFeature.Deno)) {\n paths.add(state.denoConfigPath);\n }\n if (selected.has(CustomFunctionSetupFeature.Editor)) {\n paths.add(join(root, '.vscode', 'settings.json'));\n paths.add(join(root, '.vscode', 'extensions.json'));\n }\n if (selected.has(CustomFunctionSetupFeature.Skill)) {\n getManagedAgentSkillCandidatePaths(\n root,\n state.existingSkillDirectories,\n CUSTOM_FUNCTION_MANAGED_SKILL,\n ).forEach((path) => paths.add(path));\n }\n if (selected.has(CustomFunctionSetupFeature.Ci)) {\n paths.add(join(root, '.github', 'workflows', 'transcend-custom-functions.yml'));\n }\n return [...paths].sort((left, right) => left.localeCompare(right));\n}\n\n/**\n * Enumerate paths touched when adding one function to an initialized project.\n *\n * @param state - Project discovery state\n * @param generated - Generated function files\n * @returns Absolute candidate paths\n */\nexport function getAddFunctionPlanningCandidatePaths(\n state: CustomFunctionProjectState,\n generated: GeneratedCustomFunctionTemplate,\n): string[] {\n return [\n state.manifestPath,\n join(state.manifestDirectory, generated.sourceFile.path),\n ...generated.payloadFiles.map((file) => join(state.manifestDirectory, file.path)),\n ].sort((left, right) => left.localeCompare(right));\n}\n\n/**\n * Ensure a mutation stays below an approved project root.\n *\n * @param root - Approved root\n * @param path - Candidate path\n */\nfunction assertContained(root: string, path: string): void {\n const child = relative(root, path);\n if (child === '..' || child.startsWith(`..${sep}`) || isAbsolute(child)) {\n throw new Error(`Refusing to modify path outside project root: ${path}`);\n }\n}\n\n/**\n * Validate roots and case-insensitive destinations.\n *\n * @param state - Discovery state\n * @param paths - Planned destination paths\n */\nfunction validatePlanDestinations(\n state: CustomFunctionProjectState,\n paths: readonly string[],\n): void {\n const root = setupRoot(state);\n assertContained(state.targetDirectory, state.manifestPath);\n paths.forEach((path) => assertContained(root, path));\n const existing = new Map(\n state.relativePaths.map((path) => [path.toLocaleLowerCase('en-US'), path]),\n );\n paths.forEach((path) => {\n const relativePath = relative(state.manifestDirectory, path).split(sep).join('/');\n if (relativePath.startsWith('../')) {\n return;\n }\n const components = relativePath.split('/');\n for (let length = 1; length <= components.length; length += 1) {\n const candidate = components.slice(0, length).join('/');\n const collision = existing.get(candidate.toLocaleLowerCase('en-US'));\n if (collision && collision !== candidate) {\n throw new Error(\n `Case-insensitive path collision: \"${candidate}\" conflicts with \"${collision}\".`,\n );\n }\n }\n });\n}\n\n/**\n * Add or classify a regular-file mutation.\n *\n * @param plan - Plan being assembled\n * @param input - Planning input\n * @param options - Desired file\n */\nfunction addFileChange(\n plan: CustomFunctionProjectPlan,\n input: CustomFunctionPlanningInput,\n options: {\n /** Absolute path. */\n path: string;\n /** Complete desired contents. */\n contents: string;\n /** Human-readable reason. */\n description: string;\n /** Refuse existing contents. */\n createOnly?: boolean;\n },\n): void {\n const change = planFileChange({\n snapshot: getPlanningFileSnapshot(input.snapshots, options.path),\n after: options.contents,\n description: options.description,\n ...(options.createOnly === undefined ? {} : { createOnly: options.createOnly }),\n });\n if (change) {\n plan.changes.push(change);\n } else {\n plan.unchanged.push(options.path);\n }\n}\n\n/**\n * Create the base plan and optional repository setup.\n *\n * @param input - Collected project state\n * @param options - Selected setup\n * @returns Validated project plan\n */\nexport function buildInitPlan(\n input: CustomFunctionInitPlanningInput,\n options: InitPlanOptions,\n): CustomFunctionProjectPlan {\n const { state } = input;\n const projectArguments = buildCustomFunctionProjectArguments(\n state.targetDirectory,\n state.manifestPath,\n state.invocationDirectory,\n );\n const plan: CustomFunctionProjectPlan = {\n version: CUSTOM_FUNCTION_RESULT_VERSION,\n command: 'init',\n rootDirectory: setupRoot(state),\n targetDirectory: state.targetDirectory,\n manifestPath: state.manifestPath,\n changes: [],\n unchanged: [],\n warnings: [],\n nextSteps: [\n `transcend custom-functions new ${projectArguments}`,\n `transcend custom-functions check ${projectArguments}`,\n ],\n };\n const manifestSnapshot = getPlanningFileSnapshot(input.snapshots, state.manifestPath);\n const existingManifest =\n manifestSnapshot.contents === null\n ? undefined\n : parseCustomFunctionsManifest(manifestSnapshot.contents);\n if (manifestSnapshot.contents === null) {\n addFileChange(plan, input, {\n path: state.manifestPath,\n contents: EMPTY_CUSTOM_FUNCTION_MANIFEST,\n description: 'Create the Custom Function manifest',\n });\n } else {\n plan.unchanged.push(state.manifestPath);\n }\n\n const selected = new Set(options.features);\n if (selected.has(CustomFunctionSetupFeature.Deno)) {\n const snapshot = getPlanningFileSnapshot(input.snapshots, state.denoConfigPath);\n const sourcePaths = (existingManifest?.functions ?? [])\n .map(({ code }) => code)\n .filter((path) => !path.includes('<<parameters.'));\n const payloadPaths = (existingManifest?.functions ?? [])\n .flatMap((entry) => [\n ...(entry['test-payload'] ? [entry['test-payload']] : []),\n ...(entry['test-payloads'] ?? []).map(({ payload }) => payload),\n ])\n .filter((path) => !path.includes('<<parameters.'));\n addFileChange(plan, input, {\n path: state.denoConfigPath,\n contents: mergeDenoConfiguration(\n snapshot.contents,\n input.contractVersion,\n basename(state.manifestPath),\n { sources: sourcePaths, payloads: payloadPaths },\n ),\n description: 'Merge strict target-scoped Deno authoring configuration',\n });\n }\n\n const root = setupRoot(state);\n if (selected.has(CustomFunctionSetupFeature.Editor)) {\n const settingsPath = join(root, '.vscode', 'settings.json');\n const extensionsPath = join(root, '.vscode', 'extensions.json');\n const settingsSnapshot = getPlanningFileSnapshot(input.snapshots, settingsPath);\n const extensionsSnapshot = getPlanningFileSnapshot(input.snapshots, extensionsPath);\n addFileChange(plan, input, {\n path: settingsPath,\n contents: mergeEditorSettings(settingsSnapshot.contents, root, state.targetDirectory),\n description: 'Scope Deno language support to Custom Functions',\n });\n addFileChange(plan, input, {\n path: extensionsPath,\n contents: mergeEditorExtensions(extensionsSnapshot.contents),\n description: 'Recommend the Deno editor extension',\n });\n }\n\n if (selected.has(CustomFunctionSetupFeature.Skill)) {\n const skillPlan = planManagedAgentSkill({\n rootDirectory: root,\n existingDirectories: state.existingSkillDirectories,\n snapshots: input.snapshots,\n skill: CUSTOM_FUNCTION_MANAGED_SKILL,\n });\n plan.changes.push(...skillPlan.changes);\n plan.unchanged.push(...skillPlan.unchanged);\n plan.warnings.push(...skillPlan.warnings);\n }\n if (selected.has(CustomFunctionSetupFeature.Ci)) {\n if (!state.repositoryRoot || !state.usesGithub) {\n plan.warnings.push(\n 'GitHub Actions setup was selected, but this target is not inside a detected GitHub repository.',\n );\n } else {\n const workflowPath = join(\n state.repositoryRoot,\n '.github',\n 'workflows',\n 'transcend-custom-functions.yml',\n );\n const target =\n relative(state.repositoryRoot, state.targetDirectory).split(sep).join('/') || '.';\n const contentDirectory =\n relative(state.repositoryRoot, state.manifestDirectory).split(sep).join('/') || '.';\n const manifest = relative(state.repositoryRoot, state.manifestPath).split(sep).join('/');\n const watchedPaths = (existingManifest?.functions ?? [])\n .flatMap((entry) => [\n entry.code,\n ...(entry['test-payload'] ? [entry['test-payload']] : []),\n ...(entry['test-payloads'] ?? []).map(({ payload }) => payload),\n ])\n .filter((path) => !path.includes('<<parameters.'))\n .map((path) =>\n relative(state.repositoryRoot!, resolve(state.manifestDirectory, path))\n .split(sep)\n .join('/'),\n );\n const workflowContents = generateGithubActionsWorkflow({\n cliVersion: input.cliVersion,\n targetDirectory: target,\n contentDirectory,\n manifestPath: manifest,\n watchedPaths,\n });\n const workflowSnapshot = getPlanningFileSnapshot(input.snapshots, workflowPath);\n if (workflowSnapshot.contents === null) {\n addFileChange(plan, input, {\n path: workflowPath,\n contents: workflowContents,\n description: 'Add credential-free Custom Function checks',\n createOnly: true,\n });\n } else if (workflowSnapshot.contents === workflowContents) {\n plan.unchanged.push(workflowPath);\n } else if (isUnmodifiedCustomFunctionWorkflow(workflowSnapshot.contents)) {\n addFileChange(plan, input, {\n path: workflowPath,\n contents: workflowContents,\n description: 'Update managed Custom Function checks',\n });\n } else {\n plan.unchanged.push(workflowPath);\n plan.warnings.push(`Existing GitHub Actions workflow was left unchanged: ${workflowPath}`);\n }\n }\n }\n validatePlanDestinations(\n state,\n plan.changes.map(({ path }) => path),\n );\n return plan;\n}\n\n/**\n * Add a new function to an initialized project in one transaction.\n *\n * @param input - Collected project state\n * @param options - Generated function\n * @returns Validated project plan\n */\nexport function buildAddFunctionPlan(\n input: CustomFunctionPlanningInput,\n options: {\n /** Generated function scaffold. */\n generated: GeneratedCustomFunctionTemplate;\n },\n): CustomFunctionProjectPlan {\n const { state } = input;\n const currentManifest = getPlanningFileSnapshot(input.snapshots, state.manifestPath).contents;\n if (currentManifest === null) {\n throw new Error(`Custom Function manifest does not exist: ${state.manifestPath}`);\n }\n const sourceRelativePath = options.generated.sourceFile.path.split(sep).join('/');\n const sourceCollision = state.relativePaths.find(\n (path) => path.toLocaleLowerCase('en-US') === sourceRelativePath.toLocaleLowerCase('en-US'),\n );\n if (sourceCollision) {\n throw new Error(\n `Custom Function name \"${options.generated.displayName}\" maps to ${sourceRelativePath}, ` +\n `which conflicts with existing path ${sourceCollision}. Choose another name.`,\n );\n }\n const existingSourceEntry = parseCustomFunctionsManifest(currentManifest).functions.find(\n (entry) =>\n resolve(state.manifestDirectory, entry.code) ===\n resolve(state.manifestDirectory, sourceRelativePath),\n );\n if (existingSourceEntry) {\n throw new Error(\n `Custom Function name \"${options.generated.displayName}\" maps to source used by ` +\n `\"${existingSourceEntry.name}\". Choose another name.`,\n );\n }\n const plan: CustomFunctionProjectPlan = {\n version: CUSTOM_FUNCTION_RESULT_VERSION,\n command: 'new',\n rootDirectory: state.manifestDirectory,\n targetDirectory: state.targetDirectory,\n manifestPath: state.manifestPath,\n changes: [],\n unchanged: [],\n warnings: [],\n nextSteps: [],\n };\n const updatedManifest = insertCustomFunctionManifestEntry(\n currentManifest,\n options.generated.manifestEntry,\n );\n addFileChange(plan, input, {\n path: state.manifestPath,\n contents: updatedManifest,\n description: `Register ${options.generated.displayName} in the manifest`,\n });\n\n const generatedFiles = [options.generated.sourceFile, ...options.generated.payloadFiles];\n generatedFiles.forEach((file) => {\n addFileChange(plan, input, {\n path: join(state.manifestDirectory, file.path),\n contents: file.contents,\n description:\n file === options.generated.sourceFile\n ? `Create ${options.generated.displayName} source`\n : `Create ${options.generated.displayName} test payload`,\n createOnly: true,\n });\n });\n const sourcePath = join(state.manifestDirectory, options.generated.sourceFile.path);\n const projectArguments = buildCustomFunctionProjectArguments(\n state.targetDirectory,\n state.manifestPath,\n state.invocationDirectory,\n );\n const variableNames = parameterNamesInManifestValue(options.generated.manifestEntry);\n const variablesArgument = buildPlaceholderVariablesArgument(variableNames);\n plan.nextSteps = [\n `Edit ${quoteShellArgument(displayProjectPath(state.invocationDirectory, sourcePath))}`,\n `transcend custom-functions run ${projectArguments} --function=${quoteShellArgument(\n options.generated.displayName,\n )}${variablesArgument}`,\n `transcend custom-functions check ${projectArguments}${variablesArgument}`,\n `transcend custom-functions push ${projectArguments} --dryRun${variablesArgument}`,\n ];\n if (variableNames.length > 0) {\n plan.warnings.push(\n `Supply ${variableNames.join(', ')} through --variables when running or pushing; never commit secret values.`,\n );\n }\n validatePlanDestinations(\n state,\n plan.changes.map(({ path }) => path),\n );\n return plan;\n}\n\n/**\n * Generate the template before collecting its destination snapshots.\n *\n * @param displayName - Custom Function display name\n * @param template - Template name\n * @returns Pure template output\n */\nexport function prepareGeneratedCustomFunction(\n displayName: string,\n template: Parameters<typeof generateCustomFunctionTemplate>[1],\n): GeneratedCustomFunctionTemplate {\n return generateCustomFunctionTemplate(displayName, template);\n}\n"],"mappings":"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"}
|