@transcend-io/cli 8.23.1 → 8.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/README.md +69 -0
  2. package/dist/bin/bash-complete.cjs +1 -1
  3. package/dist/bin/cli.cjs +1 -1
  4. package/dist/bin/deprecated-command.cjs +2 -2
  5. package/dist/{chunk-P7WCYR3A.cjs → chunk-4DYEWT53.cjs} +2 -2
  6. package/dist/{chunk-P7WCYR3A.cjs.map → chunk-4DYEWT53.cjs.map} +1 -1
  7. package/dist/{chunk-OZ5HYZX2.cjs → chunk-4Y2OJ475.cjs} +2 -2
  8. package/dist/{chunk-OZ5HYZX2.cjs.map → chunk-4Y2OJ475.cjs.map} +1 -1
  9. package/dist/{chunk-GPBUTESM.cjs → chunk-7A6YW7SF.cjs} +2 -2
  10. package/dist/{chunk-GPBUTESM.cjs.map → chunk-7A6YW7SF.cjs.map} +1 -1
  11. package/dist/{chunk-GGCTM7EC.cjs → chunk-7IN5X2S4.cjs} +2 -2
  12. package/dist/{chunk-GGCTM7EC.cjs.map → chunk-7IN5X2S4.cjs.map} +1 -1
  13. package/dist/{chunk-Y6RWZTI4.cjs → chunk-AVZBE2R3.cjs} +2 -2
  14. package/dist/{chunk-Y6RWZTI4.cjs.map → chunk-AVZBE2R3.cjs.map} +1 -1
  15. package/dist/{chunk-HVH5FPKY.cjs → chunk-C5AAGXZE.cjs} +2 -2
  16. package/dist/{chunk-HVH5FPKY.cjs.map → chunk-C5AAGXZE.cjs.map} +1 -1
  17. package/dist/{chunk-XYI7MMZT.cjs → chunk-CFFB56DK.cjs} +2 -2
  18. package/dist/{chunk-XYI7MMZT.cjs.map → chunk-CFFB56DK.cjs.map} +1 -1
  19. package/dist/{chunk-CUUCCUX2.cjs → chunk-CYGC567Y.cjs} +172 -168
  20. package/dist/chunk-CYGC567Y.cjs.map +1 -0
  21. package/dist/{chunk-MSIMMKMG.cjs → chunk-DCLHMZWV.cjs} +4 -4
  22. package/dist/{chunk-MSIMMKMG.cjs.map → chunk-DCLHMZWV.cjs.map} +1 -1
  23. package/dist/{chunk-ZJRAMRQ6.cjs → chunk-IWZ2HIHK.cjs} +2 -2
  24. package/dist/{chunk-ZJRAMRQ6.cjs.map → chunk-IWZ2HIHK.cjs.map} +1 -1
  25. package/dist/chunk-LFAJZQET.cjs +38 -0
  26. package/dist/chunk-LFAJZQET.cjs.map +1 -0
  27. package/dist/{chunk-KRLXEFCL.cjs → chunk-OKWH7Y2Z.cjs} +3 -3
  28. package/dist/{chunk-KRLXEFCL.cjs.map → chunk-OKWH7Y2Z.cjs.map} +1 -1
  29. package/dist/{chunk-RZGR6ZFP.cjs → chunk-UJPJAASL.cjs} +25 -20
  30. package/dist/chunk-UJPJAASL.cjs.map +1 -0
  31. package/dist/{impl-PHDAL2KZ.cjs → impl-2RF3CQYJ.cjs} +2 -2
  32. package/dist/{impl-PHDAL2KZ.cjs.map → impl-2RF3CQYJ.cjs.map} +1 -1
  33. package/dist/{impl-XQMSGCCR.cjs → impl-2ZXK4G3X.cjs} +2 -2
  34. package/dist/{impl-XQMSGCCR.cjs.map → impl-2ZXK4G3X.cjs.map} +1 -1
  35. package/dist/{impl-KQDSAEUI.cjs → impl-3PVAU2SZ.cjs} +2 -2
  36. package/dist/{impl-KQDSAEUI.cjs.map → impl-3PVAU2SZ.cjs.map} +1 -1
  37. package/dist/impl-5273O2PM.cjs +2 -0
  38. package/dist/impl-5273O2PM.cjs.map +1 -0
  39. package/dist/{impl-NJGWDDJA.cjs → impl-55HR73CO.cjs} +3 -3
  40. package/dist/{impl-NJGWDDJA.cjs.map → impl-55HR73CO.cjs.map} +1 -1
  41. package/dist/{impl-7MJUM2GC.cjs → impl-5YHZWUVM.cjs} +2 -2
  42. package/dist/{impl-7MJUM2GC.cjs.map → impl-5YHZWUVM.cjs.map} +1 -1
  43. package/dist/{impl-K4253LZW.cjs → impl-7Q7N7MEL.cjs} +6 -6
  44. package/dist/{impl-K4253LZW.cjs.map → impl-7Q7N7MEL.cjs.map} +1 -1
  45. package/dist/{impl-H4KZQFFX.cjs → impl-A5WFSC3U.cjs} +2 -2
  46. package/dist/{impl-H4KZQFFX.cjs.map → impl-A5WFSC3U.cjs.map} +1 -1
  47. package/dist/{impl-MX3IWCKH.cjs → impl-BTCB55I2.cjs} +2 -2
  48. package/dist/{impl-MX3IWCKH.cjs.map → impl-BTCB55I2.cjs.map} +1 -1
  49. package/dist/{impl-EQDDZYUF.cjs → impl-CACU53DW.cjs} +2 -2
  50. package/dist/{impl-EQDDZYUF.cjs.map → impl-CACU53DW.cjs.map} +1 -1
  51. package/dist/impl-CVF67V6C.cjs +2 -0
  52. package/dist/{impl-7BGZ5BWT.cjs.map → impl-CVF67V6C.cjs.map} +1 -1
  53. package/dist/{impl-2Z6DENXJ.cjs → impl-DD246CUP.cjs} +5 -5
  54. package/dist/{impl-2Z6DENXJ.cjs.map → impl-DD246CUP.cjs.map} +1 -1
  55. package/dist/{impl-JYTX7IXT.cjs → impl-DEJ7Q25E.cjs} +2 -2
  56. package/dist/{impl-JYTX7IXT.cjs.map → impl-DEJ7Q25E.cjs.map} +1 -1
  57. package/dist/{impl-GOJPWPSQ.cjs → impl-DUFE2SD3.cjs} +2 -2
  58. package/dist/{impl-GOJPWPSQ.cjs.map → impl-DUFE2SD3.cjs.map} +1 -1
  59. package/dist/{impl-D5WM5SAH.cjs → impl-EGBXUZEX.cjs} +2 -2
  60. package/dist/{impl-D5WM5SAH.cjs.map → impl-EGBXUZEX.cjs.map} +1 -1
  61. package/dist/impl-EMPK5SP4.cjs +2 -0
  62. package/dist/impl-EMPK5SP4.cjs.map +1 -0
  63. package/dist/{impl-XNJXNKOA.cjs → impl-F7TSRMCF.cjs} +2 -2
  64. package/dist/{impl-XNJXNKOA.cjs.map → impl-F7TSRMCF.cjs.map} +1 -1
  65. package/dist/{impl-YIRKVDLY.cjs → impl-FS3K2DMH.cjs} +2 -2
  66. package/dist/{impl-YIRKVDLY.cjs.map → impl-FS3K2DMH.cjs.map} +1 -1
  67. package/dist/{impl-MDCSR2NY.cjs → impl-GWQDOEQM.cjs} +2 -2
  68. package/dist/{impl-MDCSR2NY.cjs.map → impl-GWQDOEQM.cjs.map} +1 -1
  69. package/dist/{impl-CKK5XFKE.cjs → impl-H2HELZXJ.cjs} +2 -2
  70. package/dist/{impl-CKK5XFKE.cjs.map → impl-H2HELZXJ.cjs.map} +1 -1
  71. package/dist/{impl-Y5JWK56U.cjs → impl-H7PE22UA.cjs} +2 -2
  72. package/dist/{impl-Y5JWK56U.cjs.map → impl-H7PE22UA.cjs.map} +1 -1
  73. package/dist/{impl-OQYDIABO.cjs → impl-JX55JUEK.cjs} +2 -2
  74. package/dist/{impl-OQYDIABO.cjs.map → impl-JX55JUEK.cjs.map} +1 -1
  75. package/dist/{impl-WC6TN2UA.cjs → impl-KGR5OOIL.cjs} +2 -2
  76. package/dist/{impl-WC6TN2UA.cjs.map → impl-KGR5OOIL.cjs.map} +1 -1
  77. package/dist/{impl-MDDUW5J6.cjs → impl-M353HLO7.cjs} +2 -2
  78. package/dist/{impl-MDDUW5J6.cjs.map → impl-M353HLO7.cjs.map} +1 -1
  79. package/dist/{impl-EE7GGHZL.cjs → impl-MYKXOHZB.cjs} +2 -2
  80. package/dist/{impl-EE7GGHZL.cjs.map → impl-MYKXOHZB.cjs.map} +1 -1
  81. package/dist/{impl-UBV237P2.cjs → impl-NSQG5ZE2.cjs} +4 -4
  82. package/dist/{impl-UBV237P2.cjs.map → impl-NSQG5ZE2.cjs.map} +1 -1
  83. package/dist/{impl-XN45NETY.cjs → impl-NTMCDOS7.cjs} +2 -2
  84. package/dist/{impl-XN45NETY.cjs.map → impl-NTMCDOS7.cjs.map} +1 -1
  85. package/dist/{impl-VHHSV2GV.cjs → impl-ODHDBZ66.cjs} +2 -2
  86. package/dist/{impl-VHHSV2GV.cjs.map → impl-ODHDBZ66.cjs.map} +1 -1
  87. package/dist/{impl-4HIRQNHI.cjs → impl-OGY3N3JL.cjs} +2 -2
  88. package/dist/{impl-4HIRQNHI.cjs.map → impl-OGY3N3JL.cjs.map} +1 -1
  89. package/dist/{impl-2XWBALAH.cjs → impl-PDZTOIGB.cjs} +2 -2
  90. package/dist/{impl-2XWBALAH.cjs.map → impl-PDZTOIGB.cjs.map} +1 -1
  91. package/dist/{impl-DCMLHQCC.cjs → impl-PWMPWKRC.cjs} +2 -2
  92. package/dist/{impl-DCMLHQCC.cjs.map → impl-PWMPWKRC.cjs.map} +1 -1
  93. package/dist/{impl-QDVKSMY7.cjs → impl-QPOOCXTZ.cjs} +2 -2
  94. package/dist/{impl-QDVKSMY7.cjs.map → impl-QPOOCXTZ.cjs.map} +1 -1
  95. package/dist/{impl-J7GQPDGF.cjs → impl-R2KE5BYZ.cjs} +2 -2
  96. package/dist/{impl-J7GQPDGF.cjs.map → impl-R2KE5BYZ.cjs.map} +1 -1
  97. package/dist/{impl-QZ3PC4FN.cjs → impl-SKGYIPGE.cjs} +2 -2
  98. package/dist/{impl-QZ3PC4FN.cjs.map → impl-SKGYIPGE.cjs.map} +1 -1
  99. package/dist/{impl-OJ6CYOEK.cjs → impl-TYNSXOHT.cjs} +2 -2
  100. package/dist/{impl-OJ6CYOEK.cjs.map → impl-TYNSXOHT.cjs.map} +1 -1
  101. package/dist/{impl-NM3DQGYQ.cjs → impl-U4XXZKTL.cjs} +2 -2
  102. package/dist/{impl-NM3DQGYQ.cjs.map → impl-U4XXZKTL.cjs.map} +1 -1
  103. package/dist/{impl-TZM54LOJ.cjs → impl-VE2OJ6VL.cjs} +2 -2
  104. package/dist/{impl-TZM54LOJ.cjs.map → impl-VE2OJ6VL.cjs.map} +1 -1
  105. package/dist/{impl-4NCYLWJW.cjs → impl-WV34A76M.cjs} +3 -3
  106. package/dist/{impl-4NCYLWJW.cjs.map → impl-WV34A76M.cjs.map} +1 -1
  107. package/dist/{impl-RELREVJ4.cjs → impl-XPH46QYI.cjs} +2 -2
  108. package/dist/{impl-RELREVJ4.cjs.map → impl-XPH46QYI.cjs.map} +1 -1
  109. package/dist/{impl-YGZFTCNK.cjs → impl-YE7X5MSV.cjs} +2 -2
  110. package/dist/{impl-YGZFTCNK.cjs.map → impl-YE7X5MSV.cjs.map} +1 -1
  111. package/dist/{impl-CQHMXRI4.cjs → impl-YQ6LBC3S.cjs} +2 -2
  112. package/dist/{impl-CQHMXRI4.cjs.map → impl-YQ6LBC3S.cjs.map} +1 -1
  113. package/dist/index.cjs +3 -3
  114. package/dist/index.cjs.map +1 -1
  115. package/dist/index.d.cts +77 -2
  116. package/package.json +2 -1
  117. package/dist/chunk-CUUCCUX2.cjs.map +0 -1
  118. package/dist/chunk-RZGR6ZFP.cjs.map +0 -1
  119. package/dist/impl-7BGZ5BWT.cjs +0 -2
  120. package/dist/impl-XJQYOQHI.cjs +0 -38
  121. package/dist/impl-XJQYOQHI.cjs.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/home/runner/work/cli/cli/dist/chunk-RZGR6ZFP.cjs","../src/app.ts","../src/commands/admin/generate-api-keys/command.ts","../src/commands/admin/chunk-csv/command.ts","../src/commands/admin/routes.ts","../src/commands/consent/routes.ts","../src/commands/consent/build-xdi-sync-endpoint/command.ts","../src/commands/consent/pull-consent-metrics/command.ts","../src/commands/consent/pull-consent-preferences/command.ts","../src/commands/consent/update-consent-manager/command.ts","../src/commands/consent/upload-consent-preferences/command.ts","../src/commands/consent/upload-cookies-from-csv/command.ts","../src/commands/consent/upload-data-flows-from-csv/command.ts","../src/commands/consent/upload-preferences/command.ts","../src/commands/consent/generate-access-tokens/command.ts","../src/commands/inventory/routes.ts","../src/commands/inventory/derive-data-silos-from-data-flows-cross-instance/command.ts","../src/commands/inventory/derive-data-silos-from-data-flows/command.ts","../src/commands/inventory/discover-silos/command.ts","../src/commands/inventory/pull-datapoints/command.ts","../src/commands/inventory/pull-unstructured-discovery-files/command.ts","../src/commands/inventory/push/command.ts","../src/commands/inventory/scan-packages/command.ts","../src/commands/inventory/consent-manager-service-json-to-yml/command.ts","../src/lib/docgen/buildExamples.ts","../src/commands/inventory/consent-managers-to-business-entities/command.ts","../src/commands/migration/sync-ot/command.ts","../src/commands/migration/routes.ts","../src/commands/request/cron/mark-identifiers-completed/command.ts","../src/commands/request/cron/pull-identifiers/command.ts","../src/commands/request/cron/pull-profiles/command.ts","../src/commands/request/cron/routes.ts","../src/commands/request/enricher-restart/command.ts","../src/commands/request/export/command.ts","../src/commands/request/preflight/pull-identifiers/command.ts","../src/commands/request/preflight/push-identifiers/command.ts","../src/commands/request/preflight/routes.ts","../src/commands/request/system/retry-request-data-silos/command.ts","../src/commands/request/upload/command.ts","../src/commands/request/routes.ts"],"names":["generateApiKeysCommand","buildCommand","generateApiKeys","SCOPE_TITLES","uuidParser","createTranscendUrlParameter","chunkCsvCommand","chunkCsv","v","n","adminRoutes","buildRouteMap","buildXdiSyncEndpointCommand","buildXdiSyncEndpoint","createAuthParameter","ScopeName","arrayParser","pullConsentMetricsCommand","pullConsentMetrics","dateParser","pullConsentPreferencesCommand","pullConsentPreferences","createSombraAuthParameter","numberParser","updateConsentManagerCommand","updateConsentManager","ConsentBundleType","uploadConsentPreferencesCommand","uploadConsentPreferences","createConsentUrlParameter","uploadCookiesFromCsvCommand","uploadCookiesFromCsv","ConsentTrackerStatus","uploadDataFlowsFromCsvCommand","uploadDataFlowsFromCsv","uploadPreferencesCommand","uploadPreferences","generateAccessTokensCommand","generateAccessTokens","parseDurationToMs","consentRoutes","deriveDataSilosFromDataFlowsCrossInstanceCommand","deriveDataSilosFromDataFlowsCrossInstance","deriveDataSilosFromDataFlowsCommand","deriveDataSilosFromDataFlows","discoverSilosCommand","discoverSilos","pullDatapointsCommand","pullDatapoints","DataCategoryType","pullUnstructuredDiscoveryFilesCommand","pullUnstructuredDiscoveryFiles","UnstructuredSubDataPointRecommendationStatus","pushCommand","push","scanPackagesCommand","scanPackages","buildExampleCommand","commandPath","flags","options","command","flagList","getFlagList","forceSingleLine","argsIndent","RequestAction"],"mappings":"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","file":"/home/runner/work/cli/cli/dist/chunk-RZGR6ZFP.cjs","sourcesContent":[null,"import {\n buildInstallCommand,\n buildUninstallCommand,\n} from '@stricli/auto-complete';\nimport { buildApplication, buildRouteMap } from '@stricli/core';\nimport { adminRoutes } from './commands/admin/routes';\nimport { consentRoutes } from './commands/consent/routes';\nimport { inventoryRoutes } from './commands/inventory/routes';\nimport { migrationRoutes } from './commands/migration/routes';\nimport { requestRoutes } from './commands/request/routes';\nimport { description, name, version } from './constants';\n\nconst routes = buildRouteMap({\n routes: {\n request: requestRoutes,\n consent: consentRoutes,\n inventory: inventoryRoutes,\n admin: adminRoutes,\n migration: migrationRoutes,\n install: buildInstallCommand('@transcend-io/transcend', {\n bash: '__@transcend-io/cli_bash_complete',\n }),\n uninstall: buildUninstallCommand('@transcend-io/transcend', { bash: true }),\n },\n docs: {\n brief: description,\n hideRoute: {\n install: true,\n uninstall: true,\n },\n },\n});\n\nexport const app = buildApplication(routes, {\n name,\n versionInfo: {\n currentVersion: version,\n },\n});\n","import { buildCommand } from '@stricli/core';\nimport { createTranscendUrlParameter } from '../../../lib/cli/common-parameters';\nimport { uuidParser } from '../../../lib/cli/parsers';\nimport { SCOPE_TITLES } from '../../../constants';\n\nexport const generateApiKeysCommand = buildCommand({\n loader: async () => {\n const { generateApiKeys } = await import('./impl');\n return generateApiKeys;\n },\n parameters: {\n flags: {\n email: {\n kind: 'parsed',\n parse: String,\n brief: 'The email address that you use to log into Transcend',\n },\n password: {\n kind: 'parsed',\n parse: String,\n brief: 'The password for your account login',\n },\n apiKeyTitle: {\n kind: 'parsed',\n parse: String,\n brief: 'The title of the API key being generated or destroyed',\n },\n file: {\n kind: 'parsed',\n parse: String,\n brief: 'The file where API keys should be written to',\n },\n scopes: {\n kind: 'enum',\n values: SCOPE_TITLES,\n variadic: ',',\n brief: 'The list of scopes that should be given to the API key',\n },\n deleteExistingApiKey: {\n kind: 'boolean',\n brief:\n 'When true, if an API key exists with the specified apiKeyTitle, the existing API key is deleted',\n default: true,\n },\n createNewApiKey: {\n kind: 'boolean',\n brief:\n 'When true, new API keys will be created. Set to false if you simply want to delete all API keys with a title',\n default: true,\n },\n parentOrganizationId: {\n kind: 'parsed',\n parse: uuidParser,\n brief:\n 'Filter for only a specific organization by ID, returning all child accounts associated with that organization',\n optional: true,\n },\n transcendUrl: createTranscendUrlParameter(),\n },\n },\n docs: {\n brief: 'Generate API keys',\n fullDescription: `This command allows for creating API keys across multiple Transcend instances. This is useful for customers that are managing many Transcend instances and need to regularly create, cycle or delete API keys across all of their instances.\n\nUnlike the other commands that rely on API key authentication, this command relies upon username/password authentication. This command will spit out the API keys into a JSON file, and that JSON file can be used in subsequent CLI commands.\n\nAuthentication requires your email and password for the Transcend account. This command will only generate API keys for Transcend instances where you have the permission to \"Manage API Keys\".`,\n },\n});\n","import { buildCommand } from '@stricli/core';\n\nexport const chunkCsvCommand = buildCommand({\n loader: async () => {\n const { chunkCsv } = await import('./impl');\n return chunkCsv;\n },\n parameters: {\n flags: {\n directory: {\n kind: 'parsed',\n parse: String,\n brief: 'Directory containing CSV files to split (required)',\n },\n outputDir: {\n kind: 'parsed',\n parse: String,\n brief:\n \"Directory to write chunk files (defaults to each input file's directory)\",\n optional: true,\n },\n clearOutputDir: {\n kind: 'boolean',\n brief: 'Clear the output directory before writing chunks',\n default: true,\n },\n chunkSizeMB: {\n kind: 'parsed',\n parse: (v: string) => {\n const n = Number(v);\n if (!Number.isFinite(n) || n <= 0) {\n throw new Error('chunkSizeMB must be a positive number');\n }\n return n;\n },\n brief:\n 'Approximate chunk size in megabytes. Keep well under JS string size limits',\n default: '10',\n },\n concurrency: {\n kind: 'parsed',\n parse: (v: string) => Math.max(1, Number(v) || 0),\n brief:\n 'Max number of worker processes (defaults based on CPU and file count)',\n optional: true,\n },\n viewerMode: {\n kind: 'boolean',\n brief:\n 'Run in non-interactive viewer mode (no attach UI, auto-artifacts)',\n default: false,\n },\n },\n },\n docs: {\n brief: 'Chunk all CSVs in a directory into smaller CSV files',\n fullDescription: `Streams every CSV in --directory and writes chunked files of approximately N MB each.\n- Runs files in parallel across worker processes (configurable via --concurrency).\n- Validates row-length consistency against the header row; logs periodic progress and memory usage.`,\n },\n});\n","import { buildRouteMap } from '@stricli/core';\nimport { generateApiKeysCommand } from './generate-api-keys/command';\nimport { chunkCsvCommand } from './chunk-csv/command';\n\nexport const adminRoutes = buildRouteMap({\n routes: {\n 'generate-api-keys': generateApiKeysCommand,\n 'chunk-csv': chunkCsvCommand,\n },\n docs: {\n brief: 'Admin commands',\n },\n});\n","import { buildRouteMap } from '@stricli/core';\nimport { buildXdiSyncEndpointCommand } from './build-xdi-sync-endpoint/command';\nimport { pullConsentMetricsCommand } from './pull-consent-metrics/command';\nimport { pullConsentPreferencesCommand } from './pull-consent-preferences/command';\nimport { updateConsentManagerCommand } from './update-consent-manager/command';\nimport { uploadConsentPreferencesCommand } from './upload-consent-preferences/command';\nimport { uploadCookiesFromCsvCommand } from './upload-cookies-from-csv/command';\nimport { uploadDataFlowsFromCsvCommand } from './upload-data-flows-from-csv/command';\nimport { uploadPreferencesCommand } from './upload-preferences/command';\nimport { generateAccessTokensCommand } from './generate-access-tokens/command';\n\nexport const consentRoutes = buildRouteMap({\n routes: {\n 'build-xdi-sync-endpoint': buildXdiSyncEndpointCommand,\n 'generate-access-tokens': generateAccessTokensCommand,\n 'pull-consent-metrics': pullConsentMetricsCommand,\n 'pull-consent-preferences': pullConsentPreferencesCommand,\n 'update-consent-manager': updateConsentManagerCommand,\n 'upload-consent-preferences': uploadConsentPreferencesCommand,\n 'upload-cookies-from-csv': uploadCookiesFromCsvCommand,\n 'upload-data-flows-from-csv': uploadDataFlowsFromCsvCommand,\n 'upload-preferences': uploadPreferencesCommand,\n },\n docs: {\n brief: 'Consent commands',\n },\n});\n","import { buildCommand } from '@stricli/core';\nimport { ScopeName } from '@transcend-io/privacy-types';\nimport {\n createAuthParameter,\n createTranscendUrlParameter,\n} from '../../../lib/cli/common-parameters';\nimport { arrayParser } from '../../../lib/cli/parsers';\n\nexport const buildXdiSyncEndpointCommand = buildCommand({\n loader: async () => {\n const { buildXdiSyncEndpoint } = await import('./impl');\n return buildXdiSyncEndpoint;\n },\n parameters: {\n flags: {\n auth: createAuthParameter({\n scopes: [ScopeName.ViewConsentManager],\n }),\n xdiLocation: {\n kind: 'parsed',\n parse: String,\n brief:\n 'The location of the XDI that will be loaded by the generated sync endpoint',\n },\n file: {\n kind: 'parsed',\n parse: String,\n brief: 'The HTML file path where the sync endpoint should be written',\n default: './sync-endpoint.html',\n },\n removeIpAddresses: {\n kind: 'boolean',\n brief: 'When true, remove IP addresses from the domain list',\n default: true,\n },\n domainBlockList: {\n kind: 'parsed',\n parse: arrayParser,\n brief:\n 'The set of domains that should be excluded from the sync endpoint. Comma-separated list.',\n default: 'localhost',\n },\n xdiAllowedCommands: {\n kind: 'parsed',\n parse: String,\n brief: 'The allowed set of XDI commands',\n default: 'ConsentManager:Sync',\n },\n transcendUrl: createTranscendUrlParameter(),\n },\n },\n docs: {\n brief: 'Build XDI sync endpoint',\n fullDescription:\n 'This command allows for building of the XDI Sync Endpoint across a set of Transcend accounts.',\n },\n});\n","import { buildCommand } from '@stricli/core';\nimport { ScopeName } from '@transcend-io/privacy-types';\nimport {\n createAuthParameter,\n createTranscendUrlParameter,\n} from '../../../lib/cli/common-parameters';\nimport { dateParser } from '../../../lib/cli/parsers';\n\nexport const pullConsentMetricsCommand = buildCommand({\n loader: async () => {\n const { pullConsentMetrics } = await import('./impl');\n return pullConsentMetrics;\n },\n parameters: {\n flags: {\n auth: createAuthParameter({\n scopes: [ScopeName.ViewConsentManager],\n }),\n start: {\n kind: 'parsed',\n parse: dateParser,\n brief: 'The start date to pull metrics from',\n },\n end: {\n kind: 'parsed',\n parse: dateParser,\n brief: 'The end date to pull metrics until',\n optional: true,\n },\n folder: {\n kind: 'parsed',\n parse: String,\n brief: 'The folder to save metrics to',\n default: './consent-metrics/',\n },\n bin: {\n kind: 'parsed',\n parse: String,\n brief: 'The bin metric when pulling data (1h or 1d)',\n default: '1d',\n },\n transcendUrl: createTranscendUrlParameter(),\n },\n },\n docs: {\n brief: 'Pull consent metrics',\n fullDescription: `This command allows for pulling consent manager metrics for a Transcend account, or a set of Transcend accounts.\n\nBy default, the consent metrics will be written to a folder named \\`consent-metrics\\` within the directory where you run the command. You can override the location that these CSVs are written to using the flag \\`--folder=./my-folder/\\`. This folder will contain a set of CSV files:\n\n- \\`CONSENT_CHANGES_TIMESERIES_optIn.csv\\` -> this is a feed containing the number of explicit opt in events that happen - these are calls to \\`airgap.setConsent(event, { SaleOfInfo: true });\\`\n- \\`CONSENT_CHANGES_TIMESERIES_optOut.csv\\` -> this is a feed containing the number of explicit opt out events that happen - these are calls to \\`airgap.setConsent(event, { SaleOfInfo: false });\\`\n- \\`CONSENT_SESSIONS_BY_REGIME_Default.csv\\` -> this contains the number of sessions detected for the bin period\n- \\`PRIVACY_SIGNAL_TIMESERIES_DNT.csv\\` -> the number of DNT signals detected.\n- \\`PRIVACY_SIGNAL_TIMESERIES_GPC.csv\\` -> the number of GPC signals detected.`,\n },\n});\n","import { buildCommand, numberParser } from '@stricli/core';\nimport { ScopeName } from '@transcend-io/privacy-types';\nimport {\n createAuthParameter,\n createSombraAuthParameter,\n createTranscendUrlParameter,\n} from '../../../lib/cli/common-parameters';\nimport { dateParser } from '../../../lib/cli/parsers';\n\nexport const pullConsentPreferencesCommand = buildCommand({\n loader: async () => {\n const { pullConsentPreferences } = await import('./impl');\n return pullConsentPreferences;\n },\n parameters: {\n flags: {\n auth: createAuthParameter({\n scopes: [ScopeName.ViewManagedConsentDatabaseAdminApi],\n }),\n partition: {\n kind: 'parsed',\n parse: String,\n brief: 'The partition key to download consent preferences to',\n },\n sombraAuth: createSombraAuthParameter(),\n file: {\n kind: 'parsed',\n parse: String,\n brief: 'Path to the CSV file to save preferences to',\n default: './preferences.csv',\n },\n transcendUrl: createTranscendUrlParameter(),\n timestampBefore: {\n kind: 'parsed',\n parse: dateParser,\n brief: 'Filter for consents updated this time',\n optional: true,\n },\n timestampAfter: {\n kind: 'parsed',\n parse: dateParser,\n brief: 'Filter for consents updated after this time',\n optional: true,\n },\n identifiers: {\n kind: 'parsed',\n parse: String,\n variadic: ',',\n brief: 'Filter for specific identifiers',\n optional: true,\n },\n concurrency: {\n kind: 'parsed',\n parse: numberParser,\n brief: 'The concurrency to use when downloading consents in parallel',\n default: '100',\n },\n },\n },\n docs: {\n brief: 'Pull consent preferences',\n fullDescription:\n 'This command allows for pull of consent preferences from the Managed Consent Database.',\n },\n});\n","import { buildCommand } from '@stricli/core';\nimport { ConsentBundleType, ScopeName } from '@transcend-io/privacy-types';\nimport {\n createAuthParameter,\n createTranscendUrlParameter,\n} from '../../../lib/cli/common-parameters';\n\nexport const updateConsentManagerCommand = buildCommand({\n loader: async () => {\n const { updateConsentManager } = await import('./impl');\n return updateConsentManager;\n },\n parameters: {\n flags: {\n auth: createAuthParameter({\n scopes: [ScopeName.ManageConsentManagerDeveloperSettings],\n }),\n bundleTypes: {\n kind: 'enum',\n values: Object.values(ConsentBundleType),\n brief: 'The bundle types to deploy. Defaults to PRODUCTION,TEST.',\n variadic: ',',\n },\n deploy: {\n kind: 'boolean',\n brief:\n 'When true, deploy the Consent Manager after updating the version',\n default: false,\n },\n transcendUrl: createTranscendUrlParameter(),\n },\n },\n docs: {\n brief: 'Update consent manager',\n fullDescription:\n 'This command allows for updating Consent Manager to latest version. The Consent Manager bundle can also be deployed using this command.',\n },\n});\n","import { buildCommand, numberParser } from '@stricli/core';\nimport { createConsentUrlParameter } from '../../../lib/cli/common-parameters';\n\nexport const uploadConsentPreferencesCommand = buildCommand({\n loader: async () => {\n const { uploadConsentPreferences } = await import('./impl');\n return uploadConsentPreferences;\n },\n parameters: {\n flags: {\n base64EncryptionKey: {\n kind: 'parsed',\n parse: String,\n brief: 'The encryption key used to encrypt the userId',\n },\n base64SigningKey: {\n kind: 'parsed',\n parse: String,\n brief:\n 'The signing key used to prove authentication of consent request',\n },\n partition: {\n kind: 'parsed',\n parse: String,\n brief: 'The partition key to download consent preferences to',\n },\n file: {\n kind: 'parsed',\n parse: String,\n brief: 'The file to pull consent preferences from',\n default: './preferences.csv',\n },\n consentUrl: createConsentUrlParameter(),\n concurrency: {\n kind: 'parsed',\n parse: numberParser,\n brief: 'The concurrency to use when uploading requests in parallel',\n default: '100',\n },\n },\n },\n docs: {\n brief: 'Upload consent preferences to the Managed Consent Database',\n fullDescription:\n 'This command allows for updating of consent preferences to the Managed Consent Database.',\n },\n});\n","import { buildCommand } from '@stricli/core';\nimport { ConsentTrackerStatus, ScopeName } from '@transcend-io/privacy-types';\nimport {\n createAuthParameter,\n createTranscendUrlParameter,\n} from '../../../lib/cli/common-parameters';\n\nexport const uploadCookiesFromCsvCommand = buildCommand({\n loader: async () => {\n const { uploadCookiesFromCsv } = await import('./impl');\n return uploadCookiesFromCsv;\n },\n parameters: {\n flags: {\n auth: createAuthParameter({\n scopes: [ScopeName.ManageDataFlow],\n }),\n trackerStatus: {\n kind: 'enum',\n values: Object.values(ConsentTrackerStatus),\n brief: 'The status of the cookies you will upload.',\n },\n file: {\n kind: 'parsed',\n parse: String,\n brief: 'Path to the CSV file to upload',\n default: './cookies.csv',\n },\n transcendUrl: createTranscendUrlParameter(),\n },\n },\n docs: {\n brief: 'Upload cookies from CSV',\n fullDescription: `Upload cookies from CSV. This command allows for uploading of cookies from CSV.\n\nStep 1) Download the CSV of cookies that you want to edit from the Admin Dashboard under [Consent Management -> Cookies](https://app.transcend.io/consent-manager/cookies). You can download cookies from both the \"Triage\" and \"Approved\" tabs.\n\nStep 2) You can edit the contents of the CSV file as needed. You may adjust the \"Purpose\" column, adjust the \"Notes\" column, add \"Owners\" and \"Teams\" or even add custom columns with additional metadata.\n\nStep 3) Upload the modified CSV file back into the dashboard with this command.`,\n },\n});\n","import { buildCommand } from '@stricli/core';\nimport { ConsentTrackerStatus, ScopeName } from '@transcend-io/privacy-types';\nimport {\n createAuthParameter,\n createTranscendUrlParameter,\n} from '../../../lib/cli/common-parameters';\n\nexport const uploadDataFlowsFromCsvCommand = buildCommand({\n loader: async () => {\n const { uploadDataFlowsFromCsv } = await import('./impl');\n return uploadDataFlowsFromCsv;\n },\n parameters: {\n flags: {\n auth: createAuthParameter({\n scopes: [ScopeName.ManageDataFlow],\n }),\n trackerStatus: {\n kind: 'enum',\n values: Object.values(ConsentTrackerStatus),\n brief: 'The status of the data flows you will upload.',\n },\n file: {\n kind: 'parsed',\n parse: String,\n brief: 'Path to the CSV file to upload',\n default: './data-flows.csv',\n },\n classifyService: {\n kind: 'boolean',\n brief:\n 'When true, automatically assign the service for a data flow based on the domain that is specified',\n default: false,\n },\n transcendUrl: createTranscendUrlParameter(),\n },\n },\n docs: {\n brief: 'Upload data flows from CSV',\n fullDescription: `Upload data flows from CSV. This command allows for uploading of data flows from CSV.\n\nStep 1) Download the CSV of data flows that you want to edit from the Admin Dashboard under [Consent Management -> Data Flows](https://app.transcend.io/consent-manager/data-flows). You can download data flows from both the \"Triage\" and \"Approved\" tabs.\n\nStep 2) You can edit the contents of the CSV file as needed. You may adjust the \"Purpose\" column, adjust the \"Notes\" column, add \"Owners\" and \"Teams\" or even add custom columns with additional metadata.\n\nStep 3) Upload the modified CSV file back into the dashboard with this command.`,\n },\n});\n","import { buildCommand, numberParser } from '@stricli/core';\nimport { ScopeName } from '@transcend-io/privacy-types';\nimport {\n createAuthParameter,\n createSombraAuthParameter,\n createTranscendUrlParameter,\n} from '../../../lib/cli/common-parameters';\n\nexport const uploadPreferencesCommand = buildCommand({\n loader: async () => {\n const { uploadPreferences } = await import('./impl');\n return uploadPreferences;\n },\n parameters: {\n flags: {\n auth: createAuthParameter({\n scopes: [\n ScopeName.ManageStoredPreferences,\n ScopeName.ViewManagedConsentDatabaseAdminApi,\n ScopeName.ViewPreferenceStoreSettings,\n ],\n }),\n partition: {\n kind: 'parsed',\n parse: String,\n brief: 'The partition key to download consent preferences to',\n },\n sombraAuth: createSombraAuthParameter(),\n transcendUrl: createTranscendUrlParameter(),\n file: {\n kind: 'parsed',\n parse: String,\n brief: 'Path to the CSV file to load preferences from',\n optional: true,\n },\n directory: {\n kind: 'parsed',\n parse: String,\n brief: 'Path to the directory of CSV files to load preferences from',\n optional: true,\n },\n dryRun: {\n kind: 'boolean',\n brief:\n 'Whether to do a dry run only - will write results to receiptFilepath without updating Transcend',\n default: false,\n },\n skipExistingRecordCheck: {\n kind: 'boolean',\n brief:\n 'Whether to skip the check for existing records. SHOULD ONLY BE USED FOR INITIAL UPLOAD',\n default: false,\n },\n receiptFileDir: {\n kind: 'parsed',\n parse: String,\n brief: 'Directory path where the response receipts should be saved',\n default: './receipts',\n },\n skipWorkflowTriggers: {\n kind: 'boolean',\n brief:\n 'Whether to skip workflow triggers when uploading to preference store',\n default: false,\n },\n forceTriggerWorkflows: {\n kind: 'boolean',\n brief:\n 'Whether to force trigger workflows for existing consent records',\n default: false,\n },\n skipConflictUpdates: {\n kind: 'boolean',\n brief:\n 'Whether to skip uploading of any records where the preference store and file have a hard conflict',\n default: false,\n },\n isSilent: {\n kind: 'boolean',\n brief: 'Whether to skip sending emails in workflows',\n default: true,\n },\n attributes: {\n kind: 'parsed',\n parse: String,\n brief:\n 'Attributes to add to any DSR request if created. Comma-separated list of key:value pairs.',\n default: 'Tags:transcend-cli,Source:transcend-cli',\n },\n receiptFilepath: {\n kind: 'parsed',\n parse: String,\n brief: 'Store resulting, continuing where left off',\n default: './preference-management-upload-receipts.json',\n },\n concurrency: {\n kind: 'parsed',\n parse: numberParser,\n brief: 'The concurrency to use when uploading in parallel',\n default: '10',\n },\n },\n },\n docs: {\n brief: 'Upload preference management data to your Preference Store',\n fullDescription: `Upload preference management data to your Preference Store.\n\nThis command prompts you to map the shape of the CSV to the shape of the Transcend API. There is no requirement for the shape of the incoming CSV, as the script will handle the mapping process.\n\nThe script will also produce a JSON cache file that allows for the mappings to be preserved between runs.`,\n },\n});\n","import { buildCommand } from '@stricli/core';\nimport { ScopeName } from '@transcend-io/privacy-types';\nimport {\n createAuthParameter,\n createTranscendUrlParameter,\n} from '../../../lib/cli/common-parameters';\nimport { parseDurationToMs } from '../../../lib/cli/parsers';\n\nexport const generateAccessTokensCommand = buildCommand({\n loader: async () => {\n const { generateAccessTokens } = await import('./impl');\n return generateAccessTokens;\n },\n parameters: {\n flags: {\n auth: createAuthParameter({\n scopes: [ScopeName.GeneratePreferenceAccessTokens],\n }),\n file: {\n kind: 'parsed',\n parse: String,\n brief:\n 'Path to the CSV file containing user identifiers to generate access tokens for',\n },\n subjectType: {\n kind: 'parsed',\n parse: String,\n brief:\n 'Slug for the data subject that the user will be logged in as on the Privacy Center. e.g. \"customer\" or \"employee\"',\n },\n emailColumnName: {\n kind: 'parsed',\n parse: String,\n brief:\n 'Name of the column in the CSV that contains user email addresses',\n default: 'email',\n },\n coreIdentifierColumnName: {\n kind: 'parsed',\n parse: String,\n optional: true,\n brief:\n 'Name of the column in the CSV that contains user core identifiers',\n },\n duration: {\n kind: 'parsed',\n parse: parseDurationToMs,\n brief:\n 'How long the access tokens should be valid. Accepts human-friendly values ' +\n 'like \"2 days\", \"10h\", \"90 minutes\". A bare number is interpreted as seconds ' +\n '(e.g., \"300\" = 5 minutes). Powered by the `ms` library: ' +\n 'https://github.com/vercel/ms',\n // Default of \"1y\" → parsed to 31_557_600_000 ms\n default: '1y',\n },\n transcendUrl: createTranscendUrlParameter(),\n },\n },\n docs: {\n brief: 'Generate access tokens',\n fullDescription:\n 'This command allows for the generation of access tokens for users specified in a CSV file.',\n },\n});\n","import { buildRouteMap } from '@stricli/core';\nimport { deriveDataSilosFromDataFlowsCrossInstanceCommand } from './derive-data-silos-from-data-flows-cross-instance/command';\nimport { deriveDataSilosFromDataFlowsCommand } from './derive-data-silos-from-data-flows/command';\nimport { discoverSilosCommand } from './discover-silos/command';\nimport { pullDatapointsCommand } from './pull-datapoints/command';\nimport { pullUnstructuredDiscoveryFilesCommand } from './pull-unstructured-discovery-files/command';\nimport { pullCommand } from './pull/command';\nimport { pushCommand } from './push/command';\nimport { scanPackagesCommand } from './scan-packages/command';\nimport { consentManagerServiceJsonToYmlCommand } from './consent-manager-service-json-to-yml/command';\nimport { consentManagersToBusinessEntitiesCommand } from './consent-managers-to-business-entities/command';\n\nexport const inventoryRoutes = buildRouteMap({\n routes: {\n pull: pullCommand,\n push: pushCommand,\n 'scan-packages': scanPackagesCommand,\n 'discover-silos': discoverSilosCommand,\n 'pull-datapoints': pullDatapointsCommand,\n 'pull-unstructured-discovery-files': pullUnstructuredDiscoveryFilesCommand,\n 'derive-data-silos-from-data-flows': deriveDataSilosFromDataFlowsCommand,\n 'derive-data-silos-from-data-flows-cross-instance':\n deriveDataSilosFromDataFlowsCrossInstanceCommand,\n 'consent-manager-service-json-to-yml':\n consentManagerServiceJsonToYmlCommand,\n 'consent-managers-to-business-entities':\n consentManagersToBusinessEntitiesCommand,\n },\n docs: {\n brief: 'Inventory commands',\n },\n});\n","import { buildCommand } from '@stricli/core';\nimport {\n createAuthParameter,\n createTranscendUrlParameter,\n} from '../../../lib/cli/common-parameters';\n\nexport const deriveDataSilosFromDataFlowsCrossInstanceCommand = buildCommand({\n loader: async () => {\n const { deriveDataSilosFromDataFlowsCrossInstance } = await import(\n './impl'\n );\n return deriveDataSilosFromDataFlowsCrossInstance;\n },\n parameters: {\n flags: {\n auth: createAuthParameter({\n scopes: [],\n }),\n dataFlowsYmlFolder: {\n kind: 'parsed',\n parse: String,\n brief: 'The folder that contains data flow yml files',\n },\n output: {\n kind: 'parsed',\n parse: String,\n brief:\n 'The output transcend.yml file containing the data silo configurations',\n default: './transcend.yml',\n },\n ignoreYmls: {\n kind: 'parsed',\n parse: String,\n variadic: ',',\n brief: 'The set of yml files that should be skipped when uploading',\n optional: true,\n },\n transcendUrl: createTranscendUrlParameter(),\n },\n },\n docs: {\n brief: 'Derive data silos from data flows cross instance',\n fullDescription:\n 'Given a folder of data flow transcend.yml configurations, convert those configurations to a single transcend.yml configurations of all related data silos.',\n },\n});\n","import { buildCommand } from '@stricli/core';\nimport {\n createAuthParameter,\n createTranscendUrlParameter,\n} from '../../../lib/cli/common-parameters';\n\nexport const deriveDataSilosFromDataFlowsCommand = buildCommand({\n loader: async () => {\n const { deriveDataSilosFromDataFlows } = await import('./impl');\n return deriveDataSilosFromDataFlows;\n },\n parameters: {\n flags: {\n auth: createAuthParameter({\n scopes: [],\n }),\n dataFlowsYmlFolder: {\n kind: 'parsed',\n parse: String,\n brief: 'The folder that contains data flow yml files',\n },\n dataSilosYmlFolder: {\n kind: 'parsed',\n parse: String,\n brief: 'The folder that contains data silo yml files',\n },\n ignoreYmls: {\n kind: 'parsed',\n parse: String,\n variadic: ',',\n brief: 'The set of yml files that should be skipped when uploading',\n optional: true,\n },\n transcendUrl: createTranscendUrlParameter(),\n },\n },\n docs: {\n brief: 'Derive data silos from data flows',\n fullDescription:\n 'Given a folder of data flow transcend.yml configurations, convert those configurations to set of data silo transcend.yml configurations.',\n },\n});\n","import { buildCommand } from '@stricli/core';\nimport { ScopeName } from '@transcend-io/privacy-types';\nimport {\n createAuthParameter,\n createTranscendUrlParameter,\n} from '../../../lib/cli/common-parameters';\nimport { uuidParser } from '../../../lib/cli/parsers';\n\nexport const discoverSilosCommand = buildCommand({\n loader: async () => {\n const { discoverSilos } = await import('./impl');\n return discoverSilos;\n },\n parameters: {\n flags: {\n scanPath: {\n kind: 'parsed',\n parse: String,\n brief: 'File path in the project to scan',\n },\n dataSiloId: {\n kind: 'parsed',\n parse: uuidParser,\n brief: 'The UUID of the corresponding data silo',\n },\n auth: createAuthParameter({\n scopes: [ScopeName.ManageAssignedDataInventory],\n requiresSiloScope: true,\n }),\n fileGlobs: {\n kind: 'parsed',\n parse: String,\n brief:\n 'You can pass a glob syntax pattern(s) to specify additional file paths to scan. Comma-separated list of globs.',\n default: '',\n },\n ignoreDirs: {\n kind: 'parsed',\n parse: String,\n brief: 'Comma-separated list of directories to ignore.',\n default: '',\n },\n transcendUrl: createTranscendUrlParameter(),\n },\n },\n docs: {\n brief: 'Scan dependency management files to discover new data silos.',\n fullDescription: `We support scanning for new data silos in JavaScript, Python, Gradle, and CocoaPods projects.\n\nTo get started, add a data silo for the corresponding project type with the \"silo discovery\" plugin enabled. For example, if you want to scan a JavaScript project, add a package.json data silo. Then, specify the data silo ID in the \"--dataSiloId\" parameter.`,\n },\n});\n","import { buildCommand } from '@stricli/core';\nimport { DataCategoryType, ScopeName } from '@transcend-io/privacy-types';\nimport {\n createAuthParameter,\n createTranscendUrlParameter,\n} from '../../../lib/cli/common-parameters';\n\nexport const pullDatapointsCommand = buildCommand({\n loader: async () => {\n const { pullDatapoints } = await import('./impl');\n return pullDatapoints;\n },\n parameters: {\n flags: {\n auth: createAuthParameter({\n scopes: [ScopeName.ViewDataInventory],\n }),\n file: {\n kind: 'parsed',\n parse: String,\n brief: 'The file to save datapoints to',\n default: './datapoints.csv',\n },\n transcendUrl: createTranscendUrlParameter(),\n dataSiloIds: {\n kind: 'parsed',\n parse: String,\n variadic: ',',\n brief: 'List of data silo IDs to filter by',\n optional: true,\n },\n includeAttributes: {\n kind: 'boolean',\n brief: 'Whether to include attributes in the output',\n default: false,\n },\n includeGuessedCategories: {\n kind: 'boolean',\n brief: 'Whether to include guessed categories in the output',\n default: false,\n },\n parentCategories: {\n kind: 'enum',\n values: Object.values(DataCategoryType),\n brief: 'List of parent categories to filter by',\n variadic: ',',\n optional: true,\n },\n subCategories: {\n kind: 'parsed',\n parse: String,\n brief: 'List of subcategories to filter by',\n variadic: ',',\n optional: true,\n },\n },\n },\n docs: {\n brief: 'Export the datapoints from your Data Inventory into a CSV.',\n },\n});\n","import { buildCommand } from '@stricli/core';\nimport {\n ScopeName,\n UnstructuredSubDataPointRecommendationStatus,\n} from '@transcend-io/privacy-types';\nimport {\n createAuthParameter,\n createTranscendUrlParameter,\n} from '../../../lib/cli/common-parameters';\n\nexport const pullUnstructuredDiscoveryFilesCommand = buildCommand({\n loader: async () => {\n const { pullUnstructuredDiscoveryFiles } = await import('./impl');\n return pullUnstructuredDiscoveryFiles;\n },\n parameters: {\n flags: {\n auth: createAuthParameter({\n scopes: [ScopeName.ViewDataInventory],\n }),\n file: {\n kind: 'parsed',\n parse: String,\n brief: 'The file to save datapoints to',\n default: './unstructured-discovery-files.csv',\n },\n transcendUrl: createTranscendUrlParameter(),\n dataSiloIds: {\n kind: 'parsed',\n parse: String,\n brief: 'List of data silo IDs to filter by',\n variadic: ',',\n optional: true,\n },\n subCategories: {\n kind: 'parsed',\n parse: String,\n brief: 'List of data categories to filter by',\n variadic: ',',\n optional: true,\n },\n status: {\n kind: 'enum',\n values: Object.values(UnstructuredSubDataPointRecommendationStatus),\n brief: 'List of classification statuses to filter by',\n variadic: ',',\n optional: true,\n },\n includeEncryptedSnippets: {\n kind: 'boolean',\n brief:\n 'Whether to include encrypted snippets of the entries classified',\n default: false,\n },\n },\n },\n docs: {\n brief: 'Pull unstructured discovery files',\n fullDescription:\n 'This command allows for pulling Unstructured Discovery into a CSV.',\n },\n});\n","import { buildCommand, numberParser } from '@stricli/core';\nimport {\n createAuthParameter,\n createTranscendUrlParameter,\n} from '../../../lib/cli/common-parameters';\n\nexport const pushCommand = buildCommand({\n loader: async () => {\n const { push } = await import('./impl');\n return push;\n },\n parameters: {\n flags: {\n auth: createAuthParameter({\n scopes: 'Varies',\n }),\n file: {\n kind: 'parsed',\n parse: String,\n brief: 'Path to the YAML file to push from',\n default: './transcend.yml',\n },\n transcendUrl: createTranscendUrlParameter(),\n pageSize: {\n kind: 'parsed',\n parse: numberParser,\n brief: 'The page size to use when paginating over the API',\n default: '50',\n },\n variables: {\n kind: 'parsed',\n parse: String,\n brief:\n 'The variables to template into the YAML file when pushing configuration. Comma-separated list of key:value pairs.',\n default: '',\n },\n publishToPrivacyCenter: {\n kind: 'boolean',\n brief: 'When true, publish the configuration to the Privacy Center',\n default: false,\n },\n classifyService: {\n kind: 'boolean',\n brief:\n 'When true, automatically assign the service for a data flow based on the domain that is specified',\n default: false,\n },\n deleteExtraAttributeValues: {\n kind: 'boolean',\n brief:\n 'When true and syncing attributes, delete any extra attributes instead of just upserting',\n default: false,\n },\n },\n },\n docs: {\n brief: 'Push metadata from transcend.yml to Transcend',\n fullDescription:\n 'Given a transcend.yml file, sync the contents up to your Transcend instance.',\n },\n});\n","import { buildCommand } from '@stricli/core';\nimport { ScopeName } from '@transcend-io/privacy-types';\nimport {\n createAuthParameter,\n createTranscendUrlParameter,\n} from '../../../lib/cli/common-parameters';\n\nexport const scanPackagesCommand = buildCommand({\n loader: async () => {\n const { scanPackages } = await import('./impl');\n return scanPackages;\n },\n parameters: {\n flags: {\n auth: createAuthParameter({\n scopes: [ScopeName.ManageCodeScanning],\n }),\n scanPath: {\n kind: 'parsed',\n parse: String,\n brief: 'File path in the project to scan',\n default: './',\n },\n ignoreDirs: {\n kind: 'parsed',\n parse: String,\n variadic: ',',\n brief: 'List of directories to ignore in scan',\n optional: true,\n },\n repositoryName: {\n kind: 'parsed',\n parse: String,\n brief: 'Name of the git repository that the package should be tied to',\n optional: true,\n },\n transcendUrl: createTranscendUrlParameter(),\n },\n },\n docs: {\n brief: 'Scan dependency management files to inventory code dependencies.',\n fullDescription: `Transcend scans packages and dependencies for the following frameworks:\n\n- package.json\n- requirements.txt & setup.py\n- Podfile\n- Package.resolved\n- build.gradle\n- pubspec.yaml\n- Gemfile & .gemspec\n- composer.json\n\nThis command will scan the folder you point at to look for any of these files. Once found, the build file will be parsed in search of dependencies. Those code packages and dependencies will be uploaded to Transcend. The information uploaded to Transcend is:\n\n- repository name\n- package names\n- dependency names and versions\n- package descriptions`,\n },\n});\n","import { buildCommand } from '@stricli/core';\nimport { buildExampleCommand } from '../../../lib/docgen/buildExamples';\nimport { ConsentManagerServiceJsonToYmlCommandFlags } from './impl';\nimport { PushCommandFlags } from '../push/impl';\n\nexport const consentManagerServiceJsonToYmlCommand = buildCommand({\n loader: async () => {\n const { consentManagerServiceJsonToYml } = await import('./impl');\n return consentManagerServiceJsonToYml;\n },\n parameters: {\n flags: {\n file: {\n kind: 'parsed',\n parse: String,\n brief:\n 'Path to the services.json file, output of await airgap.getMetadata()',\n default: './services.json',\n },\n output: {\n kind: 'parsed',\n parse: String,\n brief: 'Path to the output transcend.yml to write to',\n default: './transcend.yml',\n },\n },\n },\n docs: {\n brief: 'Convert consent manager services to transcend.yml',\n fullDescription: `Import the services from an airgap.js file into a Transcend instance.\n\n1. Run \\`await airgap.getMetadata()\\` on a site with airgap\n2. Right click on the printed object, and click \\`Copy object\\`\n3. Place output of file in a file named \\`services.json\\`\n4. Run:\n\n ${buildExampleCommand<ConsentManagerServiceJsonToYmlCommandFlags>(\n ['inventory', 'consent-manager-service-json-to-yml'],\n {\n file: './services.json',\n output: './transcend.yml',\n },\n { argsIndent: 5 },\n )}\n\n5. Run:\n\n ${buildExampleCommand<PushCommandFlags>(\n ['inventory', 'push'],\n {\n auth: '$TRANSCEND_API_KEY',\n file: './transcend.yml',\n classifyService: true,\n },\n { argsIndent: 5 },\n )}`,\n },\n});\n","import { name } from '../../constants';\n\nexport interface Example<Flags> {\n /** A description of the example */\n description: string;\n /** The flag arguments to the command */\n flags: Partial<Flags>;\n}\n\n/**\n * Builds a string of examples for the CLI\n *\n * @param commandPath - The path to the command to run, omitting the `transcend` command name, e.g., `['consent', 'upload-preferences']`\n * @param examples - The examples to build\n * @returns A string of examples for the CLI\n */\nexport function buildExamples<Flags = never>(\n commandPath: string[],\n examples: NoInfer<Example<Flags>>[],\n): string {\n return examples\n .map((example) => {\n const exampleCommand = buildExampleCommand<Flags>(\n commandPath,\n example.flags,\n );\n return `**${example.description}**\\n\\n\\`\\`\\`sh\\n${exampleCommand}\\n\\`\\`\\``;\n })\n .join('\\n\\n');\n}\n\n/**\n * Builds a command string for an example\n *\n * @param commandPath - The path to the command to run, omitting the `transcend` command name, e.g., `['consent', 'upload-preferences']`\n * @param flags - The flags to build the command with\n * @param options - The options for the command\n * @returns A command string for the example\n */\nexport function buildExampleCommand<Flags = never>(\n commandPath: string[],\n flags: NoInfer<Partial<Flags>>,\n options?: {\n /** If true, the command will be forced to a single line */\n forceSingleLine?: boolean;\n /** If true, the command will be indented */\n argsIndent?: number;\n },\n): string {\n const command = commandPath.join(' ');\n const flagList = getFlagList(flags);\n const { forceSingleLine = false, argsIndent = 2 } = options ?? {};\n\n if (flagList.length === 0) {\n return `${name} ${command}`;\n }\n\n // Break the command into multiple lines if it's too long\n const exampleCommand =\n `${name} ${command} ${flagList.join(' ')}`.length <= 117 && !forceSingleLine\n ? `${command} ${flagList.join(' ')}`\n : `${command} \\\\\\n${' '.repeat(argsIndent)}${flagList.join(\n ` \\\\\\n${' '.repeat(argsIndent)}`,\n )}`;\n\n // Add `transcend` before command name\n return `${name} ${exampleCommand}`;\n}\n\n/**\n * Formats a flag value to the bash string for an example command\n *\n * @param value - The value to format\n * @param depth - The depth of the recursion\n * @returns The formatted value\n */\nfunction formatFlagValue(value: unknown, depth = 0): string {\n if (typeof value === 'boolean') {\n return value ? 'true' : 'false';\n }\n\n if (typeof value === 'number') {\n return value.toString();\n }\n\n if (value instanceof Date) {\n return value.toISOString();\n }\n\n if (Array.isArray(value) && depth === 0) {\n const values = value.map((v) => formatFlagValue(v, depth + 1));\n if (values.every((x) => x.startsWith('$') || x.includes(' '))) {\n return `\"${values.join(',')}\"`;\n }\n return values.join(',');\n }\n\n if (typeof value === 'string') {\n // If we're operating on list elements\n if (depth === 1) {\n if (value.startsWith('$')) {\n return `$\\{${value.slice(1)}}`;\n }\n return value;\n }\n\n // Escape strings that start with $ or contain spaces or special characters\n return value.startsWith('$') || value.includes(' ') ? `\"${value}\"` : value;\n }\n\n throw new Error(`Unsupported value type: ${typeof value}`);\n}\n\n/**\n * Builds a list of flags formatted for an example command\n *\n * @param flags - The flags to build the command with\n * @param depth - The depth of the recursion\n * @returns A list of flags for the example command\n */\nexport function getFlagList<Flags = never>(\n flags: Partial<Flags>,\n depth = 0,\n): string[] {\n return Object.entries(flags).map(([flag, value]) => {\n if (typeof value === 'boolean' && value) {\n // For true booleans, just pass the flag alone\n return `--${flag}`;\n }\n\n const formattedValue = formatFlagValue(value, depth);\n\n return `--${flag}=${formattedValue}`;\n });\n}\n","import { buildCommand } from '@stricli/core';\n\nexport const consentManagersToBusinessEntitiesCommand = buildCommand({\n loader: async () => {\n const { consentManagersToBusinessEntities } = await import('./impl');\n return consentManagersToBusinessEntities;\n },\n parameters: {\n flags: {\n consentManagerYmlFolder: {\n kind: 'parsed',\n parse: String,\n brief:\n 'Path to the folder of Consent Manager transcend.yml files to combine',\n },\n output: {\n kind: 'parsed',\n parse: String,\n brief:\n 'Path to the output transcend.yml with business entity configuration',\n default: './combined-business-entities.yml',\n },\n },\n },\n docs: {\n brief: 'Convert consent managers to business entities',\n fullDescription:\n 'This command allows for converting a folder or Consent Manager transcend.yml files into a single transcend.yml file where each consent manager configuration is a Business Entity in the data inventory.',\n },\n});\n","import { buildCommand, type TypedFlagParameter } from '@stricli/core';\nimport { ScopeName } from '@transcend-io/privacy-types';\nimport {\n createAuthParameter,\n createTranscendUrlParameter,\n} from '../../../lib/cli/common-parameters';\nimport { OneTrustPullResource, OneTrustPullSource } from '../../../enums';\nimport type { LocalContext } from '../../../context';\n\nexport const syncOtCommand = buildCommand({\n loader: async () => {\n const { syncOt } = await import('./impl');\n return syncOt;\n },\n parameters: {\n flags: {\n hostname: {\n kind: 'parsed',\n parse: String,\n brief:\n 'The domain of the OneTrust environment from which to pull the resource',\n optional: true,\n },\n oneTrustAuth: {\n kind: 'parsed',\n parse: String,\n brief:\n 'The OAuth access token with the scopes necessary to access the OneTrust Public APIs',\n optional: true,\n },\n source: {\n kind: 'enum',\n values: Object.values(OneTrustPullSource) as OneTrustPullSource[],\n brief: 'Whether to read the assessments from OneTrust or from a file',\n default: OneTrustPullSource.OneTrust,\n },\n transcendAuth: {\n ...createAuthParameter({\n scopes: [ScopeName.ManageAssessments],\n }),\n optional: true,\n } as TypedFlagParameter<string | undefined, LocalContext>,\n transcendUrl: createTranscendUrlParameter(),\n file: {\n kind: 'parsed',\n parse: String,\n brief:\n 'Path to the file to pull the resource into. Must be a json file!',\n optional: true,\n },\n resource: {\n kind: 'enum',\n values: Object.values(OneTrustPullResource) as OneTrustPullResource[],\n brief:\n 'The resource to pull from OneTrust. For now, only assessments is supported',\n default: OneTrustPullResource.Assessments,\n },\n dryRun: {\n kind: 'boolean',\n brief:\n 'Whether to export the resource to a file rather than sync to Transcend',\n default: false,\n },\n debug: {\n kind: 'boolean',\n brief: 'Whether to print detailed logs in case of error',\n default: false,\n },\n },\n },\n docs: {\n brief: 'Sync OneTrust data',\n fullDescription: `Pulls resources from a OneTrust and syncs them to a Transcend instance. For now, it only supports retrieving OneTrust Assessments.\n\nThis command can be helpful if you are looking to:\n- Pull resources from your OneTrust account.\n- Migrate your resources from your OneTrust account to Transcend.\n\nOneTrust authentication requires an OAuth Token with scope for accessing the assessment endpoints.\nIf syncing the resources to Transcend, you will also need to generate an API key on the Transcend Admin Dashboard.`,\n },\n});\n","import { buildRouteMap } from '@stricli/core';\nimport { syncOtCommand } from './sync-ot/command';\n\nexport const migrationRoutes = buildRouteMap({\n routes: {\n 'sync-ot': syncOtCommand,\n },\n docs: {\n brief: 'Migration commands',\n },\n});\n","import { buildCommand } from '@stricli/core';\nimport {\n createAuthParameter,\n createSombraAuthParameter,\n createTranscendUrlParameter,\n} from '../../../../lib/cli/common-parameters';\nimport { uuidParser } from '../../../../lib/cli/parsers';\nimport type { PullIdentifiersCommandFlags } from '../pull-identifiers/impl';\nimport { buildExampleCommand } from '../../../../lib/docgen/buildExamples';\nimport type { MarkIdentifiersCompletedCommandFlags } from './impl';\nimport { RequestAction } from '@transcend-io/privacy-types';\n\nexport const markIdentifiersCompletedCommand = buildCommand({\n loader: async () => {\n const { markIdentifiersCompleted } = await import('./impl');\n return markIdentifiersCompleted;\n },\n parameters: {\n flags: {\n auth: createAuthParameter({\n scopes: [],\n requiresSiloScope: true,\n }),\n dataSiloId: {\n kind: 'parsed',\n parse: uuidParser,\n brief: 'The ID of the data silo to pull in',\n },\n file: {\n kind: 'parsed',\n parse: String,\n brief: 'Path to the CSV file where identifiers will be written to',\n default: './cron-identifiers.csv',\n },\n transcendUrl: createTranscendUrlParameter(),\n sombraAuth: createSombraAuthParameter(),\n },\n },\n docs: {\n brief: 'Mark identifiers as completed after processing.',\n fullDescription: `This command takes the output of \"${buildExampleCommand<PullIdentifiersCommandFlags>(\n ['request', 'cron', 'pull-identifiers'],\n {},\n )}\" and notifies Transcend that all of the requests in the CSV have been processed.\nThis is used in the workflow like:\n\n1. Pull identifiers to CSV:\n\n ${buildExampleCommand<PullIdentifiersCommandFlags>(\n ['request', 'cron', 'pull-identifiers'],\n {\n auth: '$TRANSCEND_API_KEY',\n dataSiloId: '70810f2e-cf90-43f6-9776-901a5950599f',\n actions: [RequestAction.Erasure],\n file: './outstanding-requests.csv',\n },\n { argsIndent: 5 },\n )}\n\n2. Run your process to operate on that CSV of requests.\n\n3. Notify Transcend of completion\n\n ${buildExampleCommand<MarkIdentifiersCompletedCommandFlags>(\n ['request', 'cron', 'mark-identifiers-completed'],\n {\n auth: '$TRANSCEND_API_KEY',\n dataSiloId: '70810f2e-cf90-43f6-9776-901a5950599f',\n file: './outstanding-requests.csv',\n },\n { argsIndent: 5 },\n )}\n\nRead more at https://docs.transcend.io/docs/integrations/cron-job-integration.`,\n },\n});\n","import { buildCommand, numberParser } from '@stricli/core';\nimport {\n createAuthParameter,\n createSombraAuthParameter,\n createTranscendUrlParameter,\n} from '../../../../lib/cli/common-parameters';\nimport { uuidParser } from '../../../../lib/cli/parsers';\nimport { RequestAction } from '@transcend-io/privacy-types';\n\nexport const pullIdentifiersCommand = buildCommand({\n loader: async () => {\n const { pullIdentifiers } = await import('./impl');\n return pullIdentifiers;\n },\n parameters: {\n flags: {\n auth: createAuthParameter({\n scopes: [],\n requiresSiloScope: true,\n }),\n dataSiloId: {\n kind: 'parsed',\n parse: uuidParser,\n brief: 'The ID of the data silo to pull in',\n },\n actions: {\n kind: 'enum',\n values: Object.values(RequestAction),\n variadic: ',',\n brief: 'The request actions to restart',\n },\n file: {\n kind: 'parsed',\n parse: String,\n brief: 'Path to the CSV file where identifiers will be written to',\n default: './cron-identifiers.csv',\n },\n transcendUrl: createTranscendUrlParameter(),\n sombraAuth: createSombraAuthParameter(),\n pageLimit: {\n kind: 'parsed',\n parse: numberParser,\n brief: 'The page limit to use when pulling in pages of identifiers',\n default: '100',\n },\n skipRequestCount: {\n kind: 'boolean',\n brief:\n 'Whether to skip the count of all outstanding requests. This is required to render the progress bar, but can take a long time to run if you have a large number of outstanding requests to process. In that case, we recommend setting skipRequestCount=true so that you can still proceed with fetching the identifiers',\n default: false,\n },\n chunkSize: {\n kind: 'parsed',\n parse: numberParser,\n brief:\n 'Maximum number of rows per CSV file. For large datasets, the output will be automatically split into multiple files to avoid file system size limits. Each file will contain at most this many rows',\n default: '10000',\n },\n },\n },\n docs: {\n brief: 'Pull identifiers of outstanding requests for a data silo to a CSV.',\n fullDescription: `If you are using the cron job integration, you can run this command to pull the outstanding identifiers for the data silo to a CSV.\n\nFor large datasets, the output will be automatically split into multiple CSV files to avoid file system size limits. Use the --chunkSize parameter to control the maximum number of rows per file.\n\nRead more at https://docs.transcend.io/docs/integrations/cron-job-integration.`,\n },\n});\n","import { buildCommand, numberParser } from '@stricli/core';\nimport {\n createAuthParameter,\n createSombraAuthParameter,\n createTranscendUrlParameter,\n} from '../../../../lib/cli/common-parameters';\nimport { uuidParser } from '../../../../lib/cli/parsers';\nimport { RequestAction } from '@transcend-io/privacy-types';\n\nexport const pullProfilesCommand = buildCommand({\n loader: async () => {\n const { pullProfiles } = await import('./impl');\n return pullProfiles;\n },\n parameters: {\n flags: {\n auth: createAuthParameter({\n scopes: [],\n requiresSiloScope: true,\n }),\n cronDataSiloId: {\n kind: 'parsed',\n parse: uuidParser,\n brief: 'The ID of the cron data silo to pull in',\n },\n targetDataSiloId: {\n kind: 'parsed',\n parse: uuidParser,\n brief: 'The ID of the target data silo to pull in',\n },\n actions: {\n kind: 'enum',\n values: Object.values(RequestAction),\n variadic: ',',\n brief: 'The request actions to restart',\n },\n file: {\n kind: 'parsed',\n parse: String,\n brief: 'Path to the CSV file where identifiers will be written to',\n default: './cron-identifiers.csv',\n },\n fileTarget: {\n kind: 'parsed',\n parse: String,\n brief: 'Path to the CSV file where identifiers will be written to',\n default: './cron-identifiers-target.csv',\n },\n transcendUrl: createTranscendUrlParameter(),\n sombraAuth: createSombraAuthParameter(),\n pageLimit: {\n kind: 'parsed',\n parse: numberParser,\n brief: 'The page limit to use when pulling in pages of identifiers',\n default: '100',\n },\n skipRequestCount: {\n kind: 'boolean',\n brief:\n 'Whether to skip the count of all outstanding requests. This is required to render the progress bar, but can take a long time to run if you have a large number of outstanding requests to process. In that case, we recommend setting skipRequestCount=true so that you can still proceed with fetching the identifiers',\n default: false,\n },\n chunkSize: {\n kind: 'parsed',\n parse: numberParser,\n brief:\n 'Maximum number of rows per CSV file. For large datasets, the output will be automatically split into multiple files to avoid file system size limits. Each file will contain at most this many rows',\n default: '10000',\n },\n },\n },\n docs: {\n brief: 'Pull profiles of outstanding requests for a data silo to a CSV.',\n fullDescription: `If you are using the cron job integration, you can run this command to pull the outstanding profiles for the data silo to a CSV.\n\nFor large datasets, the output will be automatically split into multiple CSV files to avoid file system size limits. Use the --chunkSize parameter to control the maximum number of rows per file.\n\nRead more at https://docs.transcend.io/docs/integrations/cron-job-integration.`,\n },\n});\n","import { buildRouteMap } from '@stricli/core';\nimport { markIdentifiersCompletedCommand } from './mark-identifiers-completed/command';\nimport { pullIdentifiersCommand } from './pull-identifiers/command';\nimport { pullProfilesCommand } from './pull-profiles/command';\n\nexport const cronRoutes = buildRouteMap({\n routes: {\n 'pull-identifiers': pullIdentifiersCommand,\n 'pull-profiles': pullProfilesCommand,\n 'mark-identifiers-completed': markIdentifiersCompletedCommand,\n },\n docs: {\n brief: 'Cron commands',\n hideRoute: {\n 'pull-profiles': true,\n },\n },\n});\n","import { buildCommand, numberParser } from '@stricli/core';\nimport {\n RequestAction,\n RequestEnricherStatus,\n ScopeName,\n} from '@transcend-io/privacy-types';\nimport {\n createAuthParameter,\n createTranscendUrlParameter,\n} from '../../../lib/cli/common-parameters';\nimport { dateParser } from '../../../lib/cli/parsers';\n\nexport const enricherRestartCommand = buildCommand({\n loader: async () => {\n const { enricherRestart } = await import('./impl');\n return enricherRestart;\n },\n parameters: {\n flags: {\n auth: createAuthParameter({\n scopes: [ScopeName.ManageRequestCompilation],\n }),\n enricherId: {\n kind: 'parsed',\n parse: String,\n brief: 'The ID of the enricher to restart',\n },\n actions: {\n kind: 'enum',\n values: Object.values(RequestAction),\n variadic: ',',\n brief: 'The request action to restart',\n optional: true,\n },\n requestEnricherStatuses: {\n kind: 'enum',\n values: Object.values(RequestEnricherStatus),\n variadic: ',',\n brief: 'The request enricher statuses to restart',\n optional: true,\n },\n transcendUrl: createTranscendUrlParameter(),\n concurrency: {\n kind: 'parsed',\n parse: numberParser,\n brief: 'The concurrency to use when uploading requests in parallel',\n default: '15',\n },\n requestIds: {\n kind: 'parsed',\n parse: String,\n variadic: ',',\n brief: 'Specify the specific request IDs to restart',\n optional: true,\n },\n createdAtBefore: {\n kind: 'parsed',\n parse: dateParser,\n brief: 'Restart requests that were submitted before this time',\n optional: true,\n },\n createdAtAfter: {\n kind: 'parsed',\n parse: dateParser,\n brief: 'Restart requests that were submitted after this time',\n optional: true,\n },\n },\n },\n docs: {\n brief: 'Bulk restart a particular enricher across a series of DSRs',\n fullDescription: `Bulk restart a particular enricher across a series of DSRs.\n\nThe API key needs the following scopes:\n- Manage Request Compilation`,\n },\n});\n","import { buildCommand, numberParser } from '@stricli/core';\nimport {\n RequestAction,\n RequestStatus,\n ScopeName,\n} from '@transcend-io/privacy-types';\nimport {\n createAuthParameter,\n createSombraAuthParameter,\n createTranscendUrlParameter,\n} from '../../../lib/cli/common-parameters';\nimport { dateParser } from '../../../lib/cli/parsers';\n\nexport const exportCommand = buildCommand({\n loader: async () => {\n const { _export } = await import('./impl');\n return _export;\n },\n parameters: {\n flags: {\n auth: createAuthParameter({\n scopes: [ScopeName.ViewRequests, ScopeName.ViewRequestCompilation],\n }),\n sombraAuth: createSombraAuthParameter(),\n actions: {\n kind: 'enum',\n values: Object.values(RequestAction),\n variadic: ',',\n brief: 'The request actions to export',\n optional: true,\n },\n statuses: {\n kind: 'enum',\n values: Object.values(RequestStatus),\n variadic: ',',\n brief: 'The request statuses to export',\n optional: true,\n },\n transcendUrl: createTranscendUrlParameter(),\n file: {\n kind: 'parsed',\n parse: String,\n brief: 'Path to the CSV file where identifiers will be written to',\n default: './transcend-request-export.csv',\n },\n concurrency: {\n kind: 'parsed',\n parse: numberParser,\n brief: 'The concurrency to use when uploading requests in parallel',\n default: '50',\n },\n skipRequestIdentifiers: {\n kind: 'boolean',\n brief: 'Skip exporting request identifiers',\n optional: true,\n },\n createdAtBefore: {\n kind: 'parsed',\n parse: dateParser,\n brief: 'Pull requests that were submitted before this time',\n optional: true,\n },\n createdAtAfter: {\n kind: 'parsed',\n parse: dateParser,\n brief: 'Pull requests that were submitted after this time',\n optional: true,\n },\n showTests: {\n kind: 'boolean',\n brief:\n 'Filter for test requests or production requests - when not provided, pulls both',\n optional: true,\n },\n pageLimit: {\n kind: 'parsed',\n parse: numberParser,\n brief: 'The page limit to use when pulling in pages of requests',\n default: '100',\n },\n },\n },\n docs: {\n brief: 'Export privacy requests and request identifiers to a CSV file',\n fullDescription:\n 'Export privacy requests and request identifiers to a CSV file.',\n },\n});\n","import { buildCommand, numberParser } from '@stricli/core';\nimport { RequestAction, ScopeName } from '@transcend-io/privacy-types';\nimport {\n createAuthParameter,\n createSombraAuthParameter,\n createTranscendUrlParameter,\n} from '../../../../lib/cli/common-parameters';\nimport { buildExampleCommand } from '../../../../lib/docgen/buildExamples';\nimport type { PullIdentifiersCommandFlags } from './impl';\nimport type { PushIdentifiersCommandFlags } from '../push-identifiers/impl';\n\nexport const pullIdentifiersCommand = buildCommand({\n loader: async () => {\n const { pullIdentifiers } = await import('./impl');\n return pullIdentifiers;\n },\n parameters: {\n flags: {\n auth: createAuthParameter({\n scopes: [ScopeName.ViewRequests, ScopeName.ViewRequestCompilation],\n }),\n sombraAuth: createSombraAuthParameter(),\n transcendUrl: createTranscendUrlParameter(),\n file: {\n kind: 'parsed',\n parse: String,\n brief: 'Path to the CSV file where requests will be written to',\n default: './manual-enrichment-identifiers.csv',\n },\n actions: {\n kind: 'enum',\n values: Object.values(RequestAction),\n variadic: ',',\n brief: 'The request actions to pull for',\n optional: true,\n },\n concurrency: {\n kind: 'parsed',\n parse: numberParser,\n brief: 'The concurrency to use when uploading requests in parallel',\n default: '100',\n },\n },\n },\n docs: {\n brief: 'Pull identifiers for manual enrichment',\n fullDescription: `This command pulls down the set of privacy requests that are currently pending manual enrichment.\n\nThis is useful for the following workflow:\n\n1. Pull identifiers to CSV:\n\n ${buildExampleCommand<PullIdentifiersCommandFlags>(\n ['request', 'preflight', 'pull-identifiers'],\n {\n file: './enrichment-requests.csv',\n },\n { argsIndent: 5 },\n )}\n\n2. Fill out the CSV with additional identifiers\n\n3. Push updated back to Transcend:\n\n ${buildExampleCommand<PushIdentifiersCommandFlags>(\n ['request', 'preflight', 'push-identifiers'],\n {\n file: './enrichment-requests.csv',\n },\n { argsIndent: 5 },\n )}`,\n },\n});\n","import { buildCommand, numberParser } from '@stricli/core';\nimport { ScopeName } from '@transcend-io/privacy-types';\nimport {\n createAuthParameter,\n createSombraAuthParameter,\n createTranscendUrlParameter,\n} from '../../../../lib/cli/common-parameters';\nimport { uuidParser } from '../../../../lib/cli/parsers';\nimport { buildExampleCommand } from '../../../../lib/docgen/buildExamples';\nimport type { PullIdentifiersCommandFlags } from '../pull-identifiers/impl';\nimport type { PushIdentifiersCommandFlags } from './impl';\n\nexport const pushIdentifiersCommand = buildCommand({\n loader: async () => {\n const { pushIdentifiers } = await import('./impl');\n return pushIdentifiers;\n },\n parameters: {\n flags: {\n auth: createAuthParameter({\n scopes: [\n ScopeName.ManageRequestIdentities,\n ScopeName.ManageRequestCompilation,\n ],\n }),\n enricherId: {\n kind: 'parsed',\n parse: uuidParser,\n brief: 'The ID of the Request Enricher to upload to',\n },\n sombraAuth: createSombraAuthParameter(),\n transcendUrl: createTranscendUrlParameter(),\n file: {\n kind: 'parsed',\n parse: String,\n brief: 'Path to the CSV file where requests will be written to',\n default: './manual-enrichment-identifiers.csv',\n },\n markSilent: {\n kind: 'boolean',\n brief: 'When true, set requests into silent mode before enriching',\n default: false,\n },\n concurrency: {\n kind: 'parsed',\n parse: numberParser,\n brief: 'The concurrency to use when uploading requests in parallel',\n default: '100',\n },\n },\n },\n docs: {\n brief: 'Push identifiers for manual enrichment',\n fullDescription: `This command push up a set of identifiers for a set of requests pending manual enrichment.\n\nThis is useful for the following workflow:\n\n1. Pull identifiers to CSV:\n\n ${buildExampleCommand<PullIdentifiersCommandFlags>(\n ['request', 'preflight', 'pull-identifiers'],\n {\n file: './enrichment-requests.csv',\n },\n { argsIndent: 5 },\n )}\n\n2. Fill out the CSV with additional identifiers\n\n3. Push updated back to Transcend:\n\n ${buildExampleCommand<PushIdentifiersCommandFlags>(\n ['request', 'preflight', 'push-identifiers'],\n {\n file: './enrichment-requests.csv',\n },\n { argsIndent: 5 },\n )}`,\n },\n});\n","import { buildRouteMap } from '@stricli/core';\nimport { pullIdentifiersCommand } from './pull-identifiers/command';\nimport { pushIdentifiersCommand } from './push-identifiers/command';\n\nexport const preflightRoutes = buildRouteMap({\n routes: {\n 'pull-identifiers': pullIdentifiersCommand,\n 'push-identifiers': pushIdentifiersCommand,\n },\n docs: {\n brief: 'Preflight commands',\n },\n});\n","import { buildCommand } from '@stricli/core';\nimport { RequestAction, ScopeName } from '@transcend-io/privacy-types';\nimport {\n createAuthParameter,\n createTranscendUrlParameter,\n} from '../../../../lib/cli/common-parameters';\nimport { uuidParser } from '../../../../lib/cli/parsers';\n\nexport const retryRequestDataSilosCommand = buildCommand({\n loader: async () => {\n const { retryRequestDataSilos } = await import('./impl');\n return retryRequestDataSilos;\n },\n parameters: {\n flags: {\n auth: createAuthParameter({\n scopes: [ScopeName.ManageRequestCompilation],\n }),\n dataSiloId: {\n kind: 'parsed',\n parse: uuidParser,\n brief: 'The ID of the data silo to pull in',\n },\n actions: {\n kind: 'enum',\n values: Object.values(RequestAction),\n variadic: ',',\n brief: 'The request actions to restart',\n },\n transcendUrl: createTranscendUrlParameter(),\n },\n },\n docs: {\n brief: 'Retry request data silos',\n fullDescription:\n 'This command allows for bulk restarting a set of data silos jobs for open privacy requests. This is equivalent to clicking the \"Wipe and Retry\" button for a particular data silo across a set of privacy requests.',\n },\n});\n","import { buildCommand, numberParser } from '@stricli/core';\nimport { ScopeName } from '@transcend-io/privacy-types';\nimport {\n createAuthParameter,\n createSombraAuthParameter,\n createTranscendUrlParameter,\n} from '../../../lib/cli/common-parameters';\n\nexport const uploadCommand = buildCommand({\n loader: async () => {\n const { upload } = await import('./impl');\n return upload;\n },\n parameters: {\n flags: {\n auth: createAuthParameter({\n scopes: [\n ScopeName.MakeDataSubjectRequest,\n ScopeName.ViewRequestIdentitySettings,\n ScopeName.ViewGlobalAttributes,\n ],\n }),\n file: {\n kind: 'parsed',\n parse: String,\n brief: 'Path to the CSV file of requests to upload',\n default: './requests.csv',\n },\n transcendUrl: createTranscendUrlParameter(),\n cacheFilepath: {\n kind: 'parsed',\n parse: String,\n brief:\n 'The path to the JSON file encoding the metadata used to map the CSV shape to Transcend API',\n default: './transcend-privacy-requests-cache.json',\n },\n requestReceiptFolder: {\n kind: 'parsed',\n parse: String,\n brief:\n 'The path to the folder where receipts of each upload are stored',\n default: './privacy-request-upload-receipts',\n },\n sombraAuth: createSombraAuthParameter(),\n concurrency: {\n kind: 'parsed',\n parse: numberParser,\n brief: 'The concurrency to use when uploading requests in parallel',\n default: '50',\n },\n attributes: {\n kind: 'parsed',\n parse: String,\n brief:\n 'Tag all of the requests with the following attributes. Format: key1:value1;value2,key2:value3;value4',\n default: 'Tags:transcend-cli',\n },\n isTest: {\n kind: 'boolean',\n brief:\n 'Flag whether the requests being uploaded are test requests or regular requests',\n default: false,\n },\n isSilent: {\n kind: 'boolean',\n brief:\n 'Flag whether the requests being uploaded should be submitted in silent mode',\n default: true,\n },\n skipSendingReceipt: {\n kind: 'boolean',\n brief: 'Flag whether to skip sending of the receipt email',\n default: false,\n },\n emailIsVerified: {\n kind: 'boolean',\n brief:\n 'Indicate whether the email address being uploaded is pre-verified. Set to false to send a verification email',\n default: true,\n },\n skipFilterStep: {\n kind: 'boolean',\n brief: 'When true, skip the interactive step to filter down the CSV',\n default: false,\n },\n dryRun: {\n kind: 'boolean',\n brief:\n 'When true, perform a dry run of the upload instead of calling the API to submit the requests',\n default: false,\n },\n debug: {\n kind: 'boolean',\n brief: 'Debug logging',\n default: false,\n },\n defaultPhoneCountryCode: {\n kind: 'parsed',\n parse: String,\n brief:\n 'When uploading phone numbers, if the phone number is missing a country code, assume this country code',\n default: '1',\n },\n },\n },\n docs: {\n brief: 'Upload a set of requests from a CSV',\n fullDescription: `Upload a set of requests from a CSV.\n\nThis command prompts you to map the shape of the CSV to the shape of the Transcend API. There is no requirement for the shape of the incoming CSV, as the script will handle the mapping process.\n\nThe script will also produce a JSON cache file that allows for the mappings to be preserved between runs.`,\n },\n});\n","import { buildRouteMap } from '@stricli/core';\nimport { approveCommand } from './approve/command';\nimport { cancelCommand } from './cancel/command';\nimport { cronRoutes } from './cron/routes';\nimport { downloadFilesCommand } from './download-files/command';\nimport { enricherRestartCommand } from './enricher-restart/command';\nimport { exportCommand } from './export/command';\nimport { markSilentCommand } from './mark-silent/command';\nimport { notifyAdditionalTimeCommand } from './notify-additional-time/command';\nimport { preflightRoutes } from './preflight/routes';\nimport { rejectUnverifiedIdentifiersCommand } from './reject-unverified-identifiers/command';\nimport { restartCommand } from './restart/command';\nimport { skipPreflightJobsCommand } from './skip-preflight-jobs/command';\nimport { systemRoutes } from './system/routes';\nimport { uploadCommand } from './upload/command';\n\nexport const requestRoutes = buildRouteMap({\n routes: {\n approve: approveCommand,\n upload: uploadCommand,\n 'download-files': downloadFilesCommand,\n cancel: cancelCommand,\n restart: restartCommand,\n 'notify-additional-time': notifyAdditionalTimeCommand,\n 'mark-silent': markSilentCommand,\n 'enricher-restart': enricherRestartCommand,\n 'reject-unverified-identifiers': rejectUnverifiedIdentifiersCommand,\n export: exportCommand,\n 'skip-preflight-jobs': skipPreflightJobsCommand,\n system: systemRoutes,\n preflight: preflightRoutes,\n cron: cronRoutes,\n },\n docs: {\n brief: 'All commands related to DSR requests',\n },\n});\n"]}
@@ -1,2 +0,0 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true});var _chunkKRLXEFCLcjs = require('./chunk-KRLXEFCL.cjs');require('./chunk-OZ5HYZX2.cjs');require('./chunk-5UBGZNDC.cjs');var _chunkWKCTKYN4cjs = require('./chunk-WKCTKYN4.cjs');require('./chunk-CUUCCUX2.cjs');require('./chunk-LCDYXJN6.cjs');require('./chunk-ZUNVPK23.cjs');require('./chunk-P7WCYR3A.cjs');require('./chunk-Q7I37FJV.cjs');async function m({auth:r,trackerStatus:s,file:a,transcendUrl:e}){_chunkWKCTKYN4cjs.a.call(void 0, this.process.exit),await _chunkKRLXEFCLcjs.c.call(void 0, {auth:r,trackerStatus:s,file:a,transcendUrl:e})}exports.uploadCookiesFromCsv = m;
2
- //# sourceMappingURL=impl-7BGZ5BWT.cjs.map
@@ -1,38 +0,0 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var _chunkWKCTKYN4cjs = require('./chunk-WKCTKYN4.cjs');var _chunkCUUCCUX2cjs = require('./chunk-CUUCCUX2.cjs');require('./chunk-LCDYXJN6.cjs');var _chunkZUNVPK23cjs = require('./chunk-ZUNVPK23.cjs');var _chunkP7WCYR3Acjs = require('./chunk-P7WCYR3A.cjs');require('./chunk-Q7I37FJV.cjs');var _colors = require('colors'); var _colors2 = _interopRequireDefault(_colors);var _path = require('path');var _fs = require('fs');function lt(e,t){e||(_chunkZUNVPK23cjs.a.error(_colors2.default.red("A --directory must be provided.")),t.process.exit(1));let r=[];try{r=_fs.readdirSync.call(void 0, e).filter(n=>n.endsWith(".csv")).map(n=>_path.join.call(void 0, e,n)).filter(n=>{try{return _fs.statSync.call(void 0, n).isFile()}catch (e2){return!1}})}catch(o){_chunkZUNVPK23cjs.a.error(_colors2.default.red(`Failed to read directory: ${e}`)),_chunkZUNVPK23cjs.a.error(_colors2.default.red(o.message)),t.process.exit(1)}return r.length===0&&(_chunkZUNVPK23cjs.a.error(_colors2.default.red(`No CSV files found in directory: ${e}`)),t.process.exit(1)),r}var _os = require('os');function ct(e,t){let r=Math.max(1,_nullishCoalesce(_optionalChain([_os.availableParallelism, 'optionalCall', _2 => _2()]), () => (1)));return{poolSize:typeof e=="number"&&e>0?Math.min(e,t):Math.min(r,t),cpuCount:r}}var _child_process = require('child_process');function ut(e){return`'${String(e).replace(/'/g,"'\\''")}'`}function pt(e,t,r=!1){if(r)return;let o=_os.platform.call(void 0, );try{if(o==="darwin"){let c=e.map(ut).join(" -f "),i=`
2
- tell application "Terminal"
3
- activate
4
- do script "printf '\\e]0;${t}\\a'; tail -n +1 -f ${c}"
5
- end tell
6
- `;_child_process.spawn.call(void 0, "osascript",["-e",i],{stdio:"ignore",detached:!0});return}if(o==="win32"){let c=`@(${e.map(a=>`'${a.replace(/'/g,"''")}'`).join(",")})`,i=["powershell","-NoExit","-Command",`Write-Host '${t}'; $paths = ${c}; Get-Content -Path $paths -Tail 200 -Wait`];_child_process.spawn.call(void 0, "cmd.exe",["/c","start",...i],{stdio:"ignore",detached:!0}).unref();return}let n=e.map(ut).join(" -f ");try{_child_process.spawn.call(void 0, "gnome-terminal",["--","bash","-lc",`printf '\\e]0;${t}\\a'; tail -n +1 -f ${n}`],{stdio:"ignore",detached:!0}).unref()}catch (e3){_child_process.spawn.call(void 0, "xterm",["-title",t,"-e",`tail -n +1 -f ${e.join(" ")}`],{stdio:"ignore",detached:!0}).unref()}}catch(n){throw _chunkZUNVPK23cjs.a.error(_colors2.default.red(`Failed to open terminal window for tailing logs: ${n instanceof Error?n.message:String(n)}`)),n}}function dt(e){if(!_fs.existsSync.call(void 0, e)){let t=_fs.openSync.call(void 0, e,"a");_fs.closeSync.call(void 0, t)}}function ee(e,t){let r=[/worker-\d+\.log$/,/worker-\d+\.out\.log$/,/worker-\d+\.err\.log$/,/worker-\d+\.warn\.log$/,/worker-\d+\.info\.log$/];for(let o of _fs.readdirSync.call(void 0, e)){if(!r.some(c=>c.test(o)))continue;let n=_path.join.call(void 0, e,o);try{t==="delete"&&_fs.existsSync.call(void 0, n)?_fs.unlinkSync.call(void 0, n):_fs.writeFileSync.call(void 0, n,"")}catch (e4){}}process.stdout.write(_colors2.default.dim(`Logs have been ${t==="delete"?"deleted":"truncated"} in ${e}
7
- `))}function U(e){let t=e.replace(/\x1B\[[0-9;]*m/g,""),r=/\[w\d+\]\s+(ERROR|WARN)\b/i.exec(t);if(r)return r[1].toLowerCase();if(/^\s*(ERROR|ERR|FATAL)\b/i.test(t))return"error";if(/^\s*(WARN|WARNING)\b/.test(t)||/^\s*\(node:\d+\)\s*Warning:/i.test(t)||/^\s*DeprecationWarning:/i.test(t))return"warn";try{let c=_optionalChain([JSON, 'access', _3 => _3.parse, 'call', _4 => _4(t), 'optionalAccess', _5 => _5.level]);if(typeof c=="number"){if(c>=50)return"error";if(c>=40)return"warn"}else if(typeof c=="string"){let i=c.toLowerCase();if(i==="error"||i==="fatal")return"error";if(i==="warn"||i==="warning")return"warn"}}catch (e5){}let o=/\[w\d+\].*\b(WARN|WARNING|ERROR|FATAL)\b/i.exec(t);if(o){let n=o[1].toUpperCase();return n==="ERROR"||n==="FATAL"?"error":"warn"}return null}function N(e){let t="";return r=>{t+=r.toString("utf8");let o;for(;(o=t.indexOf(`
8
- `))!==-1;){let n=t.slice(0,o);e(n),t=t.slice(o+1)}}}function mt(e){let t=_path.join.call(void 0, e,"logs");_fs.mkdirSync.call(void 0, t,{recursive:!0});let r=_nullishCoalesce(process.env.RESET_LOGS, () => ("truncate"));return ee(t,r),t}var Y="--as-child",ht=Symbol("workerLogPaths");function V(e){return e[ht]}function F(e){let t=e&&e.channel;return!!(e&&e.connected&&t&&!t.destroyed)}function Q(e,t){if(!F(e))return!1;try{return _optionalChain([e, 'access', _6 => _6.send, 'optionalCall', _7 => _7(t)]),!0}catch(r){if(_optionalChain([r, 'optionalAccess', _8 => _8.code])==="ERR_IPC_CHANNEL_CLOSED"||_optionalChain([r, 'optionalAccess', _9 => _9.code])==="EPIPE"||_optionalChain([r, 'optionalAccess', _10 => _10.errno])===-32)return!1;throw r}}function gt(e){let{id:t,modulePath:r,logDir:o,openLogWindows:n,isSilent:c,childFlag:i=Y}=e,a=_path.join.call(void 0, o,`worker-${t}.log`),p=_path.join.call(void 0, o,`worker-${t}.out.log`),h=_path.join.call(void 0, o,`worker-${t}.err.log`),m=_path.join.call(void 0, o,`worker-${t}.info.log`),d=_path.join.call(void 0, o,`worker-${t}.warn.log`),l=_path.join.call(void 0, o,`worker-${t}.error.log`);[a,p,h,m,d,l].forEach(dt);let s=_child_process.fork.call(void 0, r,[i],{stdio:["pipe","pipe","pipe","ipc"],env:{...process.env,WORKER_ID:String(t),WORKER_LOG:a},execArgv:process.execArgv,silent:c}),u=_fs.createWriteStream.call(void 0, p,{flags:"a"}),g=_fs.createWriteStream.call(void 0, h,{flags:"a"}),T=_fs.createWriteStream.call(void 0, m,{flags:"a"}),C=_fs.createWriteStream.call(void 0, d,{flags:"a"}),E=_fs.createWriteStream.call(void 0, l,{flags:"a"});_optionalChain([s, 'access', _11 => _11.stdout, 'optionalAccess', _12 => _12.pipe, 'call', _13 => _13(u)]),_optionalChain([s, 'access', _14 => _14.stderr, 'optionalAccess', _15 => _15.pipe, 'call', _16 => _16(g)]);let R=P=>`[parent] ${P} capture active for w${t} (pid ${s.pid})
9
- `;if(u.write(R("stdout")),g.write(R("stderr")),T.write(R("info")),C.write(R("warn")),E.write(R("error")),s.stdout){let P=N(S=>{if(S)try{T.write(`${S}
10
- `)}catch (e6){}});s.stdout.on("data",P)}if(s.stderr){let P=N(S=>{if(!S)return;let M=U(S);try{M==="error"?E.write(`${S}
11
- `):C.write(`${S}
12
- `)}catch (e7){}});s.stderr.on("data",P)}return s[ht]={structuredPath:a,outPath:p,errPath:h,infoPath:m,warnPath:d,errorPath:l},n&&pt([a,p,h,m,d,l],`worker-${t}`,c),u.on("error",()=>{}),g.on("error",()=>{}),T.on("error",()=>{}),C.on("error",()=>{}),E.on("error",()=>{}),s}function yt(e,t,r){process.stdout.write("\x1B[2J\x1B[H");let o=i=>/\b(ERROR|uncaughtException|unhandledRejection)\b/i.test(i),n=i=>/\b(WARN|WARNING)\b/i.test(i),c=[];for(let[,i]of e){if(!i)continue;let a=[];for(let p of t)p==="out"&&i.outPath&&a.push({path:i.outPath,src:"out"}),p==="err"&&i.errPath&&a.push({path:i.errPath,src:"err"}),p==="structured"&&i.structuredPath&&a.push({path:i.structuredPath,src:"structured"}),i.warnPath&&p==="warn"&&a.push({path:i.warnPath,src:"warn"}),i.infoPath&&p==="info"&&a.push({path:i.infoPath,src:"info"});for(let{path:p,src:h}of a){let m="";try{m=_fs.readFileSync.call(void 0, p,"utf8")}catch (e8){continue}for(let d of m.split(`
13
- `)){if(!d)continue;let l=d.replace(/\x1B\[[0-9;]*m/g,"");if(r==="all"){c.push(d);continue}if(r==="error"){o(l)&&c.push(d);continue}if(n(l)||h==="err"&&!o(l)){c.push(d);continue}}}}c.sort((i,a)=>{let p=_nullishCoalesce(_optionalChain([i, 'access', _17 => _17.match, 'call', _18 => _18(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/), 'optionalAccess', _19 => _19[0]]), () => ("")),h=_nullishCoalesce(_optionalChain([a, 'access', _20 => _20.match, 'call', _21 => _21(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/), 'optionalAccess', _22 => _22[0]]), () => (""));return p.localeCompare(h)}),process.stdout.write(`${c.join(`
14
- `)}
15
- `),process.stdout.write(`
16
- Press Esc/Ctrl+] to return to dashboard.
17
- `)}var _readline = require('readline'); var Tt = _interopRequireWildcard(_readline); var X = _interopRequireWildcard(_readline);async function wt(e,t,r){await new Promise(o=>{try{let n=_fs.statSync.call(void 0, e),c=Math.max(0,n.size-t),i=_fs.createReadStream.call(void 0, e,{start:c,encoding:"utf8"});i.on("data",a=>r(a)),i.on("end",o),i.on("error",o)}catch (e9){o(void 0)}})}function bt(e,t,r){if(t.ctrl&&t.name==="c")return{type:"CTRL_C"};if(r==="dashboard")return t.name&&/^[0-9]$/.test(t.name)?{type:"ATTACH",id:Number(t.name)}:t.name==="tab"&&!t.shift?{type:"CYCLE",delta:1}:t.name==="tab"&&t.shift?{type:"CYCLE",delta:-1}:t.name==="q"?{type:"QUIT"}:null;if(t.name==="escape"||t.ctrl&&t.name==="]")return{type:"DETACH"};if(t.ctrl&&t.name==="d")return{type:"CTRL_D"};let o=_nullishCoalesce(_nullishCoalesce(t.sequence, () => (e)), () => (""));return o?{type:"FORWARD",sequence:o}:null}function xt(e){return[...e.keys()].sort((t,r)=>t-r)}function St(e,t,r){if(!e.length)return null;let o=_nullishCoalesce(t, () => (e[0])),n=e.indexOf(o);return n===-1&&(n=0),n=(n+r+e.length)%e.length,e[n]}function Pt(e){let{workers:t,onAttach:r,onDetach:o,onCtrlC:n,getLogPaths:c,replayBytes:i=200*1024,replayWhich:a=["out","err"],onEnterAttachScreen:p,ports:h}=e,m=_nullishCoalesce(_optionalChain([h, 'optionalAccess', _23 => _23.stdin]), () => (process.stdin)),d=_nullishCoalesce(_optionalChain([h, 'optionalAccess', _24 => _24.stdout]), () => (process.stdout)),l=_nullishCoalesce(_optionalChain([h, 'optionalAccess', _25 => _25.stderr]), () => (process.stderr)),s=(...x)=>{if(_chunkP7WCYR3Acjs.z)try{(_nullishCoalesce(_optionalChain([h, 'optionalAccess', _26 => _26.stderr]), () => (process.stderr))).write(`[keys] ${x.map(String).join(" ")}
18
- `)}catch (e10){}};if(!m.isTTY)return()=>{};Tt.emitKeypressEvents(m),_optionalChain([m, 'access', _27 => _27.setRawMode, 'optionalCall', _28 => _28(!0)]);let u="dashboard",g=null,T=null,C=null;async function E(x){if(!c)return;let f=c(x);if(!f)return;let y=[];for(let b of a)b==="out"&&y.push(f.outPath),b==="err"&&y.push(f.errPath),b==="structured"&&y.push(f.structuredPath);if(y.length){d.write(`
19
- ------------ replay ------------
20
- `);for(let b of y)d.write(`
21
- --- ${b} (last ~${Math.floor(i/1024)}KB) ---
22
- `),await wt(b,i,L=>d.write(L));d.write(`
23
- --------------------------------
24
-
25
- `)}}let R=async x=>{s("attach()",`id=${x}`);let f=t.get(x);if(!f)return;u==="attached"&&P(),u="attached",g=x,_optionalChain([p, 'optionalCall', _29 => _29(x)]),_optionalChain([r, 'optionalCall', _30 => _30(x)]),await E(x),T=b=>d.write(b),C=b=>l.write(b),_optionalChain([f, 'access', _31 => _31.stdout, 'optionalAccess', _32 => _32.on, 'call', _33 => _33("data",T)]),_optionalChain([f, 'access', _34 => _34.stderr, 'optionalAccess', _35 => _35.on, 'call', _36 => _36("data",C)]);let y=()=>{g===x&&P()};f.once("exit",y)},P=()=>{if(s("detach()",`id=${g}`),g==null)return;let x=g,f=t.get(x);f&&(T&&_optionalChain([f, 'access', _37 => _37.stdout, 'optionalAccess', _38 => _38.off, 'call', _39 => _39("data",T)]),C&&_optionalChain([f, 'access', _40 => _40.stderr, 'optionalAccess', _41 => _41.off, 'call', _42 => _42("data",C)])),T=null,C=null,g=null,u="dashboard",_optionalChain([o, 'optionalCall', _43 => _43()])},S=(x,f)=>{s("keypress",JSON.stringify({str:x,name:f.name,seq:f.sequence,ctrl:f.ctrl,meta:f.meta,shift:f.shift,mode:u}));let y=bt(x,f,u);if(s("mapped",JSON.stringify(y)),!!y)switch(y.type){case"CTRL_C":{if(s("CTRL_C"),u==="attached"&&g!=null){let b=t.get(g);try{_optionalChain([b, 'optionalAccess', _44 => _44.kill, 'call', _45 => _45("SIGINT")])}catch (e11){}P();return}_optionalChain([n, 'optionalCall', _46 => _46()]);return}case"ATTACH":{if(s("ATTACH",`id=${y.id}`,`has=${t.has(y.id)}`),u!=="dashboard")return;t.has(y.id)&&R(y.id);return}case"CYCLE":{if(s("CYCLE",`delta=${y.delta}`),u!=="dashboard")return;let b=St(xt(t),g,y.delta);b!=null&&R(b);return}case"QUIT":{if(u!=="dashboard")return;_optionalChain([n, 'optionalCall', _47 => _47()]);return}case"DETACH":{s("DETACH"),u==="attached"&&P();return}case"CTRL_D":{if(u==="attached"&&g!=null){let b=t.get(g);try{_optionalChain([b, 'optionalAccess', _48 => _48.stdin, 'optionalAccess', _49 => _49.end, 'call', _50 => _50()])}catch (e12){}}return}case"FORWARD":if(u==="attached"&&g!=null){let b=t.get(g);try{_optionalChain([b, 'optionalAccess', _51 => _51.stdin, 'optionalAccess', _52 => _52.write, 'call', _53 => _53(y.sequence)])}catch (e13){}}}},M=x=>{if(u==="attached"&&g!=null){let f=t.get(g);try{_optionalChain([f, 'optionalAccess', _54 => _54.stdin, 'optionalAccess', _55 => _55.write, 'call', _56 => _56(x)])}catch (e14){}}},A=()=>{m.off("keypress",S),m.off("data",M),_optionalChain([m, 'access', _57 => _57.setRawMode, 'optionalCall', _58 => _58(!1)]),d.write("\x1B[?25h")};return m.on("keypress",S),m.on("data",M),A}var _events = require('events');var _url = require('url');var Ct="",ie=(e,t)=>{let r=Math.min(e-1,9),o=e<=1?"0":`0-${r}`,n=e>10?" (Tab/Shift+Tab for \u226510)":"";return t?_colors2.default.dim("Run complete \u2014 digits to view logs \u2022 Tab/Shift+Tab cycle \u2022 Esc/Ctrl+] detach \u2022 q to quit"):_colors2.default.dim(`Hotkeys: [${o}] attach${n} \u2022 e=errors \u2022 w=warnings \u2022 i=info \u2022 l=logs \u2022 Tab/Shift+Tab \u2022 Esc/Ctrl+] detach \u2022 Ctrl+C exit`)};function vt(e,t,r=!1){let o=[...t.renderHeader(e),"",...t.renderWorkers(e),...r?[]:["",ie(e.poolSize,e.final)],...t.renderExtras?[""].concat(t.renderExtras(e)):[]].join(`
26
- `);!e.final&&o===Ct||(Ct=o,e.final?process.stdout.write("\x1B[?25h"):(process.stdout.write("\x1B[?25l"),X.cursorTo(process.stdout,0,0),X.clearScreenDown(process.stdout)),process.stdout.write(`${o}
27
- `))}function Z(e,t,r){let o=t.get(e);if(F(o))try{let n=V(o);if(n!=null)return n}catch (e15){}return r.get(e)}function I(e){return typeof e=="number"?e.toLocaleString():"0"}function Lt(e,t=40){let r=Math.max(0,Math.min(100,Math.floor(e))),o=Math.floor(r/100*t);return"\u2588".repeat(o)+"\u2591".repeat(t-o)}function le(e){let t=[...e.workerState.values()].filter(n=>n.busy).length,r=e.filesCompleted+e.filesFailed,o=e.filesTotal===0?100:Math.floor(r/Math.max(1,e.filesTotal)*100);return{done:r,inProgress:t,pct:o}}function $t(e,t=[]){let{title:r,poolSize:o,cpuCount:n,filesTotal:c,filesCompleted:i,filesFailed:a,throughput:p}=e,{inProgress:h,pct:m}=le(e),d=[`${_colors2.default.bold(r)} \u2014 ${o} workers ${_colors2.default.dim(`(CPU avail: ${n})`)}`,`${_colors2.default.dim("Files")} ${I(c)} ${_colors2.default.dim("Completed")} ${I(i)} ${_colors2.default.dim("Failed")} ${a?_colors2.default.red(I(a)):I(a)} ${_colors2.default.dim("In-flight")} ${I(h)}`,`[${Lt(m)}] ${m}%`];if(p){let l=Math.round(p.r10s*3600).toLocaleString(),s=Math.round(p.r60s*3600).toLocaleString(),u=_optionalChain([e, 'access', _59 => _59.throughput, 'optionalAccess', _60 => _60.successSoFar])!=null?` Newly uploaded: ${I(e.throughput.successSoFar)}`:"";d.push(_colors2.default.cyan(`Throughput: ${l}/hr (1h: ${s}/hr)${u}`))}return t.length?d.concat(t):d}function Rt(e,t=r=>r?_path.basename.call(void 0, r):"-"){return[...e.workerState.entries()].map(([o,n])=>{let c=n.lastLevel==="error"?_colors2.default.red("ERROR "):n.lastLevel==="warn"?_colors2.default.yellow("WARN "):n.busy?_colors2.default.green("WORKING"):_colors2.default.dim("IDLE "),i=t(n.file),a=n.startedAt?`${Math.floor((Date.now()-n.startedAt)/1e3)}s`:"-",p=_nullishCoalesce(_optionalChain([n, 'access', _61 => _61.progress, 'optionalAccess', _62 => _62.processed]), () => (0)),h=_nullishCoalesce(_optionalChain([n, 'access', _63 => _63.progress, 'optionalAccess', _64 => _64.total]), () => (0)),m=h>0?Math.floor(p/h*100):0,d=h>0?Lt(m,18):" ".repeat(18),l=h>0?`${p.toLocaleString()}/${h.toLocaleString()} (${m}%)`:_colors2.default.dim("\u2014");return` [w${o}] ${c} | ${i} | ${a} | [${d}] ${l}`})}async function Et(e){let{title:t,baseDir:r,poolSize:o,cpuCount:n,render:c,childModulePath:i,hooks:a,filesTotal:p,childFlag:h,viewerMode:m=!1}=e,d=_nullishCoalesce(e.openLogWindows, () => (!m)),l=_nullishCoalesce(e.isSilent, () => (!0)),s=Date.now(),u=mt(r),g=new Map,T=new Map,C=new Map,E=new _chunkCUUCCUX2cjs.$f,P=_nullishCoalesce(_optionalChain([a, 'access', _65 => _65.initTotals, 'optionalCall', _66 => _66()]), () => ({})),S=0,M=0,A=0,x=null,f=!1,y=!1,b=null,L=(w=!1)=>{y||c({title:t,poolSize:o,cpuCount:n,filesTotal:p,filesCompleted:M,filesFailed:A,workerState:T,totals:P,final:w,exportStatus:_optionalChain([a, 'access', _67 => _67.exportStatus, 'optionalCall', _68 => _68()]),throughput:{successSoFar:M,r10s:E.rate(1e4),r60s:E.rate(6e4)}})},B=w=>{let $=a.nextTask();if(!$)return!1;let _=g.get(w),v=a.taskLabel($),O=_optionalChain([a, 'access', _69 => _69.initSlotProgress, 'optionalCall', _70 => _70($)]);return T.set(w,{busy:!0,file:v,startedAt:Date.now(),lastLevel:"ok",progress:O}),Q(_,{type:"task",payload:$}),L(),!0};for(let w=0;w<o;w+=1){let $=gt({id:w,modulePath:i,logDir:u,openLogWindows:d,isSilent:l,childFlag:h});g.set(w,$),T.set(w,{busy:!1,file:null,startedAt:null,lastLevel:"ok"}),C.set(w,V($)),S+=1;let _=N(v=>{let O=U(v);if(!O)return;let G=T.get(w);G.lastLevel!==O&&(T.set(w,{...G,lastLevel:O}),L())});_optionalChain([$, 'access', _71 => _71.stderr, 'optionalAccess', _72 => _72.on, 'call', _73 => _73("data",_)]),$.on("message",v=>{if(!(!v||typeof v!="object")){if(v.type==="ready"){f||(f=!0,x=setInterval(()=>L(!1),350)),B(w);return}if(v.type==="progress"){P=a.onProgress(P,v.payload);let O=T.get(w);T.set(w,{...O,progress:v.payload}),L();return}if(v.type==="result"){let O=T.get(w),{totals:G,ok:ot}=a.onResult(P,v.payload);P=G,ot?(M+=1,E.add(1)):A+=1,T.set(w,{...O,busy:!1,file:null,progress:void 0,lastLevel:ot?"ok":"error"}),!B(w)&&F($)&&Q($,{type:"shutdown"}),L()}}}),$.on("exit",()=>{S-=1,S===0&&(x&&clearInterval(x),L(!0))})}let H=()=>{},K=()=>{try{_optionalChain([process, 'access', _74 => _74.stdin, 'access', _75 => _75.setRawMode, 'optionalCall', _76 => _76(!1)])}catch (e16){}try{process.stdin.pause()}catch (e17){}},et=()=>{if(x&&clearInterval(x),_optionalChain([H, 'optionalCall', _77 => _77()]),b)try{process.stdin.off("data",b)}catch (e18){}K(),process.stdout.write(`
28
- Stopping workers...
29
- `);for(let[,w]of g){F(w)&&Q(w,{type:"shutdown"});try{_optionalChain([w, 'optionalAccess', _78 => _78.kill, 'call', _79 => _79("SIGTERM")])}catch (e19){}}process.exit(130)},rt=w=>{y=!0,process.stdout.write("\x1B[2J\x1B[H"),process.stdout.write(`Attached to worker ${w}. (Esc/Ctrl+] detach \u2022 Ctrl+D EOF \u2022 Ctrl+C SIGINT)
30
- `)},jt=()=>{y=!1,L()};if(process.once("SIGINT",et),!m){if(process.stdin.isTTY){try{process.stdin.setRawMode(!0)}catch (e20){process.stdout.write(_colors2.default.yellow(`Warning: Unable to enable raw mode for interactive key handling.
31
- `))}process.stdin.resume()}H=Pt({workers:g,onAttach:rt,onDetach:jt,onCtrlC:et,getLogPaths:w=>Z(w,g,C),replayBytes:200*1024,replayWhich:["out","err"],onEnterAttachScreen:rt}),e.extraKeyHandler&&(b=e.extraKeyHandler({logsBySlot:C,repaint:()=>L(),setPaused:w=>{y=w}}),process.stdin.on("data",b))}await new Promise(w=>{let $=setInterval(async()=>{if(S===0){if(clearInterval($),x&&clearInterval(x),H(),b)try{process.stdin.off("data",b)}catch (e21){}K();let _=Date.now();try{await _optionalChain([a, 'access', _80 => _80.postProcess, 'optionalCall', _81 => _81({slots:T,totals:P,logDir:u,logsBySlot:C,startedAt:s,finishedAt:_,viewerMode:m,getLogPathsForSlot:v=>Z(v,g,C)})])}catch(v){let O=_nullishCoalesce(_optionalChain([v, 'optionalAccess', _82 => _82.stack]), () => (String(v)));process.stdout.write(_colors2.default.red(`postProcess error: ${O}
32
- `))}w()}},300)})}function Mt(e){let{logsBySlot:t,repaint:r,setPaused:o,exportMgr:n,exportStatus:c,custom:i}=e,a=l=>{process.stdout.write(`${l}
33
- `)},p=(l,s)=>{let u=Date.now(),g=_nullishCoalesce(_optionalChain([c, 'optionalAccess', _83 => _83[l]]), () => ({path:s}));c&&(c[l]={path:s||g.path,savedAt:u,exported:!0},r())},h=!1,m=(l,s)=>{h||(h=!0,o(!0),process.stdout.write("\x1B[2J\x1B[H"),process.stdout.write(`Combined logs viewer (press Esc or Ctrl+] to return)
34
-
35
- `),(async()=>{try{await yt(t,l,s)}catch (e22){h=!1,o(!1),r()}})())},d=(l,s)=>{if(n)try{let u=n.exportCombinedLogs(t,l);a(`
36
- Wrote combined ${s} logs to: ${u}`),p(l,u)}catch (e23){a(`
37
- Failed to write combined ${s} logs`)}};return l=>{let s=l.toString("utf8");if(s==="e"){m(["err"],"error");return}if(s==="w"){m(["warn","err"],"warn");return}if(s==="i"){m(["info"],"all");return}if(s==="l"){m(["out","err","structured"],"all");return}if(s==="E"){d("error","error");return}if(s==="W"){d("warn","warn");return}if(s==="I"){d("info","info");return}if(s==="A"){d("all","ALL");return}let u=_optionalChain([i, 'optionalAccess', _84 => _84[s]]);if(u){u({noteExport:p,say:a});return}(s==="\x1B"||s==="")&&(h=!1,o(!1),r())}}var _promises = require('fs/promises');var _promises3 = require('stream/promises');var _stream = require('stream');var _csvparse = require('csv-parse');var _fastcsv = require('fast-csv'); var Bt = _interopRequireWildcard(_fastcsv);function tt(e,t){let r=_fs.createWriteStream.call(void 0, e),o=Bt.format({headers:t,writeHeaders:!0,objectMode:!0});return o.pipe(r),{async write(n){o.write(n)||await _events.once.call(void 0, o,"drain")},async end(){let n=Promise.all([_events.once.call(void 0, r,"finish")]);o.end(),await n}}}function xe(e){return String(e).padStart(4,"0")}function Se(e){return Buffer.byteLength(Object.values(e).map(t=>t==null?"":String(t)).join(","),"utf8")}async function Dt(e){let{filePath:t,outputDir:r,clearOutputDir:o,chunkSizeMB:n,onProgress:c,reportEveryMs:i=500}=e,{size:a}=await _promises.stat.call(void 0, t),p=0;_chunkZUNVPK23cjs.a.info(_colors2.default.magenta(`Chunking ${t} into ~${n}MB files...`));let h=Math.floor(n*1024*1024),m=_path.basename.call(void 0, t,".csv"),d=r||_path.dirname.call(void 0, t);if(_chunkZUNVPK23cjs.a.info(_colors2.default.magenta(`Output directory: ${d}`)),await _promises.mkdir.call(void 0, d,{recursive:!0}),o){_chunkZUNVPK23cjs.a.warn(_colors2.default.yellow(`Clearing output directory: ${d}`));let f=await _promises.readdir.call(void 0, d);await Promise.all(f.filter(y=>y.startsWith(`${m}_chunk_`)&&y.endsWith(".csv")).map(y=>_promises.unlink.call(void 0, _path.join.call(void 0, d,y))))}let l=null,s=null,u=0,g=1,T=0,C=new (0, _csvparse.Parser)({columns:!1,skip_empty_lines:!0}),E=0,R=0,P=()=>{let f=R>0?E/R:0,y=f>0?Math.max(u,Math.ceil(a/f)):void 0;c(u,y),p=Date.now()};P();let S=null,M=()=>_path.join.call(void 0, d,`${m}_chunk_${xe(g)}.csv`),A=new (0, _stream.Transform)({objectMode:!0,async transform(f,y,b){try{if(!l){l=f.slice(0),s=l.length,S=tt(M(),l),b();return}s!==null&&f.length!==s&&_chunkZUNVPK23cjs.a.warn(_colors2.default.yellow(`Row has ${f.length} cols; expected ${s}`)),u+=1,u%25e4===0&&c(u);let L=Object.fromEntries(l.map((H,K)=>[H,f[K]])),B=Se(L);E+=B,R+=1,Date.now()-p>=i&&P(),S&&T>0&&T+B>h&&(await S.end(),g+=1,T=0,_chunkZUNVPK23cjs.a.info(_colors2.default.green(`Rolling to chunk ${g} after ${u.toLocaleString()} rows.`)),S=tt(M(),l)),S||(S=tt(M(),l)),await S.write(L),T+=B,b()}catch(L){b(L)}},async flush(f){try{S&&(await S.end(),S=null),P(),f()}catch(y){f(y)}}}),x=_fs.createReadStream.call(void 0, t);await _promises3.pipeline.call(void 0, x,C,A),c(u),_chunkZUNVPK23cjs.a.info(_colors2.default.green(`Chunked ${t} into ${g} file(s); processed ${u.toLocaleString()} rows.`))}async function Ft(){let e=Number(process.env.WORKER_ID||"0");_chunkZUNVPK23cjs.a.info(`[w${e}] ready pid=${process.pid}`),_optionalChain([process, 'access', _85 => _85.send, 'optionalCall', _86 => _86({type:"ready"})]),process.on("message",async t=>{if(!t||typeof t!="object"||(t.type==="shutdown"&&process.exit(0),t.type!=="task"))return;let{filePath:r,options:o}=t.payload,{outputDir:n,clearOutputDir:c,chunkSizeMB:i}=o;try{await Dt({filePath:r,outputDir:n,clearOutputDir:c,chunkSizeMB:i,onProgress:(a,p)=>_optionalChain([process, 'access', _87 => _87.send, 'optionalCall', _88 => _88({type:"progress",payload:{filePath:r,processed:a,total:p}})])}),_optionalChain([process, 'access', _89 => _89.send, 'optionalCall', _90 => _90({type:"result",payload:{ok:!0,filePath:r}})])}catch(a){let p=_chunkCUUCCUX2cjs.Wf.call(void 0, a);_chunkZUNVPK23cjs.a.error(`[w${e}] ERROR ${r}: ${p}`),_optionalChain([process, 'access', _91 => _91.send, 'optionalCall', _92 => _92({type:"result",payload:{ok:!1,filePath:r,error:p}})])}}),await new Promise(()=>{})}function Te(e){return $t(e)}function Pe(e){return Rt(e)}var It={renderHeader:Te,renderWorkers:Pe};function Ce(){return typeof __filename<"u"?__filename:process.argv[1]}async function Do(e){_chunkWKCTKYN4cjs.a.call(void 0, this.process.exit);let{directory:t,outputDir:r,clearOutputDir:o,chunkSizeMB:n,concurrency:c,viewerMode:i}=e,a=lt(t,this),{poolSize:p,cpuCount:h}=ct(c,a.length);_chunkZUNVPK23cjs.a.info(_colors2.default.green(`Chunking ${a.length} CSV file(s) with pool size ${p} (CPU=${h})`));let m=a.map(l=>({filePath:l,options:{outputDir:r,clearOutputDir:o,chunkSizeMB:n}})),d={nextTask:()=>m.shift(),taskLabel:l=>l.filePath,initTotals:()=>({}),initSlotProgress:()=>{},onProgress:l=>l,onResult:(l,s)=>({totals:l,ok:!!s.ok}),postProcess:async()=>{}};await Et({title:"Chunk CSV",baseDir:t||r||process.cwd(),childFlag:Y,childModulePath:Ce(),poolSize:p,cpuCount:h,filesTotal:a.length,hooks:d,viewerMode:i,render:l=>vt(l,It,i),extraKeyHandler:({logsBySlot:l,repaint:s,setPaused:u})=>Mt({logsBySlot:l,repaint:s,setPaused:u})})}process.argv.includes(Y)&&Ft().catch(e=>{_chunkZUNVPK23cjs.a.error(e),process.exit(1)});exports.chunkCsv = Do;
38
- //# sourceMappingURL=impl-XJQYOQHI.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/home/runner/work/cli/cli/dist/impl-XJQYOQHI.cjs","../src/commands/admin/chunk-csv/impl.ts","../src/lib/helpers/collectCsvFilesOrExit.ts","../src/lib/pooling/openTerminal.ts","../src/lib/pooling/logRotation.ts","../src/lib/pooling/spawnWorkerProcess.ts","../src/lib/pooling/showCombinedLogs.ts","../src/lib/pooling/keymap.ts","../src/lib/pooling/installInteractiveSwitcher.ts","../src/lib/pooling/dashboardPlugin.ts","../src/lib/pooling/uiPlugins.ts","../src/lib/pooling/runPool.ts","../src/lib/pooling/createExtraKeyHandler.ts","../src/lib/helpers/chunkOneCsvFile.ts"],"names":["collectCsvFilesOrExit","directory","localContext","logger","colors","files","readdirSync","f","join","p","statSync","err","logDir"],"mappings":"AAAA,2lCAAyC,wDAAoD,gCAA6B,wDAAyC,wDAA0C,gCAA6B,gFCCvN,4BCDE,wBACiB,SAatBA,EAAAA,CACdC,CAAAA,CACAC,CAAAA,CACU,CACLD,CAAAA,EAAAA,CACHE,mBAAAA,CAAO,KAAA,CAAMC,gBAAAA,CAAO,GAAA,CAAI,iCAAiC,CAAC,CAAA,CAC1DF,CAAAA,CAAa,OAAA,CAAQ,IAAA,CAAK,CAAC,CAAA,CAAA,CAG7B,IAAIG,CAAAA,CAAkB,CAAC,CAAA,CACvB,GAAI,CAEFA,CAAAA,CADgBC,6BAAAA,CAAqB,CAAA,CAElC,MAAA,CAAQC,CAAAA,EAAMA,CAAAA,CAAE,QAAA,CAAS,MAAM,CAAC,CAAA,CAChC,GAAA,CAAKA,CAAAA,EAAMC,wBAAAA,CAAKP,CAAWM,CAAC,CAAC,CAAA,CAC7B,MAAA,CAAQE,CAAAA,EAAM,CACb,GAAI,CACF,OAAOC,0BAAAA,CAAU,CAAA,CAAE,MAAA,CAAO,CAC5B,CAAA,UAAQ,CACN,MAAO,CAAA,CACT,CACF,CAAC,CACL,CAAA,KAAA,CAASC,CAAAA,CAAK,CACZR,mBAAAA,CAAO,KAAA,CAAMC,gBAAAA,CAAO,GAAA,CAAI,CAAA,0BAAA,EAA6BH,CAAS,CAAA,CAAA;ACA7C;AAAA;AAGiD,mCAAA;AAAA;AA0DzD,MAAA;ACJY;AC6BFW;AA4CS;AAeG;AAGD;ACxIQ;AAwDL;AAAK;AACnB;AAAA;ACpCrB;AC6CiB;AAAA;AAGT;AAAmD,IAAA;AAI1C;AAAA;AAAA;AA2EX;ACzBuB;AC5F3B;AC8aqB;AAgBK;AAiBlB;AAoEY;AC3eK;AAyCvB;AAAA;AA6BI;AAGA,eAAA;AC/LV,yBAAA","file":"/home/runner/work/cli/cli/dist/impl-XJQYOQHI.cjs","sourcesContent":[null,"import type { LocalContext } from '../../../context';\nimport colors from 'colors';\nimport { logger } from '../../../logger';\nimport { collectCsvFilesOrExit } from '../../../lib/helpers/collectCsvFilesOrExit';\nimport {\n computePoolSize,\n createExtraKeyHandler,\n CHILD_FLAG,\n type PoolHooks,\n runPool,\n dashboardPlugin,\n} from '../../../lib/pooling';\nimport {\n runChild,\n type ChunkProgress,\n type ChunkResult,\n type ChunkTask,\n} from './worker';\nimport { chunkCsvPlugin } from './ui';\nimport { doneInputValidation } from '../../../lib/cli/done-input-validation';\n\n/**\n * Returns the current module's path so the worker pool knows what file to re-exec.\n * In Node ESM, __filename is undefined, so we fall back to argv[1].\n *\n * @returns The current module's path as a string\n */\nfunction getCurrentModulePath(): string {\n if (typeof __filename !== 'undefined') {\n return __filename as unknown as string;\n }\n return process.argv[1];\n}\n\n/**\n * Totals aggregate for this command.\n * We don’t need custom counters since the runner already tracks\n * completed/failed counts in its header — so we just use an empty record.\n */\ntype Totals = Record<string, never>;\n\n/**\n * CLI flags accepted by the `chunk-csv` command.\n *\n * These are passed down from the CLI parser into the parent process.\n */\nexport type ChunkCsvCommandFlags = {\n directory: string;\n outputDir?: string;\n clearOutputDir: boolean;\n chunkSizeMB: number;\n concurrency?: number;\n viewerMode: boolean;\n};\n\n/**\n * Parent entrypoint for chunking many CSVs in parallel using the worker pool runner.\n *\n * Lifecycle:\n * 1) Discover CSV inputs (exit if none).\n * 2) Compute pool size (CPU-count heuristic or --concurrency).\n * 3) Build a FIFO queue of `ChunkTask`s.\n * 4) Define pool hooks to drive task assignment, progress, and result handling.\n * 5) Launch the pool with `runPool`, rendering via the `chunkCsvPlugin`.\n *\n * @param this - Bound CLI context (provides process exit + logging).\n * @param flags - CLI options for the run.\n */\nexport async function chunkCsv(\n this: LocalContext,\n flags: ChunkCsvCommandFlags,\n): Promise<void> {\n doneInputValidation(this.process.exit);\n\n const {\n directory,\n outputDir,\n clearOutputDir,\n chunkSizeMB,\n concurrency,\n viewerMode,\n } = flags;\n\n /* 1) Discover CSV inputs */\n const files = collectCsvFilesOrExit(directory, this);\n\n /* 2) Size the pool */\n const { poolSize, cpuCount } = computePoolSize(concurrency, files.length);\n\n logger.info(\n colors.green(\n `Chunking ${files.length} CSV file(s) with pool size ${poolSize} (CPU=${cpuCount})`,\n ),\n );\n\n /* 3) Prepare a simple FIFO queue of tasks (one per file). */\n const queue = files.map<ChunkTask>((filePath) => ({\n filePath,\n options: { outputDir, clearOutputDir, chunkSizeMB },\n }));\n\n /* 4) Define pool hooks to adapt runner to this command. */\n const hooks: PoolHooks<ChunkTask, ChunkProgress, ChunkResult, Totals> = {\n nextTask: () => queue.shift(),\n taskLabel: (t) => t.filePath,\n initTotals: () => ({} as Totals),\n initSlotProgress: () => undefined,\n onProgress: (totals) => totals,\n onResult: (totals, res) => ({ totals, ok: !!res.ok }),\n // postProcess receives log context when viewerMode=true — we don’t need it here.\n postProcess: async () => {\n // nothing extra for chunk-csv\n },\n };\n\n /* 5) Launch the pool runner with our hooks and custom dashboard plugin. */\n await runPool({\n title: 'Chunk CSV',\n baseDir: directory || outputDir || process.cwd(),\n childFlag: CHILD_FLAG,\n childModulePath: getCurrentModulePath(),\n poolSize,\n cpuCount,\n filesTotal: files.length,\n hooks,\n viewerMode,\n render: (input) => dashboardPlugin(input, chunkCsvPlugin, viewerMode),\n extraKeyHandler: ({ logsBySlot, repaint, setPaused }) =>\n createExtraKeyHandler({\n logsBySlot,\n repaint,\n setPaused,\n }),\n });\n}\n\n/* -------------------------------------------------------------------------------------------------\n * If invoked directly as a child process, enter worker loop\n * ------------------------------------------------------------------------------------------------- */\nif (process.argv.includes(CHILD_FLAG)) {\n runChild().catch((err) => {\n logger.error(err);\n process.exit(1);\n });\n}\n","import { join } from 'node:path';\nimport { readdirSync, statSync } from 'node:fs';\nimport colors from 'colors';\nimport { logger } from '../../logger';\nimport type { LocalContext } from '../../context';\n\n/**\n * Validate flags and collect CSV file paths from a directory.\n * On validation error, the provided `exit` function is called.\n *\n * @param directory - the directory containing CSV files\n * @param localContext - the context of the command, used for logging and exit\n * @returns an array of valid CSV file paths\n */\nexport function collectCsvFilesOrExit(\n directory: string | undefined,\n localContext: LocalContext,\n): string[] {\n if (!directory) {\n logger.error(colors.red('A --directory must be provided.'));\n localContext.process.exit(1);\n }\n\n let files: string[] = [];\n try {\n const entries = readdirSync(directory);\n files = entries\n .filter((f) => f.endsWith('.csv'))\n .map((f) => join(directory, f))\n .filter((p) => {\n try {\n return statSync(p).isFile();\n } catch {\n return false;\n }\n });\n } catch (err) {\n logger.error(colors.red(`Failed to read directory: ${directory}`));\n logger.error(colors.red((err as Error).message));\n localContext.process.exit(1);\n }\n\n if (files.length === 0) {\n logger.error(colors.red(`No CSV files found in directory: ${directory}`));\n localContext.process.exit(1);\n }\n\n return files;\n}\n","import colors from 'colors';\nimport { spawn } from 'node:child_process';\nimport { platform } from 'node:os';\nimport { logger } from '../../logger';\n\n/**\n * Escapes a string for use in a shell command.\n *\n * @param p - The string to escape.\n * @returns The escaped string, suitable for use in a shell command.\n */\nexport function shellEscape(p: string): string {\n return `'${String(p).replace(/'/g, \"'\\\\''\")}'`;\n}\n\n/**\n * Opens a new terminal window and tails multiple log files.\n *\n * @param paths - Array of file paths to tail.\n * @param title - Title for the terminal window.\n * @param isSilent - If true, does not open the terminal.\n */\nexport function openLogTailWindowMulti(\n paths: string[],\n title: string,\n isSilent = false,\n): void {\n // If silent mode is enabled, do not open the terminal\n if (isSilent) return;\n\n // Determine the platform and execute the appropriate command\n const p = platform();\n try {\n // For macOS, use AppleScript to open a new Terminal window\n // and tail the specified files\n if (p === 'darwin') {\n const tails = paths.map(shellEscape).join(' -f ');\n const script = `\n tell application \"Terminal\"\n activate\n do script \"printf '\\\\e]0;${title}\\\\a'; tail -n +1 -f ${tails}\"\n end tell\n `;\n spawn('osascript', ['-e', script], { stdio: 'ignore', detached: true });\n return;\n }\n\n // For Windows, use PowerShell to open a new terminal window\n // and tail the specified files\n // The paths are escaped to handle spaces and special characters\n // The command uses Get-Content to tail the files and -Wait to keep the terminal\n if (p === 'win32') {\n const arrayLiteral = `@(${paths\n .map((x) => `'${x.replace(/'/g, \"''\")}'`)\n .join(',')})`;\n const ps = [\n 'powershell',\n '-NoExit',\n '-Command',\n `Write-Host '${title}'; $paths = ${arrayLiteral}; Get-Content -Path $paths -Tail 200 -Wait`,\n ];\n spawn('cmd.exe', ['/c', 'start', ...ps], {\n stdio: 'ignore',\n detached: true,\n }).unref();\n return;\n }\n\n // For Linux, use gnome-terminal or xterm to open a new terminal window\n // and tail the specified files\n // The paths are escaped to handle spaces and special characters\n const tails = paths.map(shellEscape).join(' -f ');\n try {\n spawn(\n 'gnome-terminal',\n [\n '--',\n 'bash',\n '-lc',\n `printf '\\\\e]0;${title}\\\\a'; tail -n +1 -f ${tails}`,\n ],\n {\n stdio: 'ignore',\n detached: true,\n },\n ).unref();\n } catch {\n spawn(\n 'xterm',\n ['-title', title, '-e', `tail -n +1 -f ${paths.join(' ')}`],\n {\n stdio: 'ignore',\n detached: true,\n },\n ).unref();\n }\n } catch (e) {\n logger.error(\n colors.red(\n `Failed to open terminal window for tailing logs: ${\n e instanceof Error ? e.message : String(e)\n }`,\n ),\n );\n throw e;\n }\n}\n","// logRotation.ts\nimport {\n readdirSync,\n writeFileSync,\n existsSync,\n unlinkSync,\n mkdirSync,\n} from 'node:fs';\nimport { join } from 'node:path';\nimport colors from 'colors';\n\n/**\n * Reset worker logs in the given directory.\n * mode:\n * - \"truncate\": empty files but keep them (best if tails are open)\n * - \"delete\": remove files entirely (simplest if no tails yet)\n *\n * @param dir - Directory to reset logs in\n * @param mode - 'truncate' or 'delete'\n */\nfunction resetWorkerLogs(dir: string, mode: 'truncate' | 'delete'): void {\n const patterns = [\n /worker-\\d+\\.log$/,\n /worker-\\d+\\.out\\.log$/,\n /worker-\\d+\\.err\\.log$/,\n /worker-\\d+\\.warn\\.log$/,\n /worker-\\d+\\.info\\.log$/,\n ];\n for (const name of readdirSync(dir)) {\n // eslint-disable-next-line no-continue\n if (!patterns.some((rx) => rx.test(name))) continue;\n const p = join(dir, name);\n try {\n if (mode === 'delete' && existsSync(p)) unlinkSync(p);\n else writeFileSync(p, '');\n } catch {\n /* ignore */\n }\n }\n process.stdout.write(\n colors.dim(\n `Logs have been ${\n mode === 'delete' ? 'deleted' : 'truncated'\n } in ${dir}\\n`,\n ),\n );\n}\n\n/**\n * Very robust classification of a single log line into warn/error.\n * Returns 'warn' | 'error' | null (null = not a level we care to badge).\n *\n * @param line - Single line of log output to classify\n * @returns 'warn' | 'error' | null\n */\nexport function classifyLogLevel(line: string): 'warn' | 'error' | null {\n // Strip common ANSI sequences\n // eslint-disable-next-line no-control-regex\n const s = line.replace(/\\x1B\\[[0-9;]*m/g, '');\n\n // 1) Explicit worker tag: \"[w12] WARN ...\" or \"[w2] ERROR ...\"\n const mTag = /\\[w\\d+\\]\\s+(ERROR|WARN)\\b/i.exec(s);\n if (mTag) return mTag[1].toLowerCase() as 'warn' | 'error';\n\n // 2) Common plain prefixes\n if (/^\\s*(ERROR|ERR|FATAL)\\b/i.test(s)) return 'error';\n if (/^\\s*(WARN|WARNING)\\b/.test(s)) return 'warn';\n\n // Node runtime warnings\n if (/^\\s*\\(node:\\d+\\)\\s*Warning:/i.test(s)) return 'warn';\n if (/^\\s*DeprecationWarning:/i.test(s)) return 'warn';\n\n // 3) JSON logs (pino/bunyan/etc.)\n // Try to parse as JSON and inspect `level`\n try {\n const j = JSON.parse(s);\n const lv = j?.level;\n if (typeof lv === 'number') {\n // pino levels: 40=warn, 50=error, 60=fatal\n if (lv >= 50) return 'error';\n if (lv >= 40) return 'warn';\n } else if (typeof lv === 'string') {\n const L = lv.toLowerCase();\n if (L === 'error' || L === 'fatal') return 'error';\n if (L === 'warn' || L === 'warning') return 'warn';\n }\n } catch {\n // not JSON, ignore\n }\n\n // 4) Fallthrough: look for level words inside worker-tagged lines\n // e.g. \"[w3] something WARNING xyz\"\n const mInline = /\\[w\\d+\\].*\\b(WARN|WARNING|ERROR|FATAL)\\b/i.exec(s);\n if (mInline) {\n const L = mInline[1].toUpperCase();\n return L === 'ERROR' || L === 'FATAL' ? 'error' : 'warn';\n }\n\n return null;\n}\n\n/**\n * Stream splitter to get whole lines from 'data' events\n *\n * @param onLine - Callback to call with each complete line\n * @returns A function that processes a chunk of data and calls onLine for each complete line\n */\nexport function makeLineSplitter(\n onLine: (line: string) => void,\n): (chunk: Buffer | string) => void {\n let buf = '';\n return (chunk: Buffer | string) => {\n buf += chunk.toString('utf8');\n let nl: number;\n // eslint-disable-next-line no-cond-assign\n while ((nl = buf.indexOf('\\n')) !== -1) {\n const line = buf.slice(0, nl);\n onLine(line);\n buf = buf.slice(nl + 1);\n }\n };\n}\n/**\n * Checks if a log line contains an error indicator.\n *\n * @param t - The log line to check\n * @returns True if the line contains an error keyword, false otherwise\n */\nexport function isLogError(t: string): boolean {\n return /\\b(ERROR|uncaughtException|unhandledRejection)\\b/i.test(t);\n}\n\n/**\n * Checks if a log line contains a warning indicator.\n *\n * @param t - The log line to check\n * @returns True if the line contains a warning keyword, false otherwise\n */\nexport function isLogWarn(t: string): boolean {\n return /\\b(WARN|WARNING)\\b/i.test(t);\n}\n\n/**\n * Determines if a log line is a new header (error, warning, worker tag, or ISO timestamp).\n *\n * @param t - The log line to check\n * @returns True if the line is a new header, false otherwise\n */\nexport function isLogNewHeader(t: string): boolean {\n return (\n isLogError(t) ||\n isLogWarn(t) ||\n /^\\s*\\[w\\d+\\]/.test(t) ||\n /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}/.test(t)\n );\n}\n\n// eslint-disable-next-line no-control-regex\nconst stripAnsi = (s: string): string => s.replace(/\\x1B\\[[0-9;]*m/g, '');\n\n/**\n * Extracts blocks of text from a larger body of text.\n *\n * @param text - The text to extract blocks from\n * @param starts - A function that determines if a line starts a new block\n * @returns An array of extracted blocks\n */\nexport function extractBlocks(\n text: string,\n starts: (cleanLine: string) => boolean,\n): string[] {\n if (!text) return [];\n const out: string[] = [];\n const lines = text.split('\\n');\n let buf: string[] = [];\n let inBlock = false;\n\n const flush = (): void => {\n if (buf.length) out.push(buf.join('\\n'));\n buf = [];\n inBlock = false;\n };\n\n for (const raw of lines) {\n const clean = stripAnsi(raw || '');\n const headery = isLogNewHeader(clean);\n if (!inBlock) {\n if (starts(clean)) {\n inBlock = true;\n buf.push(raw);\n }\n // eslint-disable-next-line no-continue\n continue;\n }\n if (!raw || headery) {\n flush();\n if (starts(clean)) {\n inBlock = true;\n buf.push(raw);\n }\n } else {\n buf.push(raw);\n }\n }\n flush();\n return out.filter(Boolean);\n}\n\n/**\n * The kind of export artifact to retrieve the path for.\n */\nexport type LogExportKind = 'error' | 'warn' | 'info' | 'all';\n\n/**\n * Ensure log directory exists\n *\n * @param rootDir - Root directory\n * @returns log dir\n */\nexport function initLogDir(rootDir: string): string {\n const logDir = join(rootDir, 'logs');\n mkdirSync(logDir, { recursive: true });\n\n const RESET_MODE =\n (process.env.RESET_LOGS as 'truncate' | 'delete') ?? 'truncate';\n resetWorkerLogs(logDir, RESET_MODE);\n\n return logDir;\n}\n\nexport interface ExportArtifactResult {\n /** Whether the artifact was opened successfully */\n ok?: boolean;\n /** The absolute path to the export artifact */\n path: string;\n /** Time saved */\n savedAt?: number;\n /** If exported */\n exported?: boolean;\n}\n\n/**\n * Status map for export artifacts.\n */\nexport type ExportStatusMap = {\n /** The absolute paths to the error log artifacts */\n error?: ExportArtifactResult;\n /** The absolute paths to the warn log artifacts */\n warn?: ExportArtifactResult;\n /** The absolute paths to the info log artifacts */\n info?: ExportArtifactResult;\n /** The absolute paths to all log artifacts */\n all?: ExportArtifactResult;\n /** The absolute paths to the failures CSV artifacts */\n failuresCsv?: ExportArtifactResult;\n};\n\n/**\n * Return export statuses\n *\n * @param logDir - Log directory\n * @returns Export map\n */\nexport function buildExportStatus(logDir: string): ExportStatusMap {\n return {\n error: { path: join(logDir, 'combined-errors.log') },\n warn: { path: join(logDir, 'combined-warns.log') },\n info: { path: join(logDir, 'combined-info.log') },\n all: { path: join(logDir, 'combined-all.log') },\n failuresCsv: { path: join(logDir, 'failing-updates.csv') },\n };\n}\n","import { fork, type ChildProcess } from 'node:child_process';\nimport { join } from 'node:path';\nimport { createWriteStream } from 'node:fs';\nimport { openLogTailWindowMulti } from './openTerminal';\nimport { ensureLogFile } from './ensureLogFile';\nimport { classifyLogLevel, makeLineSplitter } from './logRotation';\n\n/** Default child-flag used if a caller doesn’t provide one. */\nexport const CHILD_FLAG = '--as-child';\n\n// Symbol key so we can stash/retrieve paths on the child proc safely\nconst LOG_PATHS_SYM: unique symbol = Symbol('workerLogPaths');\n\nexport interface WorkerLogPaths {\n /** Structured (app-controlled) log file path written via WORKER_LOG */\n structuredPath: string;\n /** Raw stdout capture */\n outPath: string;\n /** Raw stderr capture */\n errPath: string;\n /** Lines classified as INFO (primarily stdout) */\n infoPath: string;\n /** Lines classified as WARN (from stderr without error tokens) */\n warnPath: string;\n /** Lines classified as ERROR (from stderr, including uncaught) */\n errorPath: string;\n}\n\n/** Convenience alias for the optional return from getWorkerLogPaths */\nexport type SlotPaths = Map<number, WorkerLogPaths | undefined>;\n\n/**\n * Retrieve the paths we stashed on the child.\n *\n * @param child - The worker ChildProcess instance.\n * @returns The log paths or undefined if not set.\n */\nexport function getWorkerLogPaths(\n child: ChildProcess,\n): WorkerLogPaths | undefined {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (child as any)[LOG_PATHS_SYM] as WorkerLogPaths | undefined;\n}\n\n/**\n * Is IPC channel still open? (Node doesn't type `.channel`)\n *\n * @param w - The worker ChildProcess instance.\n * @returns True if the IPC channel is open, false otherwise.\n */\nexport function isIpcOpen(w: ChildProcess | undefined | null): boolean {\n const ch = w && w.channel;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return !!(w && w.connected && ch && !(ch as any).destroyed);\n}\n\n/**\n * Safely send a message to the worker process.\n *\n * @param w - The worker ChildProcess instance.\n * @param msg - The message to send.\n * @returns True if the message was sent successfully, false otherwise.\n */\nexport function safeSend(w: ChildProcess, msg: unknown): boolean {\n if (!isIpcOpen(w)) return false;\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n w.send?.(msg as any);\n return true;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (err: any) {\n if (\n err?.code === 'ERR_IPC_CHANNEL_CLOSED' ||\n err?.code === 'EPIPE' ||\n err?.errno === -32\n ) {\n return false;\n }\n throw err;\n }\n}\n\nexport interface SpawnWorkerOptions {\n /** Worker slot/index */\n id: number;\n /** Absolute path to the module to fork (should handle CHILD_FLAG) */\n modulePath: string;\n /** Directory where log files will be written */\n logDir: string;\n /** If true, open tail windows for the log files */\n openLogWindows: boolean;\n /** If true, spawn with silent stdio (respect your existing setting) */\n isSilent: boolean;\n /** Optional override for the child flag (defaults to CHILD_FLAG) */\n childFlag?: string;\n}\n\n/**\n * Spawn a worker process with piped stdio and persisted logs.\n *\n * Files produced per worker:\n * - worker-{id}.log (structured WORKER_LOG written by the child)\n * - worker-{id}.out.log (raw stdout)\n * - worker-{id}.err.log (raw stderr)\n * - worker-{id}.info.log (classified INFO lines from stdout)\n * - worker-{id}.warn.log (classified WARN lines from stderr)\n * - worker-{id}.error.log (classified ERROR lines from stderr)\n *\n * @param opts - Options for spawning the worker process.\n * @returns The spawned ChildProcess instance.\n */\nexport function spawnWorkerProcess(opts: SpawnWorkerOptions): ChildProcess {\n const {\n id,\n modulePath,\n logDir,\n openLogWindows,\n isSilent,\n childFlag = CHILD_FLAG,\n } = opts;\n\n const structuredPath = join(logDir, `worker-${id}.log`);\n const outPath = join(logDir, `worker-${id}.out.log`);\n const errPath = join(logDir, `worker-${id}.err.log`);\n const infoPath = join(logDir, `worker-${id}.info.log`);\n const warnPath = join(logDir, `worker-${id}.warn.log`);\n const errorPath = join(logDir, `worker-${id}.error.log`);\n\n [structuredPath, outPath, errPath, infoPath, warnPath, errorPath].forEach(\n ensureLogFile,\n );\n\n const child = fork(modulePath, [childFlag], {\n stdio: ['pipe', 'pipe', 'pipe', 'ipc'],\n env: { ...process.env, WORKER_ID: String(id), WORKER_LOG: structuredPath },\n execArgv: process.execArgv,\n silent: isSilent,\n });\n\n // Raw capture streams\n const outStream = createWriteStream(outPath, { flags: 'a' });\n const errStream = createWriteStream(errPath, { flags: 'a' });\n\n // Classified streams\n const infoStream = createWriteStream(infoPath, { flags: 'a' });\n const warnStream = createWriteStream(warnPath, { flags: 'a' });\n const errorStream = createWriteStream(errorPath, { flags: 'a' });\n\n // Pipe raw streams\n child.stdout?.pipe(outStream);\n child.stderr?.pipe(errStream);\n\n // Headers so tail windows show something immediately\n const hdr = (name: string): string =>\n `[parent] ${name} capture active for w${id} (pid ${child.pid})\\n`;\n outStream.write(hdr('stdout'));\n errStream.write(hdr('stderr'));\n infoStream.write(hdr('info'));\n warnStream.write(hdr('warn'));\n errorStream.write(hdr('error'));\n\n // Classified INFO from stdout (line-buffered)\n if (child.stdout) {\n const onOutLine = makeLineSplitter((line) => {\n if (!line) return;\n try {\n // Treat all stdout lines as INFO for the classified stream\n infoStream.write(`${line}\\n`);\n } catch {\n /* ignore */\n }\n });\n child.stdout.on('data', onOutLine);\n }\n\n // Classified WARN/ERROR from stderr (line-buffered)\n if (child.stderr) {\n const onErrLine = makeLineSplitter((line) => {\n if (!line) return;\n const lvl = classifyLogLevel(line); // 'warn' | 'error' | null\n try {\n if (lvl === 'error') {\n errorStream.write(`${line}\\n`);\n } else {\n // Treat untagged stderr as WARN by default (common in libs)\n warnStream.write(`${line}\\n`);\n }\n } catch {\n /* ignore */\n }\n });\n child.stderr.on('data', onErrLine);\n }\n\n // Stash log path metadata on the child\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (child as any)[LOG_PATHS_SYM] = {\n structuredPath,\n outPath,\n errPath,\n infoPath,\n warnPath,\n errorPath,\n } as WorkerLogPaths;\n\n if (openLogWindows) {\n openLogTailWindowMulti(\n [structuredPath, outPath, errPath, infoPath, warnPath, errorPath],\n `worker-${id}`,\n isSilent,\n );\n }\n\n // Best-effort error suppression on file streams\n outStream.on('error', () => {\n /* ignore */\n });\n errStream.on('error', () => {\n /* ignore */\n });\n infoStream.on('error', () => {\n /* ignore */\n });\n warnStream.on('error', () => {\n /* ignore */\n });\n errorStream.on('error', () => {\n /* ignore */\n });\n\n return child;\n}\n","/* eslint-disable no-continue, no-control-regex */\nimport { readFileSync } from 'node:fs';\nimport type { WorkerLogPaths } from './spawnWorkerProcess';\n\n/**\n * Log locations\n */\nexport type LogLocation = 'out' | 'err' | 'structured' | 'warn' | 'info';\n\n/**\n * Which logs to show in the combined output.\n * Can include 'out' (stdout), 'err' (stderr), 'structured' (\n */\nexport type WhichLogs = Array<LogLocation>;\n\n/**\n * Show combined logs from all worker processes.\n *\n * @param slotLogPaths - Map of worker IDs to their log file paths.\n * @param whichList - one or more sources to include (e.g., ['err','out'])\n * @param filterLevel - 'error', 'warn', or 'all' to filter log levels.\n */\nexport function showCombinedLogs(\n slotLogPaths: Map<number, WorkerLogPaths | undefined>,\n whichList: WhichLogs,\n filterLevel: 'error' | 'warn' | 'all',\n): void {\n process.stdout.write('\\x1b[2J\\x1b[H');\n\n const isError = (t: string): boolean =>\n /\\b(ERROR|uncaughtException|unhandledRejection)\\b/i.test(t);\n const isWarnTag = (t: string): boolean => /\\b(WARN|WARNING)\\b/i.test(t);\n\n const lines: string[] = [];\n\n for (const [, paths] of slotLogPaths) {\n if (!paths) continue;\n\n const files: Array<{\n /** Absolute file path to read from */\n path: string;\n /** Source type for this file, used for classification */\n src: LogLocation;\n }> = [];\n for (const which of whichList) {\n if (which === 'out' && paths.outPath) {\n files.push({ path: paths.outPath, src: 'out' });\n }\n if (which === 'err' && paths.errPath) {\n files.push({ path: paths.errPath, src: 'err' });\n }\n if (which === 'structured' && paths.structuredPath) {\n files.push({ path: paths.structuredPath, src: 'structured' });\n }\n if (paths.warnPath && which === 'warn') {\n files.push({ path: paths.warnPath, src: 'warn' });\n }\n if (paths.infoPath && which === 'info') {\n files.push({ path: paths.infoPath, src: 'info' });\n }\n }\n\n for (const { path, src } of files) {\n let text = '';\n try {\n text = readFileSync(path, 'utf8');\n } catch {\n continue;\n }\n\n for (const ln of text.split('\\n')) {\n if (!ln) continue;\n\n const clean = ln.replace(/\\x1B\\[[0-9;]*m/g, '');\n\n if (filterLevel === 'all') {\n lines.push(ln);\n continue;\n }\n\n if (filterLevel === 'error') {\n if (isError(clean)) lines.push(ln);\n continue;\n }\n\n // filterLevel === 'warn'\n // Accept:\n // - explicit WARN tag anywhere\n // - OR lines from stderr that are NOT explicit errors (many warn libs print to stderr)\n // - OR lines containing the word \"warning\" (common in some libs)\n if (isWarnTag(clean) || (src === 'err' && !isError(clean))) {\n lines.push(ln);\n continue;\n }\n }\n }\n }\n\n // simple time-sort; each worker often prefixes ISO timestamps\n lines.sort((a, b) => {\n const ta = a.match(/\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}/)?.[0] ?? '';\n const tb = b.match(/\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}/)?.[0] ?? '';\n return ta.localeCompare(tb);\n });\n\n process.stdout.write(`${lines.join('\\n')}\\n`);\n process.stdout.write('\\nPress Esc/Ctrl+] to return to dashboard.\\n');\n}\n/* eslint-enable no-continue, no-control-regex */\n","import type * as readline from 'node:readline';\n\n/**\n * Map a key press to an action in the interactive dashboard.\n */\nexport type Action =\n | {\n /** Indicates attaching to a session by id. */\n type: 'ATTACH';\n /** The id of the session to attach to. */\n id: number;\n }\n | {\n /** Indicates cycling through sessions. */\n type: 'CYCLE';\n /** The direction to cycle: +1 for next, -1 for previous. */\n delta: number;\n }\n | {\n /** Indicates detaching from the current session. */\n type: 'DETACH';\n }\n | {\n /** Indicates the Ctrl+C key combination was pressed. */\n type: 'CTRL_C';\n }\n | {\n /** Indicates the Ctrl+D key combination was pressed. */\n type: 'CTRL_D';\n }\n | {\n /** Indicates quitting the dashboard. */\n type: 'QUIT';\n }\n | {\n /** Forwards an unhandled key sequence. */\n type: 'FORWARD';\n /** The key sequence to forward. */\n sequence: string;\n };\n\n/**\n * Map a key press to an action in the interactive dashboard.\n *\n * @param str - The string representation of the key press.\n * @param key - The key object containing details about the key press.\n * @param mode - The current mode of the dashboard, either 'dashboard' or 'attached'.\n * @returns An Action object representing the mapped action, or null if no action is mapped.\n */\nexport function keymap(\n str: string,\n key: readline.Key,\n mode: 'dashboard' | 'attached',\n): Action | null {\n if (key.ctrl && key.name === 'c') return { type: 'CTRL_C' };\n\n if (mode === 'dashboard') {\n if (key.name && /^[0-9]$/.test(key.name)) {\n return { type: 'ATTACH', id: Number(key.name) };\n }\n if (key.name === 'tab' && !key.shift) return { type: 'CYCLE', delta: +1 };\n if (key.name === 'tab' && key.shift) return { type: 'CYCLE', delta: -1 };\n if (key.name === 'q') return { type: 'QUIT' };\n return null;\n }\n\n // attached\n if (key.name === 'escape' || (key.ctrl && key.name === ']')) {\n return { type: 'DETACH' };\n }\n if (key.ctrl && key.name === 'd') return { type: 'CTRL_D' };\n\n const sequence = key.sequence ?? str ?? '';\n return sequence ? { type: 'FORWARD', sequence } : null;\n}\n","import * as readline from 'node:readline';\nimport type { ChildProcess } from 'node:child_process';\nimport type { WorkerLogPaths } from './spawnWorkerProcess';\nimport { replayFileTailToStdout } from './replayFileTailToStdout';\nimport { keymap } from './keymap';\nimport { cycleWorkers, getWorkerIds } from './workerIds';\nimport type { WhichLogs } from './showCombinedLogs';\nimport { DEBUG } from '../../constants';\n\n/**\n * Key action types for the interactive switcher\n */\nexport type InteractiveDashboardMode = 'dashboard' | 'attached';\n\nexport interface SwitcherPorts {\n /** Standard input stream */\n stdin: NodeJS.ReadStream;\n /** Standard output stream */\n stdout: NodeJS.WriteStream;\n /** Standard error stream */\n stderr: NodeJS.WriteStream;\n}\n\n/**\n * Install an interactive switcher for managing worker processes.\n *\n * @param opts - Options for the switcher\n * @returns A cleanup function to remove the switcher\n */\nexport function installInteractiveSwitcher(opts: {\n /** Registry of live workers by id */\n workers: Map<number, ChildProcess>;\n /** Hooks */\n onAttach?: (id: number) => void;\n /** Optional detach handler */\n onDetach?: () => void;\n /** Optional Ctrl+C handler for parent graceful shutdown in dashboard */\n onCtrlC?: () => void; // parent graceful shutdown in dashboard\n /** Provide log paths so we can replay the tail on attach */\n getLogPaths?: (id: number) => WorkerLogPaths | undefined;\n /** How many bytes to replay from the end of each file (default 200 KB) */\n replayBytes?: number;\n /** Which logs to replay first (default ['out','err']) */\n replayWhich?: WhichLogs;\n /** Print a small banner/clear screen before replaying (optional) */\n onEnterAttachScreen?: (id: number) => void;\n /** Optional stdio ports for testing; defaults to process stdio */\n ports?: SwitcherPorts;\n}): () => void {\n const {\n workers,\n onAttach,\n onDetach,\n onCtrlC,\n getLogPaths,\n replayBytes = 200 * 1024,\n replayWhich = ['out', 'err'],\n onEnterAttachScreen,\n ports,\n } = opts;\n\n const stdin = ports?.stdin ?? process.stdin;\n const stdout = ports?.stdout ?? process.stdout;\n const stderr = ports?.stderr ?? process.stderr;\n\n const d = (...a: unknown[]): void => {\n if (DEBUG) {\n try {\n (ports?.stderr ?? process.stderr).write(\n `[keys] ${a.map(String).join(' ')}\\n`,\n );\n } catch {\n // noop\n }\n }\n };\n\n if (!stdin.isTTY) {\n // Not a TTY; return a no-op cleanup\n return () => {\n // noop\n };\n }\n\n readline.emitKeypressEvents(stdin);\n stdin.setRawMode?.(true);\n\n let mode: InteractiveDashboardMode = 'dashboard';\n let focus: number | null = null;\n\n // live mirroring handlers while attached\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let outHandler: ((chunk: any) => void) | null = null;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let errHandler: ((chunk: any) => void) | null = null;\n\n /**\n * Cycle through worker IDs, wrapping around.\n *\n * @param id - The current worker ID to start cycling from.\n * @returns The next worker ID after cycling, or null if no workers are available.\n */\n async function replayLogs(id: number): Promise<void> {\n if (!getLogPaths) return;\n const paths = getLogPaths(id);\n if (!paths) return;\n\n const toReplay: string[] = [];\n for (const which of replayWhich) {\n if (which === 'out') toReplay.push(paths.outPath);\n if (which === 'err') toReplay.push(paths.errPath);\n if (which === 'structured') toReplay.push(paths.structuredPath);\n }\n\n if (toReplay.length) {\n stdout.write('\\n------------ replay ------------\\n');\n for (const p of toReplay) {\n stdout.write(\n `\\n--- ${p} (last ~${Math.floor(replayBytes / 1024)}KB) ---\\n`,\n );\n await replayFileTailToStdout(p, replayBytes, (s) => stdout.write(s));\n }\n stdout.write('\\n--------------------------------\\n\\n');\n }\n }\n\n const attach = async (id: number): Promise<void> => {\n d('attach()', `id=${id}`); // at function entry\n\n const w = workers.get(id);\n if (!w) return;\n\n // Detach any previous focus\n if (mode === 'attached') detach();\n\n mode = 'attached';\n focus = id;\n\n // UX: clear + banner\n onEnterAttachScreen?.(id);\n\n onAttach?.(id); // prints “Attached to worker …” and clears\n await replayLogs(id); // now the tail stays visible\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n outHandler = (chunk: any) => stdout.write(chunk);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n errHandler = (chunk: any) => stderr.write(chunk);\n w.stdout?.on('data', outHandler);\n w.stderr?.on('data', errHandler);\n\n // auto-detach if child exits\n const onExit = (): void => {\n if (focus === id) detach();\n };\n w.once('exit', onExit);\n };\n\n const detach = (): void => {\n d('detach()', `id=${focus}`); // at function entry\n\n if (focus == null) return;\n const id = focus;\n const w = workers.get(id);\n if (w) {\n if (outHandler) w.stdout?.off('data', outHandler);\n if (errHandler) w.stderr?.off('data', errHandler);\n }\n outHandler = null;\n errHandler = null;\n focus = null;\n mode = 'dashboard';\n onDetach?.();\n };\n\n const onKey = (str: string, key: readline.Key): void => {\n d(\n 'keypress',\n JSON.stringify({\n str,\n name: key.name,\n seq: key.sequence,\n ctrl: key.ctrl,\n meta: key.meta,\n shift: key.shift,\n mode,\n }),\n );\n const act = keymap(str, key, mode);\n d('mapped', JSON.stringify(act));\n\n if (!act) return;\n\n // eslint-disable-next-line default-case\n switch (act.type) {\n case 'CTRL_C': {\n d('CTRL_C');\n if (mode === 'attached' && focus != null) {\n const w = workers.get(focus);\n try {\n w?.kill('SIGINT');\n } catch {\n // noop\n }\n // optional: auto-detach so second Ctrl+C exits parent\n detach();\n return;\n }\n onCtrlC?.();\n return;\n }\n\n case 'ATTACH': {\n d('ATTACH', `id=${act.id}`, `has=${workers.has(act.id)}`);\n\n if (mode !== 'dashboard') return;\n // eslint-disable-next-line no-void\n if (workers.has(act.id)) void attach(act.id);\n return;\n }\n\n case 'CYCLE': {\n d('CYCLE', `delta=${act.delta}`);\n if (mode !== 'dashboard') return;\n const next = cycleWorkers(getWorkerIds(workers), focus, act.delta);\n // eslint-disable-next-line no-void\n if (next != null) void attach(next);\n return;\n }\n\n case 'QUIT': {\n if (mode !== 'dashboard') return;\n onCtrlC?.();\n return;\n }\n\n case 'DETACH': {\n d('DETACH');\n if (mode === 'attached') detach();\n return;\n }\n\n case 'CTRL_D': {\n if (mode === 'attached' && focus != null) {\n const w = workers.get(focus);\n try {\n w?.stdin?.end();\n } catch {\n // noop\n }\n }\n return;\n }\n\n case 'FORWARD': {\n if (mode === 'attached' && focus != null) {\n const w = workers.get(focus);\n try {\n w?.stdin?.write(act.sequence);\n } catch {\n // noop\n }\n }\n }\n }\n };\n\n // Raw bytes fallback (usually not hit because keypress handles it)\n const onData = (chunk: Buffer): void => {\n if (mode === 'attached' && focus != null) {\n const w = workers.get(focus);\n try {\n w?.stdin?.write(chunk);\n } catch {\n // noop\n }\n }\n };\n\n const cleanup = (): void => {\n stdin.off('keypress', onKey);\n stdin.off('data', onData);\n stdin.setRawMode?.(false);\n stdout.write('\\x1b[?25h');\n };\n\n stdin.on('keypress', onKey);\n stdin.on('data', onData);\n\n return cleanup;\n}\n","// lib/ui/dashboardPlugin.ts\nimport * as readline from 'node:readline';\nimport colors from 'colors';\nimport type { SlotState } from './types';\nimport type { ObjByString } from '@transcend-io/type-utils';\n\n/**\n * A dashboard plugin defines how to render the worker pool UI.\n * Commands can supply a plugin to customize:\n * - The header block (summary stats, title, etc.)\n * - Per-worker rows (one line per worker slot)\n * - Optional extras (artifact exports, breakdowns, footers)\n *\n * @template TTotals - The shape of the aggregate totals object maintained by the command.\n */\nexport interface DashboardPlugin<TTotals, TSlotState extends ObjByString> {\n /**\n * Render the header block of the dashboard.\n *\n * @param ctx - Context with pool/worker state, totals, and metadata.\n * @returns An array of strings, each representing one line in the header.\n */\n renderHeader: (ctx: CommonCtx<TTotals, TSlotState>) => string[];\n\n /**\n * Render per-worker rows, usually one line per worker slot.\n *\n * @param ctx - Context with pool/worker state, totals, and metadata.\n * @returns An array of strings, each representing one row in the workers section.\n */\n renderWorkers: (ctx: CommonCtx<TTotals, TSlotState>) => string[];\n\n /**\n * Render any optional extra blocks that appear after the worker rows.\n * Useful for printing export paths, aggregated metrics, breakdowns, etc.\n *\n * @param ctx - Context with pool/worker state, totals, and metadata.\n * @returns An array of strings, each representing one additional line.\n */\n renderExtras?: (ctx: CommonCtx<TTotals, TSlotState>) => string[];\n}\n\n/**\n * Shared context object passed into all render methods of a {@link DashboardPlugin}.\n *\n * @template TTotals - The shape of the aggregate totals object maintained by the command.\n */\nexport type CommonCtx<TTotals, TSlotState extends ObjByString> = {\n /** Human-readable title for the dashboard (e.g., \"Parallel uploader\"). */\n title: string;\n\n /** Number of worker processes spawned in the pool. */\n poolSize: number;\n\n /** Logical CPU count, included for informational display. */\n cpuCount: number;\n\n /** Total number of \"files\" or logical units the command expects to process. */\n filesTotal: number;\n\n /** Count of successfully completed files/tasks. */\n filesCompleted: number;\n\n /** Count of failed files/tasks. */\n filesFailed: number;\n\n /**\n * State of each worker slot, keyed by worker id.\n * Includes busy flag, file label, start time, last log badge, and progress.\n */\n workerState: Map<number, SlotState<TSlotState>>;\n\n /**\n * Aggregate totals maintained by the command’s hook logic.\n * Domain-specific metrics (e.g., rows uploaded, bytes processed) can be surfaced here.\n */\n totals: TTotals;\n\n /**\n * Throughput metrics tracked by the runner:\n * - successSoFar: convenience alias for completed count\n * - r10s: completions/sec averaged over last 10s\n * - r60s: completions/sec averaged over last 60s\n */\n throughput: {\n /** Cumulative count of successful completions so far. */\n successSoFar: number;\n /** Recent throughput rate over the last 10 seconds. */\n r10s: number;\n /** Recent throughput rate over the last 60 seconds. */\n r60s: number;\n };\n\n /** True when the pool has fully drained and all workers have exited. */\n final: boolean;\n\n /**\n * Optional export status payload provided by the command.\n * Useful for rendering artifact paths or \"latest export\" summaries.\n */\n exportStatus?: Record<string, unknown>;\n};\n\n/** The most recently rendered frame, cached to suppress flicker from duplicate renders. */\nlet lastFrame = '';\n\n/**\n * Generate the hotkeys hint string that appears at the bottom of the dashboard.\n *\n * @param poolSize - The number of worker slots in the pool.\n * @param final - Whether the run has completed.\n * @returns A dimmed string listing the supported hotkeys for attach/detach/quit.\n */\nexport const hotkeysHint = (poolSize: number, final: boolean): string => {\n const maxDigit = Math.min(poolSize - 1, 9);\n const digitRange = poolSize <= 1 ? '0' : `0-${maxDigit}`;\n const extra = poolSize > 10 ? ' (Tab/Shift+Tab for ≥10)' : '';\n return final\n ? colors.dim(\n 'Run complete — digits to view logs • Tab/Shift+Tab cycle • Esc/Ctrl+] detach • q to quit',\n )\n : colors.dim(\n `Hotkeys: [${digitRange}] attach${extra} • e=errors • w=warnings • i=info • l=logs • ` +\n 'Tab/Shift+Tab • Esc/Ctrl+] detach • Ctrl+C exit',\n );\n};\n\n/**\n * Render the dashboard using a supplied {@link DashboardPlugin}.\n *\n * The frame is composed of:\n * - Header lines\n * - A blank separator\n * - Worker rows\n * - A blank separator\n * - Hotkeys hint\n * - Optional extras (if plugin supplies them)\n *\n * Optimizations:\n * - Suppresses re-renders if the frame is identical to the previous frame (flicker-free).\n * - Hides the terminal cursor during live updates, restoring it when final.\n *\n * @param ctx - Shared context containing pool state, worker state, totals, throughput, etc.\n * @param plugin - The plugin that defines how to render the header, workers, and optional extras.\n * @param viewerMode - If true, renders in viewer mode (no ability to switch between files).\n */\nexport function dashboardPlugin<TTotals, TSlotState extends ObjByString>(\n ctx: CommonCtx<TTotals, TSlotState>,\n plugin: DashboardPlugin<TTotals, TSlotState>,\n viewerMode = false,\n): void {\n const frame = [\n ...plugin.renderHeader(ctx),\n '',\n ...plugin.renderWorkers(ctx),\n ...(viewerMode ? [] : ['', hotkeysHint(ctx.poolSize, ctx.final)]),\n ...(plugin.renderExtras ? [''].concat(plugin.renderExtras(ctx)) : []),\n ].join('\\n');\n\n // Skip duplicate renders during live runs to avoid flicker.\n if (!ctx.final && frame === lastFrame) return;\n lastFrame = frame;\n\n if (!ctx.final) {\n // Hide cursor and repaint in place\n process.stdout.write('\\x1b[?25l');\n readline.cursorTo(process.stdout, 0, 0);\n readline.clearScreenDown(process.stdout);\n } else {\n // Restore cursor on final render\n process.stdout.write('\\x1b[?25h');\n }\n process.stdout.write(`${frame}\\n`);\n}\n","import colors from 'colors';\nimport { basename } from 'node:path';\nimport type { CommonCtx } from './dashboardPlugin';\nimport type { ObjByString } from '@transcend-io/type-utils';\n\n/**\n * Progress snapshot for a worker slot in the chunk-csv command.\n */\nexport type ChunkSlotProgress = {\n /** Absolute path of the file being processed by this worker. */\n filePath?: string;\n /** Number of rows processed so far in this file. */\n processed?: number;\n /** Optional total number of rows in the file (if known). */\n total?: number;\n};\n\n/**\n * Format a number safely for display.\n *\n * @param n - The number to format (or `undefined`).\n * @returns A localized string representation, or \"0\".\n */\nexport function fmtNum(n: number | undefined): string {\n return typeof n === 'number' ? n.toLocaleString() : '0';\n}\n\n/**\n * Draw a horizontal bar of length `width` filled to `pct` percent.\n *\n * @param pct - Percentage 0..100.\n * @param width - Number of characters in the bar.\n * @returns A string like \"████░░░░\".\n */\nexport function pctBar(pct: number, width = 40): string {\n const clamped = Math.max(0, Math.min(100, Math.floor(pct)));\n const filled = Math.floor((clamped / 100) * width);\n return '█'.repeat(filled) + '░'.repeat(width - filled);\n}\n\n/**\n * Compute pool-wide progress values needed by headers.\n *\n * @param ctx - Dashboard context containing pool state, worker state, totals, etc.\n * @returns An object with `done`, `inProgress`, and `pct` properties.\n */\nexport function poolProgress<TTotals, TSlot extends ObjByString>(\n ctx: CommonCtx<TTotals, TSlot>,\n): {\n /** Count of successfully completed files/tasks. */\n done: number;\n /** Count of currently in-progress files/tasks. */\n inProgress: number;\n /** Percentage of completion (0-100). */\n pct: number;\n} {\n const inProgress = [...ctx.workerState.values()].filter((s) => s.busy).length;\n const done = ctx.filesCompleted + ctx.filesFailed;\n const pct =\n ctx.filesTotal === 0\n ? 100\n : Math.floor((done / Math.max(1, ctx.filesTotal)) * 100);\n return { done, inProgress, pct };\n}\n\n/**\n * Compose the common header lines (title, pool stats, progress bar, throughput).\n *\n * @param ctx - Dashboard context.\n * @param extraLines - Optional extra lines (e.g., totals block).\n * @returns Header lines.\n */\nexport function makeHeader<TTotals, TSlot extends ObjByString>(\n ctx: CommonCtx<TTotals, TSlot>,\n extraLines: string[] = [],\n): string[] {\n const {\n title,\n poolSize,\n cpuCount,\n filesTotal,\n filesCompleted,\n filesFailed,\n throughput,\n } = ctx;\n const { inProgress, pct } = poolProgress(ctx);\n\n const lines: string[] = [\n `${colors.bold(title)} — ${poolSize} workers ${colors.dim(\n `(CPU avail: ${cpuCount})`,\n )}`,\n `${colors.dim('Files')} ${fmtNum(filesTotal)} ${colors.dim(\n 'Completed',\n )} ${fmtNum(filesCompleted)} ${colors.dim('Failed')} ${\n filesFailed ? colors.red(fmtNum(filesFailed)) : fmtNum(filesFailed)\n } ${colors.dim('In-flight')} ${fmtNum(inProgress)}`,\n `[${pctBar(pct)}] ${pct}%`,\n ];\n\n if (throughput) {\n const perHour10 = Math.round(throughput.r10s * 3600).toLocaleString();\n const perHour60 = Math.round(throughput.r60s * 3600).toLocaleString();\n const suffix =\n ctx.throughput?.successSoFar != null\n ? ` Newly uploaded: ${fmtNum(ctx.throughput.successSoFar)}`\n : '';\n lines.push(\n colors.cyan(`Throughput: ${perHour10}/hr (1h: ${perHour60}/hr)${suffix}`),\n );\n }\n\n return extraLines.length ? lines.concat(extraLines) : lines;\n}\n\n/**\n * Render per-worker rows with a compact progress bar and status badge.\n *\n * @param ctx - Dashboard context (slot progress type must have processed/total?).\n * @param getFileLabel - Optional: override how the filename is shown.\n * @returns Array of strings, each representing one worker row.\n */\nexport function makeWorkerRows<\n TTotals,\n TSlot extends Omit<ChunkSlotProgress, 'filePath'>,\n>(\n ctx: CommonCtx<TTotals, TSlot>,\n getFileLabel: (file: string | null | undefined) => string = (file) =>\n file ? basename(file) : '-',\n): string[] {\n const miniWidth = 18;\n\n return [...ctx.workerState.entries()].map(([id, s]) => {\n const badge =\n s.lastLevel === 'error'\n ? colors.red('ERROR ')\n : s.lastLevel === 'warn'\n ? colors.yellow('WARN ')\n : s.busy\n ? colors.green('WORKING')\n : colors.dim('IDLE ');\n\n const fname = getFileLabel(s.file);\n const elapsed = s.startedAt\n ? `${Math.floor((Date.now() - s.startedAt) / 1000)}s`\n : '-';\n\n const processed = s.progress?.processed ?? 0;\n const total = s.progress?.total ?? 0;\n const pctw = total > 0 ? Math.floor((processed / total) * 100) : 0;\n const mini = total > 0 ? pctBar(pctw, miniWidth) : ' '.repeat(miniWidth);\n const miniTxt =\n total > 0\n ? `${processed.toLocaleString()}/${total.toLocaleString()} (${pctw}%)`\n : colors.dim('—');\n\n return ` [w${id}] ${badge} | ${fname} | ${elapsed} | [${mini}] ${miniTxt}`;\n });\n}\n","/* eslint-disable max-lines */\nimport colors from 'colors';\nimport type { ChildProcess } from 'node:child_process';\nimport { RateCounter } from '../helpers';\nimport type { SlotState, FromWorker, ToWorker } from './types';\nimport {\n getWorkerLogPaths,\n isIpcOpen,\n safeSend,\n spawnWorkerProcess,\n type WorkerLogPaths,\n} from './spawnWorkerProcess';\nimport { classifyLogLevel, initLogDir, makeLineSplitter } from './logRotation';\nimport { safeGetLogPathsForSlot } from './safeGetLogPathsForSlot';\nimport { installInteractiveSwitcher } from './installInteractiveSwitcher';\nimport type { ObjByString } from '@transcend-io/type-utils';\n\n/**\n * Callbacks used by the generic pool orchestrator to:\n * - fetch tasks,\n * - format labels for UI,\n * - fold progress and results into aggregate totals,\n * - run optional post-processing once the pool completes.\n *\n * Each command supplies concrete `TTask`, `TProg`, `TRes`, and optionally a\n * custom totals type `TTotals`.\n */\nexport interface PoolHooks<\n TTask extends ObjByString,\n TProg extends ObjByString,\n TRes extends ObjByString,\n TTotals = unknown,\n> {\n /**\n * Produce the next work item for a slot.\n *\n * @returns The next task or `undefined` if no tasks remain.\n */\n nextTask: () => TTask | undefined;\n\n /**\n * Human-readable label for a task, shown in dashboards.\n *\n * @param t - The task to label.\n * @returns A short descriptor, typically a file path or identifier.\n */\n taskLabel: (t: TTask) => string;\n\n /**\n * Fold an incoming progress payload into aggregate totals.\n * Should be pure (no side effects) and return the new totals object.\n *\n * @param prevTotals - The previous totals value.\n * @param prog - The latest progress payload from a worker.\n * @returns Updated totals.\n */\n onProgress: (prevTotals: TTotals, prog: TProg) => TTotals;\n\n /**\n * Handle a final result from a worker.\n * Should be pure and return the new totals plus a boolean indicating if the\n * unit succeeded (used to set per-slot level/metrics).\n *\n * @param prevTotals - The previous totals value.\n * @param res - The result payload from a worker.\n * @returns Object containing updated totals and success flag.\n */\n onResult: (\n prevTotals: TTotals,\n res: TRes,\n ) => {\n /** Updated totals after processing this result */\n totals: TTotals;\n /** Whether the task was successful */\n ok: boolean;\n };\n\n /**\n * Initialize per-slot progress state when a task is assigned.\n * Useful when you want a non-undefined `progress` immediately.\n *\n * @param t - The task to be started in this slot.\n * @returns Initial progress state or `undefined`.\n */\n initSlotProgress?: (t: TTask) => TProg | undefined;\n\n /**\n * Produce the initial totals value for the pool (defaults to `{}`).\n *\n * @returns A new totals object.\n */\n initTotals?: () => TTotals;\n\n /**\n * Provide an export status map for dashboards (optional).\n *\n * @returns A status object or `undefined` if not applicable.\n */\n exportStatus?: () => Record<string, unknown> | undefined;\n\n /**\n * Optional post-processing step invoked after the pool finishes.\n * Common use: writing combined logs/artifacts once all workers complete.\n *\n * When {@link RunPoolOptions.viewerMode} is enabled, the runner also passes\n * the **log directory** and the **per-slot log file paths** so you can\n * replicate the legacy “viewer mode” auto-exports (combined logs, indices, etc.).\n */\n postProcess?: (ctx: {\n /** Live snapshot of all worker slots at completion. */\n slots: Map<number, SlotState<TProg>>;\n /** Final aggregate totals. */\n totals: TTotals;\n /** Absolute path to the pool’s log directory. */\n logDir: string;\n /**\n * Mapping of slot id -> log paths (stdout/stderr/current, rotations may exist).\n * Use this to collect and export artifacts after completion.\n */\n logsBySlot: Map<number, WorkerLogPaths | undefined>;\n /** Unix millis when the pool started (first worker spawned). */\n startedAt: number;\n /** Unix millis when the pool fully completed (after last worker exit). */\n finishedAt: number;\n /**\n * Helper to safely re-fetch a slot’s current log paths, accounting for respawns.\n * Mirrors the dashboard’s attach/switcher behavior.\n */\n getLogPathsForSlot: (id: number) => WorkerLogPaths | undefined;\n /** True if the pool was run in viewerMode (non-interactive). */\n viewerMode: boolean;\n }) => Promise<void> | void;\n}\n\n/**\n * Options to run a generic worker pool.\n *\n * @template TTask - The payload sent to each worker as a \"task\".\n * @template TProg - The progress payload emitted by workers.\n * @template TRes - The result payload emitted by workers.\n * @template TTotals - The aggregate totals object maintained by hooks.\n */\nexport interface RunPoolOptions<\n TTask extends ObjByString,\n TProg extends ObjByString,\n TRes extends ObjByString,\n TTotals extends ObjByString,\n> {\n /** Human-readable name for the pool, shown in headers (e.g., \"Parallel uploader\", \"Chunk CSV\"). */\n title: string;\n\n /**\n * Directory for pool-local state (logs, discovery messages, artifacts).\n * Usually the CLI's working directory for the command.\n */\n baseDir: string;\n\n /** Absolute path of the module the child should execute (the command impl that calls runChild when CHILD_FLAG is present). */\n childModulePath: string;\n\n /**\n * Number of worker processes to spawn. Typically derived via a helper like `computePoolSize`.\n */\n poolSize: number;\n\n /** Logical CPU count used for display only (not required to equal `poolSize`). */\n cpuCount: number;\n\n /**\n * Flag that the child module expects to see in `process.argv` to run in \"worker\" mode.\n * This MUST match the flag the worker module checks (e.g., `--as-child`).\n */\n childFlag: string;\n\n /**\n * Renderer function injected by the command. The runner calls this on each \"tick\"\n * and on significant state changes (progress, completion, attach/detach).\n */\n render: (input: {\n /** Header/title for the UI. */\n title: string;\n /** Configured pool size (number of workers). */\n poolSize: number;\n /** CPU count for informational display. */\n cpuCount: number;\n /** Total number of files/tasks anticipated by the command. */\n filesTotal: number;\n /** Number of files/tasks that have produced a successful result so far. */\n filesCompleted: number;\n /** Number of files/tasks that have produced a failed result so far. */\n filesFailed: number;\n /**\n * Per-slot state for each worker, including busy flag, file label, start time,\n * last log level badge, and optional progress payload.\n */\n workerState: Map<number, SlotState<TProg>>;\n /**\n * Arbitrary totals object maintained by hooks. This is the primary place to surface\n * domain-specific aggregate metrics in the UI.\n */\n totals: TTotals;\n /**\n * Smoothed throughput metrics computed by the runner:\n * - successSoFar: convenience mirror of completed count for the renderer\n * - r10s: moving average of completions per second over ~10 seconds\n * - r60s: moving average of completions per second over ~60 seconds\n */\n throughput: {\n /** Convenience mirror of `filesCompleted` for renderers that expect it in this block. */\n successSoFar: number;\n /** Moving average completions/sec (10s window). */\n r10s: number;\n /** Moving average completions/sec (60s window). */\n r60s: number;\n };\n /** True when the pool has fully drained and all workers have exited. */\n final: boolean;\n /**\n * Optional export status payload surfaced by hooks; used by commands that generate\n * multiple artifact files and want to show \"latest paths\" in the UI.\n */\n exportStatus?: Record<string, unknown>;\n }) => void;\n\n /**\n * Hook suite that adapts the pool to a specific command:\n * - nextTask(): TTask | undefined\n * - taskLabel(task): string\n * - initTotals?(): TTotals\n * - initSlotProgress?(task): TProg\n * - onProgress(totals, prog): TTotals\n * - onResult(totals, res): { totals: TTotals; ok: boolean }\n * - postProcess?({ slots, totals, logDir, logsBySlot, ... }): Promise<void> | void\n * - exportStatus?(): Record<string, unknown>\n */\n hooks: PoolHooks<TTask, TProg, TRes, TTotals>;\n\n /**\n * Total number of \"files\" or logical items the command expects to process.\n * Used purely for UI/ETA; does not affect scheduling.\n */\n filesTotal: number;\n\n /** Open worker logs in new terminals (macOS). Default true unless viewerMode=true. */\n openLogWindows?: boolean;\n\n /** Silence worker stdio (except logs). */\n isSilent?: boolean;\n\n /**\n * When true, run in “viewer mode” (non-interactive):\n * - Do NOT install the interactive attach/switcher.\n * - Default `openLogWindows` to false.\n * - Still render on a timer.\n * - Provide `logDir`/`logsBySlot` to `postProcess` for auto-exports.\n */\n viewerMode?: boolean;\n\n /**\n * Optional factory for additional key bindings (e.g., log viewers/exports).\n * Only used when viewerMode === false.\n */\n extraKeyHandler?: (args: {\n /** per-slot log paths (kept up-to-date across respawns) */\n logsBySlot: Map<number, WorkerLogPaths | undefined>;\n /** re-render dashboard now */\n repaint: () => void;\n /** pause/unpause dashboard repaint while showing viewers */\n setPaused: (p: boolean) => void;\n }) => (buf: Buffer) => void;\n}\n\n/**\n * Run a multi-process worker pool for a command.\n * The runner owns: spawning workers, assigning tasks, collecting progress/results,\n * basic log badging (WARN/ERROR), an interactive attach/switcher (unless viewerMode),\n * and a render loop.\n *\n * The command injects \"hooks\" to customize scheduling and totals aggregation.\n *\n * @param opts - Options\n */\nexport async function runPool<\n TTask extends ObjByString,\n TProg extends ObjByString,\n TRes extends ObjByString,\n TTotals extends ObjByString,\n>(opts: RunPoolOptions<TTask, TProg, TRes, TTotals>): Promise<void> {\n const {\n title,\n baseDir,\n poolSize,\n cpuCount,\n render,\n childModulePath,\n hooks,\n filesTotal,\n childFlag,\n viewerMode = false,\n } = opts;\n\n // Default behaviors may change under viewerMode.\n const openLogWindows = opts.openLogWindows ?? !viewerMode;\n const isSilent = opts.isSilent ?? true;\n\n const startedAt = Date.now();\n const logDir = initLogDir(baseDir);\n\n /** Live worker processes keyed by slot id. */\n const workers = new Map<number, ChildProcess>();\n /** Per-slot state tracked for the UI and scheduling. */\n const workerState = new Map<number, SlotState<TProg>>();\n /** File paths for each worker’s stdout/stderr logs. */\n const slotLogs = new Map<number, WorkerLogPaths | undefined>();\n /** Completion throughput meter. */ const meter = new RateCounter();\n const totalsInit = (hooks.initTotals?.() ?? {}) as TTotals;\n\n let totalsBox = totalsInit;\n let activeWorkers = 0;\n let completed = 0;\n let failed = 0;\n\n // Repaint ticker starts on first READY to avoid double-first-render.\n let ticker: NodeJS.Timeout | null = null;\n let firstReady = false;\n // Gate repaint during popup viewers/exports (driven by extraKeyHandler).\n let paused = false;\n // Keep a reference so we can unbind on exit.\n let extraHandler: ((buf: Buffer) => void) | null = null;\n\n /**\n * Paint the UI. The renderer is intentionally pure and receives\n * a snapshot of current state.\n *\n * @param final - If true, render the final state and exit.\n */\n const repaint = (final = false): void => {\n if (paused) return;\n render({\n title,\n poolSize,\n cpuCount,\n filesTotal,\n filesCompleted: completed,\n filesFailed: failed,\n workerState,\n totals: totalsBox,\n final,\n exportStatus: hooks.exportStatus?.(),\n throughput: {\n successSoFar: completed,\n r10s: meter.rate(10_000),\n r60s: meter.rate(60_000),\n },\n });\n };\n\n /**\n * Assign the next task to `id` if available.\n *\n * @param id - The worker slot id to assign a task to.\n * @returns true if a task was assigned.\n *\n * NOTE: This is the critical fix. We **do not** \"peek & put back\" a task.\n * We only consume via `nextTask()` inside this function.\n */\n const assign = (id: number): boolean => {\n const task = hooks.nextTask();\n if (!task) return false;\n\n const child = workers.get(id)!;\n const label = hooks.taskLabel(task);\n const initialProg = hooks.initSlotProgress?.(task);\n\n workerState.set(id, {\n busy: true,\n file: label,\n startedAt: Date.now(),\n lastLevel: 'ok',\n progress: initialProg,\n });\n\n safeSend(child, { type: 'task', payload: task } as ToWorker<TTask>);\n repaint();\n return true;\n };\n\n /* Spawn workers */\n for (let i = 0; i < poolSize; i += 1) {\n const child = spawnWorkerProcess({\n id: i,\n modulePath: childModulePath,\n logDir,\n openLogWindows,\n isSilent,\n childFlag,\n });\n workers.set(i, child);\n workerState.set(i, {\n busy: false,\n file: null,\n startedAt: null,\n lastLevel: 'ok',\n });\n slotLogs.set(i, getWorkerLogPaths(child));\n activeWorkers += 1;\n\n // badge WARN/ERROR quickly from stderr\n const errLine = makeLineSplitter((line) => {\n const lvl = classifyLogLevel(line);\n if (!lvl) return;\n const prev = workerState.get(i)!;\n if (prev.lastLevel !== lvl) {\n workerState.set(i, { ...prev, lastLevel: lvl });\n repaint();\n }\n });\n child.stderr?.on('data', errLine);\n\n // messages from the worker\n // eslint-disable-next-line no-loop-func\n child.on('message', (msg: FromWorker<TProg, TRes>) => {\n if (!msg || typeof msg !== 'object') return;\n\n if (msg.type === 'ready') {\n if (!firstReady) {\n firstReady = true;\n ticker = setInterval(() => repaint(false), 350);\n }\n assign(i); // try to start work immediately\n return;\n }\n\n if (msg.type === 'progress') {\n totalsBox = hooks.onProgress(totalsBox, msg.payload);\n const prev = workerState.get(i)!;\n workerState.set(i, { ...prev, progress: msg.payload });\n repaint();\n return;\n }\n\n if (msg.type === 'result') {\n const prev = workerState.get(i)!;\n const { totals: t2, ok } = hooks.onResult(totalsBox, msg.payload);\n totalsBox = t2;\n\n if (ok) {\n completed += 1;\n meter.add(1);\n } else {\n failed += 1;\n }\n\n workerState.set(i, {\n ...prev,\n busy: false,\n file: null,\n progress: undefined,\n lastLevel: ok ? 'ok' : 'error',\n });\n\n // Just try to assign; if none left, shut this child down.\n if (!assign(i) && isIpcOpen(child)) {\n safeSend(child, { type: 'shutdown' } as ToWorker<TTask>);\n }\n repaint();\n }\n });\n\n // eslint-disable-next-line no-loop-func\n child.on('exit', () => {\n activeWorkers -= 1;\n if (activeWorkers === 0) {\n if (ticker) clearInterval(ticker);\n repaint(true);\n }\n });\n }\n\n /* Interactive attach/switcher */\n let cleanupSwitcher: () => void = () => {\n /* noop */\n // no-op by default, overridden in non-viewerMode\n };\n\n const tearDownStdin = (): void => {\n try {\n process.stdin.setRawMode?.(false);\n } catch {\n /* noop */\n }\n try {\n process.stdin.pause();\n } catch {\n /* noop */\n }\n };\n\n const onSigint = (): void => {\n if (ticker) clearInterval(ticker);\n cleanupSwitcher?.();\n if (extraHandler) {\n try {\n process.stdin.off('data', extraHandler);\n } catch {\n /* noop */\n }\n }\n tearDownStdin();\n\n process.stdout.write('\\nStopping workers...\\n');\n for (const [, w] of workers) {\n if (isIpcOpen(w)) safeSend(w, { type: 'shutdown' } as ToWorker<TTask>);\n try {\n w?.kill('SIGTERM');\n } catch {\n /* noop */\n }\n }\n process.exit(130);\n };\n\n const onAttach = (id: number): void => {\n paused = true; // stop dashboard repaint while attached/viewing\n process.stdout.write('\\x1b[2J\\x1b[H'); // clear + home\n process.stdout.write(\n `Attached to worker ${id}. (Esc/Ctrl+] detach • Ctrl+D EOF • Ctrl+C SIGINT)\\n`,\n );\n };\n const onDetach = (): void => {\n paused = false;\n repaint();\n };\n\n process.once('SIGINT', onSigint);\n\n if (!viewerMode) {\n if (process.stdin.isTTY) {\n try {\n process.stdin.setRawMode(true);\n } catch {\n process.stdout.write(\n colors.yellow(\n 'Warning: Unable to enable raw mode for interactive key handling.\\n',\n ),\n );\n }\n process.stdin.resume(); // keep stdin flowing (no encoding — raw Buffer)\n }\n\n cleanupSwitcher = installInteractiveSwitcher({\n workers,\n onAttach,\n onDetach,\n onCtrlC: onSigint,\n getLogPaths: (id) => safeGetLogPathsForSlot(id, workers, slotLogs),\n replayBytes: 200 * 1024,\n replayWhich: ['out', 'err'],\n onEnterAttachScreen: onAttach,\n });\n\n if (opts.extraKeyHandler) {\n extraHandler = opts.extraKeyHandler({\n logsBySlot: slotLogs,\n repaint: () => repaint(),\n setPaused: (p) => {\n paused = p;\n },\n });\n process.stdin.on('data', extraHandler);\n }\n }\n\n /* Wait for full completion, then post-process (with log context if needed). */\n await new Promise<void>((resolve) => {\n const check = setInterval(async () => {\n if (activeWorkers === 0) {\n clearInterval(check);\n if (ticker) clearInterval(ticker);\n cleanupSwitcher();\n\n if (extraHandler) {\n try {\n process.stdin.off('data', extraHandler);\n } catch {\n /* noop */\n }\n }\n tearDownStdin();\n\n const finishedAt = Date.now();\n\n try {\n await hooks.postProcess?.({\n slots: workerState,\n totals: totalsBox,\n logDir,\n logsBySlot: slotLogs,\n startedAt,\n finishedAt,\n viewerMode,\n getLogPathsForSlot: (id: number) =>\n safeGetLogPathsForSlot(id, workers, slotLogs),\n });\n } catch (err: unknown) {\n const msg =\n (\n err as {\n /** Error stack */\n stack?: string;\n }\n )?.stack ?? String(err);\n process.stdout.write(colors.red(`postProcess error: ${msg}\\n`));\n }\n resolve();\n }\n }, 300);\n });\n}\n/* eslint-enable max-lines */\n","import type { ExportStatusMap } from './logRotation';\nimport { showCombinedLogs, type LogLocation } from './showCombinedLogs';\nimport type { SlotPaths } from './spawnWorkerProcess';\n\n/** Severity filter applied by the viewer. */\ntype ViewLevel = 'error' | 'warn' | 'all';\n\n/**\n * Options for {@link createExtraKeyHandler}.\n */\nexport type CreateExtraKeyHandlerOpts = {\n /**\n * Per-slot log file paths maintained by the runner; used to stream or export logs.\n */\n logsBySlot: SlotPaths;\n\n /**\n * Request an immediate dashboard repaint (e.g., after updating export status).\n */\n repaint: () => void;\n\n /**\n * Pause/unpause dashboard repainting. The handler pauses while a viewer is open\n * to prevent the dashboard from overwriting the viewer output, then resumes on exit.\n */\n setPaused: (p: boolean) => void;\n\n /**\n * Optional export manager to enable uppercase export keys:\n * - `E` (errors) • `W` (warnings) • `I` (info) • `A` (all)\n *\n * Provide this only if your command supports writing combined log files.\n */\n exportMgr?: {\n /** Destination directory for exported artifacts. */\n exportsDir: string;\n /**\n * Write a combined log file for the selected severity and return the absolute path.\n *\n * @param logs - Log paths to combine.\n * @param which - Severity selection.\n * @returns Absolute path to the written file.\n */\n exportCombinedLogs: (\n logs: SlotPaths,\n which: 'error' | 'warn' | 'info' | 'all',\n ) => string;\n };\n\n /**\n * Optional “Exports” status map. If provided, the handler updates timestamps\n * when exports are written so your dashboard panel can reflect “last saved” times.\n */\n exportStatus?: ExportStatusMap;\n\n /**\n * Optional custom key bindings for command-specific actions.\n * Each handler receives helpers to print messages and to update the exports panel.\n *\n * Example:\n * ```ts\n * custom: {\n * F: async ({ say, noteExport }) => {\n * const p = await writeFailingUpdatesCsv(...);\n * say(`Wrote failing updates to: ${p}`);\n * noteExport('failuresCsv', p);\n * }\n * }\n * ```\n */\n custom?: Record<\n string,\n (ctx: {\n /** Update {@link exportStatus} (if present) and repaint the dashboard. */\n noteExport: (slot: keyof ExportStatusMap, absPath: string) => void;\n /** Print a line to stdout, automatically newline-terminated. */\n say: (s: string) => void;\n }) => void | Promise<void>\n >;\n};\n\n/**\n * Create a keypress handler for interactive viewers/exports.\n * Shared handler for \"extra\" keyboard shortcuts used by the interactive dashboard.\n *\n * It wires:\n * - **Viewers (lowercase):** `e` (errors), `w` (warnings), `i` (info), `l` (all)\n * - **Exports (uppercase, optional):** `E` (errors), `W` (warnings), `I` (info), `A` (all)\n * - **Dismiss:** `Esc` or `Ctrl+]` exits a viewer and returns to the dashboard\n * - **Custom keys (optional):** Provide a `custom` map to handle command-specific bindings\n *\n * Usage (inside `runPool({... extraKeyHandler })`):\n * ```ts\n * extraKeyHandler: ({ logsBySlot, repaint, setPaused }) =>\n * createExtraKeyHandler({ logsBySlot, repaint, setPaused })\n * ```\n *\n * If you also want export hotkeys + an \"Exports\" panel:\n * ```ts\n * extraKeyHandler: ({ logsBySlot, repaint, setPaused }) =>\n * createExtraKeyHandler({\n * logsBySlot, repaint, setPaused,\n * exportMgr, // enables E/W/I/A\n * exportStatus, // keeps panel timestamps up to date\n * custom: { // optional, e.g. 'F' to export a CSV\n * F: async ({ say, noteExport }) => { ... }\n * }\n * })\n * ```\n *\n * @param opts - Configuration for viewers, exports, and custom keys.\n * @returns A `(buf: Buffer) => void` handler suitable for `process.stdin.on('data', ...)`.\n */\nexport function createExtraKeyHandler(\n opts: CreateExtraKeyHandlerOpts,\n): (buf: Buffer) => void {\n const { logsBySlot, repaint, setPaused, exportMgr, exportStatus, custom } =\n opts;\n\n const say = (s: string): void => {\n process.stdout.write(`${s}\\n`);\n };\n\n /**\n * Record that an export was written and trigger a repaint so the dashboard’s\n * \"Exports\" panel shows the updated timestamp/path.\n *\n * @param slot - Slot name in {@link ExportStatusMap} (e.g., \"error\", \"warn\", etc.).\n * @param p - Absolute path to the exported file.\n */\n const noteExport = (slot: keyof ExportStatusMap, p: string): void => {\n const now = Date.now();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const cur: any = exportStatus?.[slot] ?? { path: p };\n if (exportStatus) {\n exportStatus[slot] = {\n path: p || cur.path,\n savedAt: now,\n exported: true,\n };\n repaint();\n }\n };\n\n let viewing = false; // optional guard to prevent stacking viewers\n\n /**\n * Show an inline combined log viewer for the selected sources/level.\n * Pauses dashboard repaint to keep the viewer visible until the user exits.\n *\n * @param sources - Log sources to include (e.g., \"err\", \"warn\", \"info\").\n * @param level - Severity level to filter by (e.g., \"error\", \"warn\", \"all\").\n */\n const view = (sources: LogLocation[], level: ViewLevel): void => {\n if (viewing) return;\n viewing = true;\n setPaused(true);\n\n // optional UX: clear screen and show a hint\n process.stdout.write('\\x1b[2J\\x1b[H'); // clear+home\n process.stdout.write(\n 'Combined logs viewer (press Esc or Ctrl+] to return)\\n\\n',\n );\n\n (async () => {\n try {\n await showCombinedLogs(logsBySlot, sources, level);\n // NOTE: do NOT unpause here; ESC will handle it.\n } catch {\n // If showCombinedLogs throws, recover and unpause\n viewing = false;\n setPaused(false);\n repaint();\n }\n })();\n };\n\n /**\n * Export combined logs (if an export manager was provided).\n *\n * @param which - Severity to export (e.g., \"error\", \"warn\", \"info\", \"all\").\n * @param label - Human-readable label for the export (e.g., \"error\", \"warn\").\n */\n const exportCombined = (\n which: 'error' | 'warn' | 'info' | 'all',\n label: string,\n ): void => {\n if (!exportMgr) return;\n try {\n const p = exportMgr.exportCombinedLogs(logsBySlot, which);\n say(`\\nWrote combined ${label} logs to: ${p}`);\n noteExport(which as keyof ExportStatusMap, p);\n } catch {\n say(`\\nFailed to write combined ${label} logs`);\n }\n };\n\n // The keypress handler the runner will attach to stdin.\n return (buf: Buffer): void => {\n const s = buf.toString('utf8');\n\n // Viewers (lowercase)\n if (s === 'e') {\n view(['err'], 'error');\n return;\n }\n if (s === 'w') {\n view(['warn', 'err'], 'warn');\n return;\n }\n if (s === 'i') {\n view(['info'], 'all');\n return;\n }\n if (s === 'l') {\n view(['out', 'err', 'structured'], 'all');\n return;\n }\n\n // Exports (uppercase) — enabled only when exportMgr is present\n if (s === 'E') {\n exportCombined('error', 'error');\n return;\n }\n if (s === 'W') {\n exportCombined('warn', 'warn');\n return;\n }\n if (s === 'I') {\n exportCombined('info', 'info');\n return;\n }\n if (s === 'A') {\n exportCombined('all', 'ALL');\n return;\n }\n\n // Command-specific bindings\n const fn = custom?.[s];\n if (fn) {\n fn({ noteExport, say });\n return;\n }\n\n // Exit a viewer (Esc / Ctrl+]) — resume dashboard\n if (s === '\\x1b' || s === '\\x1d') {\n viewing = false;\n setPaused(false);\n repaint();\n }\n };\n}\n","import { createReadStream, createWriteStream } from 'node:fs';\nimport { mkdir, readdir, unlink, stat } from 'node:fs/promises';\nimport { pipeline } from 'node:stream/promises';\nimport { Transform } from 'node:stream';\nimport { once } from 'node:events';\nimport { Parser } from 'csv-parse';\nimport { basename, dirname, join } from 'node:path';\nimport colors from 'colors';\nimport * as fastcsv from 'fast-csv';\nimport { logger } from '../../logger';\n\n/**\n * Options for chunking a single CSV file\n */\nexport type ChunkOpts = {\n /** Path to the CSV file to chunk */\n filePath: string;\n /** Output directory for chunk files; defaults to the same directory as the input file */\n outputDir?: string;\n /** Clear output directory before starting */\n clearOutputDir: boolean;\n /** Chunk size in MB */\n chunkSizeMB: number;\n /** Optional report interval in milliseconds for progress updates */\n reportEveryMs?: number;\n /** Callback for progress updates */\n onProgress: (processed: number, total?: number) => void;\n};\n\n/**\n * Create a CSV writer (fast-csv formatter piped to a write stream) that writes\n * a header line first, and then accepts object rows. Returns a tiny API to\n * write rows with backpressure handling and to close the file cleanly.\n *\n * @param filePath - The path to the output CSV file\n * @param headers - The headers for the CSV file\n * @returns An object with `write` and `end` methods\n */\nfunction createCsvChunkWriter(\n filePath: string,\n headers: string[],\n): {\n /** Write a row object to the CSV file */\n write: (row: Record<string, unknown>) => Promise<void>;\n /** Close the CSV file, ensuring all data is flushed */\n end: () => Promise<void>;\n} {\n const ws = createWriteStream(filePath);\n const csv = fastcsv.format({ headers, writeHeaders: true, objectMode: true });\n // Pipe csv → file stream\n csv.pipe(ws);\n\n return {\n /**\n * Write a row object to the CSV file.\n *\n * @param row - The row data as an object\n */\n async write(row) {\n // Respect backpressure from fast-csv formatter\n const ok = csv.write(row);\n if (!ok) {\n await once(csv, 'drain');\n }\n },\n /**\n * Close the CSV file, ensuring all data is flushed.\n */\n async end() {\n // End formatter; wait for underlying file stream to finish flush/close\n const finished = Promise.all([once(ws, 'finish')]);\n csv.end();\n await finished;\n },\n };\n}\n\n/**\n * Zero-pad chunk numbers to four digits (e.g., 1 → \"0001\").\n *\n * @param n - The chunk number to pad\n * @returns The padded chunk number as a string\n */\nfunction pad4(n: number): string {\n return String(n).padStart(4, '0');\n}\n\n/**\n * Approximate row size in bytes using comma-joined field values.\n *\n * @param obj - The row object to estimate size for\n * @returns Approximate byte size of the row when serialized as CSV\n */\nfunction approxRowBytes(obj: Record<string, unknown>): number {\n // naive but fast; adequate for chunk rollover thresholding\n return Buffer.byteLength(\n Object.values(obj)\n .map((v) => (v == null ? '' : String(v)))\n .join(','),\n 'utf8',\n );\n}\n\n/**\n * Stream a single CSV file and write chunk files of roughly chunkSizeMB.\n * - Writes header to each chunk.\n * - Logs periodic progress via onProgress.\n *\n * @param opts - Options for chunking the file\n * @returns Promise that resolves when done\n */\nexport async function chunkOneCsvFile(opts: ChunkOpts): Promise<void> {\n const {\n filePath,\n outputDir,\n clearOutputDir,\n chunkSizeMB,\n onProgress,\n reportEveryMs = 500,\n } = opts;\n const { size: fileBytes } = await stat(filePath); // total bytes on disk\n let lastTick = 0;\n\n logger.info(\n colors.magenta(`Chunking ${filePath} into ~${chunkSizeMB}MB files...`),\n );\n\n const chunkSizeBytes = Math.floor(chunkSizeMB * 1024 * 1024);\n const baseName = basename(filePath, '.csv');\n const outDir = outputDir || dirname(filePath);\n logger.info(colors.magenta(`Output directory: ${outDir}`));\n await mkdir(outDir, { recursive: true });\n\n // Clear previous chunk files for this base\n if (clearOutputDir) {\n logger.warn(colors.yellow(`Clearing output directory: ${outDir}`));\n const files = await readdir(outDir);\n await Promise.all(\n files\n .filter((f) => f.startsWith(`${baseName}_chunk_`) && f.endsWith('.csv'))\n .map((f) => unlink(join(outDir, f))),\n );\n }\n\n let headerRow: string[] | null = null;\n let expectedCols: number | null = null;\n let totalLines = 0;\n let currentChunk = 1;\n let currentSize = 0;\n\n const parser = new Parser({\n columns: false,\n skip_empty_lines: true,\n });\n\n // running sample to estimate avg row bytes\n let sampleBytes = 0;\n let sampleRows = 0;\n\n const emit = (): void => {\n const avg = sampleRows > 0 ? sampleBytes / sampleRows : 0;\n const estTotal =\n avg > 0 ? Math.max(totalLines, Math.ceil(fileBytes / avg)) : undefined;\n onProgress(totalLines, estTotal); // <-- now has total\n lastTick = Date.now();\n };\n\n // seed an initial 0/N as soon as we start\n emit();\n\n // Current active chunk writer; created after we know headers\n let writer: {\n /** Write a row object to the current chunk file */\n write: (row: Record<string, unknown>) => Promise<void>;\n /** Close the current chunk file */\n end: () => Promise<void>;\n } | null = null;\n\n // Returns current chunk file path — chunk number is always 4-digit padded\n const currentChunkPath = (): string =>\n join(outDir, `${baseName}_chunk_${pad4(currentChunk)}.csv`);\n\n const t = new Transform({\n objectMode: true,\n /**\n * Transform each row of the CSV file into a chunk.\n *\n * @param row - The current row being processed\n * @param _enc - Encoding (not used)\n * @param cb - Callback to signal completion or error\n */\n async transform(row: string[], _enc, cb) {\n try {\n // First row is the header\n if (!headerRow) {\n headerRow = row.slice(0);\n expectedCols = headerRow.length;\n\n // Open first chunk with header asynchronously\n writer = createCsvChunkWriter(currentChunkPath(), headerRow);\n cb();\n return;\n }\n\n // sanity check rows (non-fatal)\n if (expectedCols !== null && row.length !== expectedCols) {\n // optionally log a warning or collect metrics\n logger.warn(\n colors.yellow(\n `Row has ${row.length} cols; expected ${expectedCols}`,\n ),\n );\n }\n\n totalLines += 1;\n if (totalLines % 250_000 === 0) {\n onProgress(totalLines);\n }\n\n // Build row object using the original header\n const obj = Object.fromEntries(headerRow!.map((h, i) => [h, row[i]]));\n\n // Determine the row size up-front\n const rowBytes = approxRowBytes(obj);\n sampleBytes += rowBytes;\n sampleRows += 1;\n\n // time-based throttle for UI updates\n if (Date.now() - lastTick >= reportEveryMs) emit();\n\n // If adding this row would exceed the threshold, roll first,\n // so this row becomes the first row in the next chunk.\n if (\n writer &&\n currentSize > 0 &&\n currentSize + rowBytes > chunkSizeBytes\n ) {\n await writer.end();\n currentChunk += 1;\n currentSize = 0;\n logger.info(\n colors.green(\n `Rolling to chunk ${currentChunk} after ${totalLines.toLocaleString()} rows.`,\n ),\n );\n writer = createCsvChunkWriter(currentChunkPath(), headerRow!);\n }\n\n // Ensure writer exists (should after header)\n if (!writer) {\n writer = createCsvChunkWriter(currentChunkPath(), headerRow!);\n }\n\n // Write row and update approximate size\n await writer.write(obj);\n currentSize += rowBytes;\n\n cb();\n } catch (e) {\n cb(e as Error);\n }\n },\n\n // Ensure final file is closed\n /**\n * Flush is called when the readable has ended; we close any open writer.\n *\n * @param cb - Callback to signal completion or error\n */\n async flush(cb) {\n try {\n if (writer) {\n await writer.end();\n writer = null;\n }\n emit(); // Final progress tick\n cb();\n } catch (e) {\n cb(e as Error);\n }\n },\n });\n\n const rs = createReadStream(filePath);\n await pipeline(rs, parser, t);\n\n // Final progress tick\n onProgress(totalLines);\n logger.info(\n colors.green(\n `Chunked ${filePath} into ${currentChunk} file(s); processed ${totalLines.toLocaleString()} rows.`,\n ),\n );\n}\n"]}